diff options
Diffstat (limited to 'comm/suite/editor')
213 files changed, 32335 insertions, 0 deletions
diff --git a/comm/suite/editor/base/content/ComposerCommands.js b/comm/suite/editor/base/content/ComposerCommands.js new file mode 100644 index 0000000000..daff0d4563 --- /dev/null +++ b/comm/suite/editor/base/content/ComposerCommands.js @@ -0,0 +1,4051 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implementations of nsIControllerCommand for composer commands */ + +// Linting is disabled in chunks of this file because it contains code that never +// runs in Thunderbird, and references things that don't exist in Thunderbird. + +/* import-globals-from editor.js */ +/* import-globals-from editorUtilities.js */ +/* globals CreatePublishDataFromUrl editPage FormatDirForPublishing getTopWin + goPreferences nsIPromptService openComposeWindow openNewPrivateWith + PrintPreviewListener SavePublishDataToPrefs SavePassword savePWObj */ + +var gComposerJSCommandControllerID = 0; + +function SetupHTMLEditorCommands() { + var commandTable = GetComposerCommandTable(); + if (!commandTable) { + return; + } + + // Include everything a text editor does + SetupTextEditorCommands(); + + // dump("Registering HTML editor commands\n"); + + commandTable.registerCommand("cmd_renderedHTMLEnabler", nsDummyHTMLCommand); + + commandTable.registerCommand("cmd_grid", nsGridCommand); + + commandTable.registerCommand("cmd_listProperties", nsListPropertiesCommand); + commandTable.registerCommand("cmd_pageProperties", nsPagePropertiesCommand); + commandTable.registerCommand("cmd_colorProperties", nsColorPropertiesCommand); + commandTable.registerCommand("cmd_increaseFontStep", nsIncreaseFontCommand); + commandTable.registerCommand("cmd_decreaseFontStep", nsDecreaseFontCommand); + commandTable.registerCommand( + "cmd_advancedProperties", + nsAdvancedPropertiesCommand + ); + commandTable.registerCommand( + "cmd_objectProperties", + nsObjectPropertiesCommand + ); + commandTable.registerCommand( + "cmd_removeNamedAnchors", + nsRemoveNamedAnchorsCommand + ); + commandTable.registerCommand("cmd_editLink", nsEditLinkCommand); + + commandTable.registerCommand("cmd_form", nsFormCommand); + commandTable.registerCommand("cmd_inputtag", nsInputTagCommand); + commandTable.registerCommand("cmd_inputimage", nsInputImageCommand); + commandTable.registerCommand("cmd_textarea", nsTextAreaCommand); + commandTable.registerCommand("cmd_select", nsSelectCommand); + commandTable.registerCommand("cmd_button", nsButtonCommand); + commandTable.registerCommand("cmd_label", nsLabelCommand); + commandTable.registerCommand("cmd_fieldset", nsFieldSetCommand); + commandTable.registerCommand("cmd_image", nsImageCommand); + commandTable.registerCommand("cmd_hline", nsHLineCommand); + commandTable.registerCommand("cmd_link", nsLinkCommand); + commandTable.registerCommand("cmd_anchor", nsAnchorCommand); + commandTable.registerCommand( + "cmd_insertHTMLWithDialog", + nsInsertHTMLWithDialogCommand + ); + commandTable.registerCommand( + "cmd_insertMathWithDialog", + nsInsertMathWithDialogCommand + ); + commandTable.registerCommand("cmd_insertBreak", nsInsertBreakCommand); + commandTable.registerCommand("cmd_insertBreakAll", nsInsertBreakAllCommand); + + commandTable.registerCommand("cmd_table", nsInsertOrEditTableCommand); + commandTable.registerCommand("cmd_editTable", nsEditTableCommand); + commandTable.registerCommand("cmd_SelectTable", nsSelectTableCommand); + commandTable.registerCommand("cmd_SelectRow", nsSelectTableRowCommand); + commandTable.registerCommand("cmd_SelectColumn", nsSelectTableColumnCommand); + commandTable.registerCommand("cmd_SelectCell", nsSelectTableCellCommand); + commandTable.registerCommand( + "cmd_SelectAllCells", + nsSelectAllTableCellsCommand + ); + commandTable.registerCommand("cmd_InsertTable", nsInsertTableCommand); + commandTable.registerCommand( + "cmd_InsertRowAbove", + nsInsertTableRowAboveCommand + ); + commandTable.registerCommand( + "cmd_InsertRowBelow", + nsInsertTableRowBelowCommand + ); + commandTable.registerCommand( + "cmd_InsertColumnBefore", + nsInsertTableColumnBeforeCommand + ); + commandTable.registerCommand( + "cmd_InsertColumnAfter", + nsInsertTableColumnAfterCommand + ); + commandTable.registerCommand( + "cmd_InsertCellBefore", + nsInsertTableCellBeforeCommand + ); + commandTable.registerCommand( + "cmd_InsertCellAfter", + nsInsertTableCellAfterCommand + ); + commandTable.registerCommand("cmd_DeleteTable", nsDeleteTableCommand); + commandTable.registerCommand("cmd_DeleteRow", nsDeleteTableRowCommand); + commandTable.registerCommand("cmd_DeleteColumn", nsDeleteTableColumnCommand); + commandTable.registerCommand("cmd_DeleteCell", nsDeleteTableCellCommand); + commandTable.registerCommand( + "cmd_DeleteCellContents", + nsDeleteTableCellContentsCommand + ); + commandTable.registerCommand("cmd_JoinTableCells", nsJoinTableCellsCommand); + commandTable.registerCommand("cmd_SplitTableCell", nsSplitTableCellCommand); + commandTable.registerCommand( + "cmd_TableOrCellColor", + nsTableOrCellColorCommand + ); + commandTable.registerCommand("cmd_NormalizeTable", nsNormalizeTableCommand); + commandTable.registerCommand("cmd_smiley", nsSetSmiley); + commandTable.registerCommand("cmd_ConvertToTable", nsConvertToTable); +} + +function SetupTextEditorCommands() { + var commandTable = GetComposerCommandTable(); + if (!commandTable) { + return; + } + + // dump("Registering plain text editor commands\n"); + + commandTable.registerCommand("cmd_findReplace", nsFindReplaceCommand); + commandTable.registerCommand("cmd_find", nsFindCommand); + commandTable.registerCommand("cmd_findNext", nsFindAgainCommand); + commandTable.registerCommand("cmd_findPrev", nsFindAgainCommand); + commandTable.registerCommand("cmd_rewrap", nsRewrapCommand); + commandTable.registerCommand("cmd_spelling", nsSpellingCommand); + commandTable.registerCommand("cmd_validate", nsValidateCommand); + commandTable.registerCommand("cmd_insertChars", nsInsertCharsCommand); +} + +function SetupComposerWindowCommands() { + // Don't need to do this if already done + if (gComposerWindowControllerID) { + return; + } + + // Create a command controller and register commands + // specific to Web Composer window (file-related commands, HTML Source...) + // We can't use the composer controller created on the content window else + // we can't process commands when in HTMLSource editor + // IMPORTANT: For each of these commands, the doCommand method + // must first call SetEditMode(gPreviousNonSourceDisplayMode); + // to go from HTML Source mode to any other edit mode + + var windowControllers = window.controllers; + + if (!windowControllers) { + return; + } + + var commandTable; + var composerController; + var editorController; + try { + composerController = Cc[ + "@mozilla.org/embedcomp/base-command-controller;1" + ].createInstance(); + + editorController = composerController.QueryInterface( + Ci.nsIControllerContext + ); + + // Get the nsIControllerCommandTable interface we need to register commands + var interfaceRequestor = composerController.QueryInterface( + Ci.nsIInterfaceRequestor + ); + commandTable = interfaceRequestor.getInterface( + Ci.nsIControllerCommandTable + ); + } catch (e) { + dump("Failed to create composerController\n"); + return; + } + + if (!commandTable) { + dump("Failed to get interface for nsIControllerCommandManager\n"); + return; + } + + // File-related commands + commandTable.registerCommand("cmd_open", nsOpenCommand); + commandTable.registerCommand("cmd_save", nsSaveCommand); + commandTable.registerCommand("cmd_saveAs", nsSaveAsCommand); + commandTable.registerCommand("cmd_exportToText", nsExportToTextCommand); + commandTable.registerCommand( + "cmd_saveAndChangeEncoding", + nsSaveAndChangeEncodingCommand + ); + commandTable.registerCommand("cmd_publish", nsPublishCommand); + commandTable.registerCommand("cmd_publishAs", nsPublishAsCommand); + commandTable.registerCommand("cmd_publishSettings", nsPublishSettingsCommand); + commandTable.registerCommand("cmd_revert", nsRevertCommand); + commandTable.registerCommand("cmd_openRemote", nsOpenRemoteCommand); + commandTable.registerCommand("cmd_preview", nsPreviewCommand); + commandTable.registerCommand("cmd_editSendPage", nsSendPageCommand); + commandTable.registerCommand("cmd_print", nsPrintCommand); + commandTable.registerCommand("cmd_printpreview", nsPrintPreviewCommand); + commandTable.registerCommand("cmd_printSetup", nsPrintSetupCommand); + commandTable.registerCommand("cmd_close", nsCloseCommand); + commandTable.registerCommand("cmd_preferences", nsPreferencesCommand); + + // Edit Mode commands + if (GetCurrentEditorType() == "html") { + commandTable.registerCommand("cmd_NormalMode", nsNormalModeCommand); + commandTable.registerCommand("cmd_AllTagsMode", nsAllTagsModeCommand); + commandTable.registerCommand("cmd_HTMLSourceMode", nsHTMLSourceModeCommand); + commandTable.registerCommand("cmd_PreviewMode", nsPreviewModeCommand); + commandTable.registerCommand("cmd_FinishHTMLSource", nsFinishHTMLSource); + commandTable.registerCommand("cmd_CancelHTMLSource", nsCancelHTMLSource); + commandTable.registerCommand( + "cmd_updateStructToolbar", + nsUpdateStructToolbarCommand + ); + } + + windowControllers.insertControllerAt(0, editorController); + + // Store the controller ID so we can be sure to get the right one later + gComposerWindowControllerID = windowControllers.getControllerId( + editorController + ); +} + +function GetComposerCommandTable() { + var controller; + if (gComposerJSCommandControllerID) { + try { + controller = window.content.controllers.getControllerById( + gComposerJSCommandControllerID + ); + } catch (e) {} + } + if (!controller) { + // create it + controller = Cc[ + "@mozilla.org/embedcomp/base-command-controller;1" + ].createInstance(); + + var editorController = controller.QueryInterface(Ci.nsIControllerContext); + editorController.setCommandContext(GetCurrentEditorElement()); + window.content.controllers.insertControllerAt(0, controller); + + // Store the controller ID so we can be sure to get the right one later + gComposerJSCommandControllerID = window.content.controllers.getControllerId( + controller + ); + } + + if (controller) { + var interfaceRequestor = controller.QueryInterface( + Ci.nsIInterfaceRequestor + ); + return interfaceRequestor.getInterface(Ci.nsIControllerCommandTable); + } + return null; +} + +/* eslint-disable complexity */ +function goUpdateCommandState(command) { + try { + var controller = top.document.commandDispatcher.getControllerForCommand( + command + ); + if (!(controller instanceof Ci.nsICommandController)) { + return; + } + + var params = newCommandParams(); + if (!params) { + return; + } + + controller.getCommandStateWithParams(command, params); + + switch (command) { + case "cmd_bold": + case "cmd_italic": + case "cmd_underline": + case "cmd_var": + case "cmd_samp": + case "cmd_code": + case "cmd_acronym": + case "cmd_abbr": + case "cmd_cite": + case "cmd_strong": + case "cmd_em": + case "cmd_superscript": + case "cmd_subscript": + case "cmd_strikethrough": + case "cmd_tt": + case "cmd_nobreak": + case "cmd_ul": + case "cmd_ol": + pokeStyleUI(command, params.getBooleanValue("state_all")); + break; + + case "cmd_paragraphState": + case "cmd_align": + case "cmd_highlight": + case "cmd_backgroundColor": + case "cmd_fontColor": + case "cmd_fontFace": + case "cmd_fontSize": + case "cmd_absPos": + pokeMultiStateUI(command, params); + break; + + case "cmd_decreaseZIndex": + case "cmd_increaseZIndex": + case "cmd_indent": + case "cmd_outdent": + case "cmd_increaseFont": + case "cmd_decreaseFont": + case "cmd_increaseFontStep": + case "cmd_decreaseFontStep": + case "cmd_removeStyles": + case "cmd_smiley": + break; + + default: + dump("no update for command: " + command + "\n"); + } + } catch (e) { + dump( + "An error occurred updating the " + command + " command: \n" + e + "\n" + ); + } +} +/* eslint-enable complexity */ + +function goUpdateComposerMenuItems(commandset) { + // dump("Updating commands for " + commandset.id + "\n"); + + for (var i = 0; i < commandset.childNodes.length; i++) { + var commandNode = commandset.childNodes[i]; + var commandID = commandNode.id; + if (commandID) { + goUpdateCommand(commandID); // enable or disable + if (commandNode.hasAttribute("state")) { + goUpdateCommandState(commandID); + } + } + } +} + +function goDoCommandParams(command, params) { + try { + var controller = top.document.commandDispatcher.getControllerForCommand( + command + ); + if (controller && controller.isCommandEnabled(command)) { + if (controller instanceof Ci.nsICommandController) { + controller.doCommandWithParams(command, params); + + // the following two lines should be removed when we implement observers + if (params) { + controller.getCommandStateWithParams(command, params); + } + } else { + controller.doCommand(command); + } + ResetStructToolbar(); + } + } catch (e) { + dump("An error occurred executing the " + command + " command\n"); + } +} + +function pokeStyleUI(uiID, aDesiredState) { + try { + var commandNode = top.document.getElementById(uiID); + if (!commandNode) { + return; + } + + var uiState = "true" == commandNode.getAttribute("state"); + if (aDesiredState != uiState) { + commandNode.setAttribute("state", aDesiredState ? "true" : "false"); + } + } catch (e) { + dump("poking UI for " + uiID + " failed: " + e + "\n"); + } +} + +function doStyleUICommand(cmdStr) { + try { + var cmdParams = newCommandParams(); + goDoCommandParams(cmdStr, cmdParams); + if (cmdParams) { + pokeStyleUI(cmdStr, cmdParams.getBooleanValue("state_all")); + } + + ResetStructToolbar(); + } catch (e) {} +} + +// Copied from jsmime.js. +function stringToTypedArray(buffer) { + var typedarray = new Uint8Array(buffer.length); + for (var i = 0; i < buffer.length; i++) { + typedarray[i] = buffer.charCodeAt(i); + } + return typedarray; +} + +function pokeMultiStateUI(uiID, cmdParams) { + try { + var commandNode = document.getElementById(uiID); + if (!commandNode) { + return; + } + + var isMixed = cmdParams.getBooleanValue("state_mixed"); + var desiredAttrib; + if (isMixed) { + desiredAttrib = "mixed"; + } else { + var valuetype = cmdParams.getValueType("state_attribute"); + if (valuetype == Ci.nsICommandParams.eStringType) { + desiredAttrib = cmdParams.getCStringValue("state_attribute"); + // Decode UTF-8, for example for font names in Japanese. + desiredAttrib = new TextDecoder("UTF-8").decode( + stringToTypedArray(desiredAttrib) + ); + } else { + desiredAttrib = cmdParams.getStringValue("state_attribute"); + } + } + + var uiState = commandNode.getAttribute("state"); + if (desiredAttrib != uiState) { + commandNode.setAttribute("state", desiredAttrib); + } + } catch (e) {} +} + +function doStatefulCommand(commandID, newState) { + var commandNode = document.getElementById(commandID); + if (commandNode) { + commandNode.setAttribute("state", newState); + } + gContentWindow.focus(); // needed for command dispatch to work + + try { + var cmdParams = newCommandParams(); + if (!cmdParams) { + return; + } + + cmdParams.setStringValue("state_attribute", newState); + goDoCommandParams(commandID, cmdParams); + + pokeMultiStateUI(commandID, cmdParams); + + ResetStructToolbar(); + } catch (e) { + dump("error thrown in doStatefulCommand: " + e + "\n"); + } +} + +function PrintObject(obj) { + dump("-----" + obj + "------\n"); + var names = ""; + for (var i in obj) { + if (i == "value") { + names += i + ": " + obj.value + "\n"; + } else if (i == "id") { + names += i + ": " + obj.id + "\n"; + } else { + names += i + "\n"; + } + } + + dump(names + "-----------\n"); +} + +function PrintNodeID(id) { + PrintObject(document.getElementById(id)); +} + +var nsDummyHTMLCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + // do nothing + dump("Hey, who's calling the dummy command?\n"); + }, +}; + +var nsOpenCommand = { + isCommandEnabled(aCommand, dummy) { + // We can always do this. + return true; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + var fileType = IsHTMLEditor() ? "html" : "text"; + var title = GetString(IsHTMLEditor() ? "OpenHTMLFile" : "OpenTextFile"); + + var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); + fp.init(window, title, nsIFilePicker.modeOpen); + + SetFilePickerDirectory(fp, fileType); + + // Direct user to prefer HTML files and/or text files depending on whether + // loading into Composer or Text editor, so we call separately to control + // the order of the filter list. + if (fileType == "html") { + fp.appendFilters(nsIFilePicker.filterHTML); + } + fp.appendFilters(nsIFilePicker.filterText); + fp.appendFilters(nsIFilePicker.filterAll); + + fp.open(rv => { + if (rv == nsIFilePicker.returnCancel) { + return; + } + // editPage checks for already open window and activates it. + if (fp.fileURL.spec) { + SaveFilePickerDirectory(fp, fileType); + editPage(fp.fileURL.spec, fileType); + } + }); + }, +}; + +// STRUCTURE TOOLBAR +// +var nsUpdateStructToolbarCommand = { + isCommandEnabled(aCommand, dummy) { + UpdateStructToolbar(); + return true; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + doCommand(aCommand) {}, +}; + +// ******* File output commands and utilities ******** // +var nsSaveCommand = { + isCommandEnabled(aCommand, dummy) { + // Always allow saving when editing a remote document, + // otherwise the document modified state would prevent that + // when you first open a remote file. + try { + var docUrl = GetDocumentUrl(); + return ( + IsDocumentEditable() && + (IsDocumentModified() || + IsHTMLSourceChanged() || + IsUrlAboutBlank(docUrl) || + GetScheme(docUrl) != "file") + ); + } catch (e) { + return false; + } + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + var editor = GetCurrentEditor(); + if (editor) { + if (IsHTMLEditor()) { + SetEditMode(gPreviousNonSourceDisplayMode); + } + SaveDocument( + IsUrlAboutBlank(GetDocumentUrl()), + false, + editor.contentsMIMEType + ); + } + }, +}; + +var nsSaveAsCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + var editor = GetCurrentEditor(); + if (editor) { + if (IsHTMLEditor()) { + SetEditMode(gPreviousNonSourceDisplayMode); + } + SaveDocument(true, false, editor.contentsMIMEType); + } + }, +}; + +var nsExportToTextCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + if (GetCurrentEditor()) { + SetEditMode(gPreviousNonSourceDisplayMode); + SaveDocument(true, true, "text/plain"); + } + }, +}; + +var nsSaveAndChangeEncodingCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + SetEditMode(gPreviousNonSourceDisplayMode); + window.ok = false; + window.exportToText = false; + var oldTitle = GetDocumentTitle(); + window.openDialog( + "chrome://editor/content/EditorSaveAsCharset.xhtml", + "_blank", + "chrome,close,titlebar,modal,resizable=yes" + ); + + if (GetDocumentTitle() != oldTitle) { + UpdateWindowTitle(); + } + + if (window.ok) { + if (window.exportToText) { + SaveDocument(true, true, "text/plain"); + } else { + var editor = GetCurrentEditor(); + SaveDocument(true, false, editor ? editor.contentsMIMEType : null); + } + } + }, +}; + +var nsPublishCommand = { + isCommandEnabled(aCommand, dummy) { + if (IsDocumentEditable()) { + // Always allow publishing when editing a local document, + // otherwise the document modified state would prevent that + // when you first open any local file. + try { + var docUrl = GetDocumentUrl(); + return ( + IsDocumentModified() || + IsHTMLSourceChanged() || + IsUrlAboutBlank(docUrl) || + GetScheme(docUrl) == "file" + ); + } catch (e) { + return false; + } + } + return false; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + if (GetCurrentEditor()) { + let docUrl = GetDocumentUrl(); + let filename = GetFilename(docUrl); + let publishData; + + // First check pref to always show publish dialog + let showPublishDialog = Services.prefs.getBoolPref( + "editor.always_show_publish_dialog" + ); + + if (!showPublishDialog && filename) { + // Try to get publish data from the document url + publishData = CreatePublishDataFromUrl(docUrl); + + // If none, use default publishing site? Need a pref for this + // if (!publishData) + // publishData = GetPublishDataFromSiteName(GetDefaultPublishSiteName(), filename); + } + + if (showPublishDialog || !publishData) { + // Show the publish dialog + publishData = {}; + window.ok = false; + let oldTitle = GetDocumentTitle(); + window.openDialog( + "chrome://editor/content/EditorPublish.xhtml", + "_blank", + "chrome,close,titlebar,modal", + "", + "", + publishData + ); + if (GetDocumentTitle() != oldTitle) { + UpdateWindowTitle(); + } + + if (!window.ok) { + return false; + } + } + if (publishData) { + SetEditMode(gPreviousNonSourceDisplayMode); + return Publish(publishData); + } + } + return false; + }, +}; + +var nsPublishAsCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + if (GetCurrentEditor()) { + SetEditMode(gPreviousNonSourceDisplayMode); + + window.ok = false; + var publishData = {}; + var oldTitle = GetDocumentTitle(); + window.openDialog( + "chrome://editor/content/EditorPublish.xhtml", + "_blank", + "chrome,close,titlebar,modal", + "", + "", + publishData + ); + if (GetDocumentTitle() != oldTitle) { + UpdateWindowTitle(); + } + + if (window.ok) { + return Publish(publishData); + } + } + return false; + }, +}; + +// ------- output utilities ----- // + +// returns a fileExtension string +function GetExtensionBasedOnMimeType(aMIMEType) { + try { + var mimeService = null; + mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + + var fileExtension = mimeService.getPrimaryExtension(aMIMEType, null); + + // the MIME service likes to give back ".htm" for text/html files, + // so do a special-case fix here. + if (fileExtension == "htm") { + fileExtension = "html"; + } + + return fileExtension; + } catch (e) {} + return ""; +} + +function GetSuggestedFileName(aDocumentURLString, aMIMEType) { + var extension = GetExtensionBasedOnMimeType(aMIMEType); + if (extension) { + extension = "." + extension; + } + + // check for existing file name we can use + if (aDocumentURLString && !IsUrlAboutBlank(aDocumentURLString)) { + try { + let docURI = Services.io.newURI( + aDocumentURLString, + GetCurrentEditor().documentCharacterSet + ); + docURI = docURI.QueryInterface(Ci.nsIURL); + + // grab the file name + let url = validateFileName(decodeURIComponent(docURI.fileBaseName)); + if (url) { + return url + extension; + } + } catch (e) {} + } + + // Check if there is a title we can use to generate a valid filename, + // if we can't, use the default filename. + var title = + validateFileName(GetDocumentTitle()) || + GetString("untitledDefaultFilename"); + return title + extension; +} + +/** + * @return {Promise} dialogResult + */ +function PromptForSaveLocation( + aDoSaveAsText, + aEditorType, + aMIMEType, + aDocumentURLString +) { + var dialogResult = {}; + dialogResult.filepickerClick = nsIFilePicker.returnCancel; + dialogResult.resultingURI = ""; + dialogResult.resultingLocalFile = null; + + var fp = null; + try { + fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); + } catch (e) {} + if (!fp) { + return dialogResult; + } + + // determine prompt string based on type of saving we'll do + var promptString; + if (aDoSaveAsText || aEditorType == "text") { + promptString = GetString("SaveTextAs"); + } else { + promptString = GetString("SaveDocumentAs"); + } + + fp.init(window, promptString, nsIFilePicker.modeSave); + + // Set filters according to the type of output + if (aDoSaveAsText) { + fp.appendFilters(nsIFilePicker.filterText); + } else { + fp.appendFilters(nsIFilePicker.filterHTML); + } + fp.appendFilters(nsIFilePicker.filterAll); + + // now let's actually set the filepicker's suggested filename + var suggestedFileName = GetSuggestedFileName(aDocumentURLString, aMIMEType); + if (suggestedFileName) { + fp.defaultString = suggestedFileName; + } + + // set the file picker's current directory + // assuming we have information needed (like prior saved location) + try { + var fileHandler = GetFileProtocolHandler(); + + var isLocalFile = true; + try { + let docURI = Services.io.newURI( + aDocumentURLString, + GetCurrentEditor().documentCharacterSet + ); + isLocalFile = docURI.schemeIs("file"); + } catch (e) {} + + var parentLocation = null; + if (isLocalFile) { + var fileLocation = fileHandler.getFileFromURLSpec(aDocumentURLString); // this asserts if url is not local + parentLocation = fileLocation.parent; + } + if (parentLocation) { + // Save current filepicker's default location + if ("gFilePickerDirectory" in window) { + gFilePickerDirectory = fp.displayDirectory; + } + + fp.displayDirectory = parentLocation; + } else { + // Initialize to the last-used directory for the particular type (saved in prefs) + SetFilePickerDirectory(fp, aEditorType); + } + } catch (e) {} + + return new Promise(resolve => { + fp.open(rv => { + dialogResult.filepickerClick = rv; + if (rv != nsIFilePicker.returnCancel && fp.file) { + // Allow OK and replace. + // reset urlstring to new save location + dialogResult.resultingURIString = fileHandler.getURLSpecFromFile( + fp.file + ); + dialogResult.resultingLocalFile = fp.file; + SaveFilePickerDirectory(fp, aEditorType); + resolve(dialogResult); + } else if ("gFilePickerDirectory" in window && gFilePickerDirectory) { + fp.displayDirectory = gFilePickerDirectory; + resolve(null); + } + }); + }); +} + +/** + * If needed, prompt for document title and set the document title to the + * preferred value. + * @return true if the title was set up successfully; + * false if the user cancelled the title prompt + */ +function PromptAndSetTitleIfNone() { + if (GetDocumentTitle()) { + // we have a title; no need to prompt! + return true; + } + + let result = { value: null }; + let captionStr = GetString("DocumentTitle"); + let msgStr = GetString("NeedDocTitle") + "\n" + GetString("DocTitleHelp"); + let confirmed = Services.prompt.prompt( + window, + captionStr, + msgStr, + result, + null, + { value: 0 } + ); + if (confirmed) { + SetDocumentTitle(TrimString(result.value)); + } + + return confirmed; +} + +var gPersistObj; + +// Don't forget to do these things after calling OutputFileWithPersistAPI: +// we need to update the uri before notifying listeners +// if (doUpdateURI) +// SetDocumentURI(docURI); +// UpdateWindowTitle(); +// if (!aSaveCopy) +// editor.resetModificationCount(); +// this should cause notification to listeners that document has changed + +const webPersist = Ci.nsIWebBrowserPersist; +function OutputFileWithPersistAPI( + editorDoc, + aDestinationLocation, + aRelatedFilesParentDir, + aMimeType +) { + gPersistObj = null; + var editor = GetCurrentEditor(); + try { + editor.forceCompositionEnd(); + } catch (e) {} + + var isLocalFile = false; + try { + aDestinationLocation.QueryInterface(Ci.nsIFile); + isLocalFile = true; + } catch (e) { + try { + var tmp = aDestinationLocation.QueryInterface(Ci.nsIURI); + isLocalFile = tmp.schemeIs("file"); + } catch (e) {} + } + + try { + // we should supply a parent directory if/when we turn on functionality to save related documents + var persistObj = Cc[ + "@mozilla.org/embedding/browser/nsWebBrowserPersist;1" + ].createInstance(webPersist); + persistObj.progressListener = gEditorOutputProgressListener; + + var wrapColumn = GetWrapColumn(); + var outputFlags = GetOutputFlags(aMimeType, wrapColumn); + + // for 4.x parity as well as improving readability of file locally on server + // this will always send crlf for upload (http/ftp) + if (!isLocalFile) { + // if we aren't saving locally then send both cr and lf + outputFlags |= + webPersist.ENCODE_FLAGS_CR_LINEBREAKS | + webPersist.ENCODE_FLAGS_LF_LINEBREAKS; + + // we want to serialize the output for all remote publishing + // some servers can handle only one connection at a time + // some day perhaps we can make this user-configurable per site? + persistObj.persistFlags = + persistObj.persistFlags | webPersist.PERSIST_FLAGS_SERIALIZE_OUTPUT; + } + + // note: we always want to set the replace existing files flag since we have + // already given user the chance to not replace an existing file (file picker) + // or the user picked an option where the file is implicitly being replaced (save) + persistObj.persistFlags = + persistObj.persistFlags | + webPersist.PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS | + webPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES | + webPersist.PERSIST_FLAGS_DONT_FIXUP_LINKS | + webPersist.PERSIST_FLAGS_DONT_CHANGE_FILENAMES | + webPersist.PERSIST_FLAGS_FIXUP_ORIGINAL_DOM; + persistObj.saveDocument( + editorDoc, + aDestinationLocation, + aRelatedFilesParentDir, + aMimeType, + outputFlags, + wrapColumn + ); + gPersistObj = persistObj; + } catch (e) { + dump("caught an error, bail\n"); + return false; + } + + return true; +} + +// returns output flags based on mimetype, wrapCol and prefs +function GetOutputFlags(aMimeType, aWrapColumn) { + var outputFlags = 0; + var editor = GetCurrentEditor(); + var outputEntity = + editor && editor.documentCharacterSet == "ISO-8859-1" + ? webPersist.ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES + : webPersist.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES; + if (aMimeType == "text/plain") { + // When saving in "text/plain" format, always do formatting + outputFlags |= webPersist.ENCODE_FLAGS_FORMATTED; + } else { + // Should we prettyprint? Check the pref + if (Services.prefs.getBoolPref("editor.prettyprint")) { + outputFlags |= webPersist.ENCODE_FLAGS_FORMATTED; + } + + try { + // How much entity names should we output? Check the pref + switch (Services.prefs.getCharPref("editor.encode_entity")) { + case "basic": + outputEntity = webPersist.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES; + break; + case "latin1": + outputEntity = webPersist.ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES; + break; + case "html": + outputEntity = webPersist.ENCODE_FLAGS_ENCODE_HTML_ENTITIES; + break; + case "none": + outputEntity = 0; + break; + } + } catch (e) {} + } + outputFlags |= outputEntity; + + if (aWrapColumn > 0) { + outputFlags |= webPersist.ENCODE_FLAGS_WRAP; + } + + return outputFlags; +} + +// returns number of column where to wrap +const nsIWebBrowserPersist = Ci.nsIWebBrowserPersist; +function GetWrapColumn() { + try { + return GetCurrentEditor().wrapWidth; + } catch (e) {} + return 0; +} + +const gShowDebugOutputStateChange = false; +const gShowDebugOutputProgress = false; +const gShowDebugOutputStatusChange = false; + +const gShowDebugOutputLocationChange = false; +const gShowDebugOutputSecurityChange = false; + +const nsIWebProgressListener = Ci.nsIWebProgressListener; +const nsIChannel = Ci.nsIChannel; + +const kErrorBindingAborted = 2152398850; +const kErrorBindingRedirected = 2152398851; +const kFileNotFound = 2152857618; + +var gEditorOutputProgressListener = { + /* eslint-disable complexity */ + onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { + var editor = GetCurrentEditor(); + + // Use this to access onStateChange flags + var requestSpec; + try { + var channel = aRequest.QueryInterface(nsIChannel); + requestSpec = StripUsernamePasswordFromURI(channel.URI); + } catch (e) { + if (gShowDebugOutputStateChange) { + dump("***** onStateChange; NO REQUEST CHANNEL\n"); + } + } + + var pubSpec; + if (gPublishData) { + pubSpec = + gPublishData.publishUrl + gPublishData.docDir + gPublishData.filename; + } + + if (gShowDebugOutputStateChange) { + dump("\n***** onStateChange request: " + requestSpec + "\n"); + dump(" state flags: "); + + if (aStateFlags & nsIWebProgressListener.STATE_START) { + dump(" STATE_START, "); + } + if (aStateFlags & nsIWebProgressListener.STATE_STOP) { + dump(" STATE_STOP, "); + } + if (aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) { + dump(" STATE_IS_NETWORK "); + } + + dump( + `\n * requestSpec=${requestSpec}, pubSpec=${pubSpec}, aStatus=${aStatus}\n` + ); + + DumpDebugStatus(aStatus); + } + // The rest only concerns publishing, so bail out if no dialog + if (!gProgressDialog) { + return; + } + + // Detect start of file upload of any file: + // (We ignore any START messages after gPersistObj says publishing is finished + if ( + aStateFlags & nsIWebProgressListener.STATE_START && + gPersistObj && + requestSpec && + gPersistObj.currentState != gPersistObj.PERSIST_STATE_FINISHED + ) { + document + .getElementById("navigator-throbber") + .setAttribute("busy", "true"); + try { + // Add url to progress dialog's list showing each file uploading + gProgressDialog.SetProgressStatus(GetFilename(requestSpec), "busy"); + } catch (e) {} + } + + // Detect end of file upload of any file: + if (aStateFlags & nsIWebProgressListener.STATE_STOP) { + document.getElementById("navigator-throbber").removeAttribute("busy"); + // ignore aStatus == kErrorBindingAborted; check http response for possible errors + try { + // check http channel for response: 200 range is ok; other ranges are not + var httpChannel = aRequest.QueryInterface(Ci.nsIHttpChannel); + var httpResponse = httpChannel.responseStatus; + if (httpResponse < 200 || httpResponse >= 300) { + // Not a real error but enough to pass check below. + aStatus = httpResponse; + } else if (aStatus == kErrorBindingAborted) { + aStatus = 0; + } + + if (gShowDebugOutputStateChange) { + dump("http response is: " + httpResponse + "\n"); + } + } catch (e) { + if (aStatus == kErrorBindingAborted) { + aStatus = 0; + } + } + + // We abort publishing for all errors except if image src file is not found + var abortPublishing = aStatus != 0 && aStatus != kFileNotFound; + + // Notify progress dialog when we receive the STOP + // notification for a file if there was an error + // or a successful finish + // (Check requestSpec to be sure message is for destination url) + if ( + aStatus != 0 || + (requestSpec && + requestSpec.startsWith(GetScheme(gPublishData.publishUrl))) + ) { + try { + gProgressDialog.SetProgressFinished( + GetFilename(requestSpec), + aStatus + ); + } catch (e) {} + } + + if (abortPublishing) { + // Cancel publishing + gPersistObj.cancelSave(); + + // Don't do any commands after failure + gCommandAfterPublishing = null; + + // Restore original document to undo image src url adjustments + if (gRestoreDocumentSource) { + try { + editor.rebuildDocumentFromSource(gRestoreDocumentSource); + + // Clear transaction cache since we just did a potentially + // very large insert and this will eat up memory + editor.clearUndoRedo(); + } catch (e) {} + } + + // Notify progress dialog that we're finished + // and keep open to show error + gProgressDialog.SetProgressFinished(null, 0); + + // We don't want to change location or reset mod count, etc. + return; + } + + // XXX HACK: "file://" protocol is not supported in network code + // (bug 151867 filed to add this support, bug 151869 filed + // to remove this and other code in nsIWebBrowserPersist) + // nsIWebBrowserPersist *does* copy the file(s), but we don't + // get normal onStateChange messages. + + // Case 1: If images are included, we get fairly normal + // STATE_START/STATE_STOP & STATE_IS_NETWORK messages associated with the image files, + // thus we must finish HTML file progress below + + // Case 2: If just HTML file is uploaded, we get STATE_START and STATE_STOP + // notification with a null "requestSpec", and + // the gPersistObj is destroyed before we get here! + // So create an new object so we can flow through normal processing below + if ( + !requestSpec && + GetScheme(gPublishData.publishUrl) == "file" && + (!gPersistObj || + gPersistObj.currentState == + nsIWebBrowserPersist.PERSIST_STATE_FINISHED) + ) { + aStateFlags |= nsIWebProgressListener.STATE_IS_NETWORK; + if (!gPersistObj) { + gPersistObj = { + result: aStatus, + currentState: nsIWebBrowserPersist.PERSIST_STATE_FINISHED, + }; + } + } + + // STATE_IS_NETWORK signals end of publishing, as does the gPersistObj.currentState + if ( + aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK && + gPersistObj.currentState == nsIWebBrowserPersist.PERSIST_STATE_FINISHED + ) { + if (GetScheme(gPublishData.publishUrl) == "file") { + // XXX "file://" hack: We don't get notified about the HTML file, so end progress for it + // (This covers both "Case 1 and 2" described above) + gProgressDialog.SetProgressFinished( + gPublishData.filename, + gPersistObj.result + ); + } + + if (gPersistObj.result == 0) { + // All files are finished and publishing succeeded (some images may have failed) + try { + // Make a new docURI from the "browse location" in case "publish location" was FTP + // We need to set document uri before notifying listeners + var docUrl = GetDocUrlFromPublishData(gPublishData); + SetDocumentURI( + Services.io.newURI(docUrl, editor.documentCharacterSet) + ); + + UpdateWindowTitle(); + + // this should cause notification to listeners that doc has changed + editor.resetModificationCount(); + + // Set UI based on whether we're editing a remote or local url + // Why is urlstring undefined? + /* eslint-disable-next-line no-undef */ + SetSaveAndPublishUI(urlstring); + } catch (e) {} + + // Save publishData to prefs + if (gPublishData) { + if (gPublishData.savePublishData) { + // We published successfully, so we can safely + // save docDir and otherDir to prefs + gPublishData.saveDirs = true; + SavePublishDataToPrefs(gPublishData); + } else { + SavePassword(gPublishData); + } + } + + // Ask progress dialog to close, but it may not + // if user checked checkbox to keep it open + gProgressDialog.RequestCloseDialog(); + } else { + // We previously aborted publishing because of error: + // Calling gPersistObj.cancelSave() resulted in a non-zero gPersistObj.result, + // so notify progress dialog we're finished + gProgressDialog.SetProgressFinished(null, 0); + } + } + } + }, + /* eslint-enable complexity */ + + onProgressChange( + aWebProgress, + aRequest, + aCurSelfProgress, + aMaxSelfProgress, + aCurTotalProgress, + aMaxTotalProgress + ) { + if (!gPersistObj) { + return; + } + + if (gShowDebugOutputProgress) { + dump( + "\n onProgressChange: gPersistObj.result=" + gPersistObj.result + "\n" + ); + try { + var channel = aRequest.QueryInterface(nsIChannel); + dump("***** onProgressChange request: " + channel.URI.spec + "\n"); + } catch (e) {} + dump( + "***** self: " + + aCurSelfProgress + + " / " + + aMaxSelfProgress + + "\n" + ); + dump( + "***** total: " + + aCurTotalProgress + + " / " + + aMaxTotalProgress + + "\n\n" + ); + + if (gPersistObj.currentState == gPersistObj.PERSIST_STATE_READY) { + dump(" Persister is ready to save data\n\n"); + } else if (gPersistObj.currentState == gPersistObj.PERSIST_STATE_SAVING) { + dump(" Persister is saving data.\n\n"); + } else if ( + gPersistObj.currentState == gPersistObj.PERSIST_STATE_FINISHED + ) { + dump(" PERSISTER HAS FINISHED SAVING DATA\n\n\n"); + } + } + }, + + onLocationChange(aWebProgress, aRequest, aLocation, aFlags) { + if (gShowDebugOutputLocationChange) { + dump("***** onLocationChange: " + aLocation.spec + "\n"); + try { + var channel = aRequest.QueryInterface(nsIChannel); + dump("***** request: " + channel.URI.spec + "\n"); + } catch (e) {} + } + }, + + onStatusChange(aWebProgress, aRequest, aStatus, aMessage) { + if (gShowDebugOutputStatusChange) { + dump("***** onStatusChange: " + aMessage + "\n"); + try { + var channel = aRequest.QueryInterface(nsIChannel); + dump("***** request: " + channel.URI.spec + "\n"); + } catch (e) { + dump(" couldn't get request\n"); + } + + DumpDebugStatus(aStatus); + + if (gPersistObj) { + if (gPersistObj.currentState == gPersistObj.PERSIST_STATE_READY) { + dump(" Persister is ready to save data\n\n"); + } else if ( + gPersistObj.currentState == gPersistObj.PERSIST_STATE_SAVING + ) { + dump(" Persister is saving data.\n\n"); + } else if ( + gPersistObj.currentState == gPersistObj.PERSIST_STATE_FINISHED + ) { + dump(" PERSISTER HAS FINISHED SAVING DATA\n\n\n"); + } + } + } + }, + + onSecurityChange(aWebProgress, aRequest, state) { + if (gShowDebugOutputSecurityChange) { + try { + var channel = aRequest.QueryInterface(nsIChannel); + dump("***** onSecurityChange request: " + channel.URI.spec + "\n"); + } catch (e) {} + } + }, + + onContentBlockingEvent(aWebProgress, aRequest, aEvent) {}, + + QueryInterface: ChromeUtils.generateQI([ + "nsIWebProgressListener", + "nsISupportsWeakReference", + "nsIPrompt", + "nsIAuthPrompt", + ]), + + // nsIPrompt + alert(dlgTitle, text) { + Services.prompt.alert( + gProgressDialog ? gProgressDialog : window, + dlgTitle, + text + ); + }, + alertCheck(dialogTitle, text, checkBoxLabel, checkObj) { + Services.prompt.alert(window, dialogTitle, text); + }, + confirm(dlgTitle, text) { + return ConfirmWithTitle(dlgTitle, text, null, null); + }, + confirmCheck(dlgTitle, text, checkBoxLabel, checkObj) { + Services.prompt.confirmEx( + window, + dlgTitle, + text, + nsIPromptService.STD_OK_CANCEL_BUTTONS, + "", + "", + "", + checkBoxLabel, + checkObj + ); + }, + confirmEx( + dlgTitle, + text, + btnFlags, + btn0Title, + btn1Title, + btn2Title, + checkBoxLabel, + checkVal + ) { + return Services.prompt.confirmEx( + window, + dlgTitle, + text, + btnFlags, + btn0Title, + btn1Title, + btn2Title, + checkBoxLabel, + checkVal + ); + }, + + /** *********************************************************************** + * gEditorOutputProgressListener needs to implement both nsIPrompt * + * (providing alert) and nsIAuthPrompt (providing password saving). * + * Unfortunately, both interfaces specify prompt/promptPassword/ * + * promptUsernameAndPassword, albeit with conflicting method signatures. * + * Luckily, though, we only make use of their nsIAuthPrompt variants, * + * hence we can comment out the nsIPrompt ones here to avoid JavaScript * + * strict mode clutter. See bug 371174 for more information. * + ************************************************************************* + prompt : function(dlgTitle, text, inoutText, checkBoxLabel, checkObj) + { + return Services.prompt.prompt(window, dlgTitle, text, inoutText, checkBoxLabel, checkObj); + }, + promptPassword : function(dlgTitle, text, pwObj, checkBoxLabel, savePWObj) + { + var ret = false; + try { + // Note difference with nsIAuthPrompt::promptPassword, which has + // just "in" savePassword param, while nsIPrompt is "inout" + // Initialize with user's previous preference for this site + if (gPublishData) + savePWObj.value = gPublishData.savePassword; + + ret = Services.prompt.promptPassword(gProgressDialog ? gProgressDialog : window, + dlgTitle, text, pwObj, checkBoxLabel, savePWObj); + + if (!ret) + setTimeout(CancelPublishing, 0); + + if (ret && gPublishData) + UpdateUsernamePasswordFromPrompt(gPublishData, gPublishData.username, pwObj.value, savePWObj.value); + } catch(e) {} + + return ret; + }, + promptUsernameAndPassword : function(dlgTitle, text, userObj, pwObj, checkBoxLabel, savePWObj) + { + var ret = PromptUsernameAndPassword(dlgTitle, text, savePWObj.value, userObj, pwObj); + if (!ret) + setTimeout(CancelPublishing, 0); + + return ret; + }, + *************************************************************************/ + + select(dlgTitle, text, selectList, outSelection) { + return Services.prompt.select( + window, + dlgTitle, + text, + selectList, + outSelection + ); + }, + + // nsIAuthPrompt + prompt(dlgTitle, text, pwrealm, savePW, defaultText, result) { + var ret = Services.prompt.prompt( + gProgressDialog ? gProgressDialog : window, + dlgTitle, + text, + defaultText, + pwrealm, + savePWObj + ); + if (!ret) { + setTimeout(CancelPublishing, 0); + } + return ret; + }, + + promptUsernameAndPassword(dlgTitle, text, pwrealm, savePW, userObj, pwObj) { + var ret = PromptUsernameAndPassword(dlgTitle, text, savePW, userObj, pwObj); + if (!ret) { + setTimeout(CancelPublishing, 0); + } + return ret; + }, + + promptPassword(dlgTitle, text, pwrealm, savePW, pwObj) { + var ret = false; + try { + // Note difference with nsIPrompt::promptPassword, which has + // "inout" savePassword param, while nsIAuthPrompt is just "in" + // Also nsIAuth doesn't supply "checkBoxLabel" + // Initialize with user's previous preference for this site + var savePWObj = { value: savePW }; + // Initialize with user's previous preference for this site + if (gPublishData) { + savePWObj.value = gPublishData.savePassword; + } + + ret = Services.prompt.promptPassword( + gProgressDialog ? gProgressDialog : window, + dlgTitle, + text, + pwObj, + GetString("SavePassword"), + savePWObj + ); + + if (!ret) { + setTimeout(CancelPublishing, 0); + } + + if (ret && gPublishData) { + UpdateUsernamePasswordFromPrompt( + gPublishData, + gPublishData.username, + pwObj.value, + savePWObj.value + ); + } + } catch (e) {} + + return ret; + }, +}; + +function PromptUsernameAndPassword(dlgTitle, text, savePW, userObj, pwObj) { + // HTTP prompts us twice even if user Cancels from 1st attempt! + // So never put up dialog if there's no publish data + if (!gPublishData) { + return false; + } + + var ret = false; + try { + var savePWObj = { value: savePW }; + + // Initialize with user's previous preference for this site + if (gPublishData) { + // HTTP put uses this dialog if either username or password is bad, + // so prefill username input field with the previous value for modification + savePWObj.value = gPublishData.savePassword; + if (!userObj.value) { + userObj.value = gPublishData.username; + } + } + + ret = Services.prompt.promptUsernameAndPassword( + gProgressDialog ? gProgressDialog : window, + dlgTitle, + text, + userObj, + pwObj, + GetString("SavePassword"), + savePWObj + ); + if (ret && gPublishData) { + UpdateUsernamePasswordFromPrompt( + gPublishData, + userObj.value, + pwObj.value, + savePWObj.value + ); + } + } catch (e) {} + + return ret; +} + +/* eslint-disable complexity */ +function DumpDebugStatus(aStatus) { + // see nsError.h and netCore.h and ftpCore.h + + if (aStatus == kErrorBindingAborted) { + dump("***** status is NS_BINDING_ABORTED\n"); + } else if (aStatus == kErrorBindingRedirected) { + dump("***** status is NS_BINDING_REDIRECTED\n"); + } else if (aStatus == 2152398859) { + // in netCore.h 11 + dump("***** status is ALREADY_CONNECTED\n"); + } else if (aStatus == 2152398860) { + // in netCore.h 12 + dump("***** status is NOT_CONNECTED\n"); + } else if (aStatus == 2152398861) { + // in nsISocketTransportService.idl 13 + dump("***** status is CONNECTION_REFUSED\n"); + } else if (aStatus == 2152398862) { + // in nsISocketTransportService.idl 14 + dump("***** status is NET_TIMEOUT\n"); + } else if (aStatus == 2152398863) { + // in netCore.h 15 + dump("***** status is IN_PROGRESS\n"); + } else if (aStatus == 2152398864) { + // 0x804b0010 in netCore.h 16 + dump("***** status is OFFLINE\n"); + } else if (aStatus == 2152398865) { + // in netCore.h 17 + dump("***** status is NO_CONTENT\n"); + } else if (aStatus == 2152398866) { + // in netCore.h 18 + dump("***** status is UNKNOWN_PROTOCOL\n"); + } else if (aStatus == 2152398867) { + // in netCore.h 19 + dump("***** status is PORT_ACCESS_NOT_ALLOWED\n"); + } else if (aStatus == 2152398868) { + // in nsISocketTransportService.idl 20 + dump("***** status is NET_RESET\n"); + } else if (aStatus == 2152398869) { + // in ftpCore.h 21 + dump("***** status is FTP_LOGIN\n"); + } else if (aStatus == 2152398870) { + // in ftpCore.h 22 + dump("***** status is FTP_CWD\n"); + } else if (aStatus == 2152398871) { + // in ftpCore.h 23 + dump("***** status is FTP_PASV\n"); + } else if (aStatus == 2152398872) { + // in ftpCore.h 24 + dump("***** status is FTP_PWD\n"); + } else if (aStatus == 2152857601) { + dump("***** status is UNRECOGNIZED_PATH\n"); + } else if (aStatus == 2152857602) { + dump("***** status is UNRESOLABLE SYMLINK\n"); + } else if (aStatus == 2152857604) { + dump("***** status is UNKNOWN_TYPE\n"); + } else if (aStatus == 2152857605) { + dump("***** status is DESTINATION_NOT_DIR\n"); + } else if (aStatus == 2152857606) { + dump("***** status is TARGET_DOES_NOT_EXIST\n"); + } else if (aStatus == 2152857608) { + dump("***** status is ALREADY_EXISTS\n"); + } else if (aStatus == 2152857609) { + dump("***** status is INVALID_PATH\n"); + } else if (aStatus == 2152857610) { + dump("***** status is DISK_FULL\n"); + } else if (aStatus == 2152857612) { + dump("***** status is NOT_DIRECTORY\n"); + } else if (aStatus == 2152857613) { + dump("***** status is IS_DIRECTORY\n"); + } else if (aStatus == 2152857614) { + dump("***** status is IS_LOCKED\n"); + } else if (aStatus == 2152857615) { + dump("***** status is TOO_BIG\n"); + } else if (aStatus == 2152857616) { + dump("***** status is NO_DEVICE_SPACE\n"); + } else if (aStatus == 2152857617) { + dump("***** status is NAME_TOO_LONG\n"); + } else if (aStatus == 2152857618) { + // 80520012 + dump("***** status is FILE_NOT_FOUND\n"); + } else if (aStatus == 2152857619) { + dump("***** status is READ_ONLY\n"); + } else if (aStatus == 2152857620) { + dump("***** status is DIR_NOT_EMPTY\n"); + } else if (aStatus == 2152857621) { + dump("***** status is ACCESS_DENIED\n"); + } else if (aStatus == 2152398878) { + dump("***** status is ? (No connection or time out?)\n"); + } else { + dump("***** status is " + aStatus + "\n"); + } +} +/* eslint-enable complexity */ + +// Update any data that the user supplied in a prompt dialog +function UpdateUsernamePasswordFromPrompt( + publishData, + username, + password, + savePassword +) { + if (!publishData) { + return; + } + + // Set flag to save publish data after publishing if it changed in dialog + // and the "SavePassword" checkbox was checked + // or we already had site data for this site + // (Thus we don't automatically create a site until user brings up Publish As dialog) + publishData.savePublishData = + (gPublishData.username != username || gPublishData.password != password) && + (savePassword || !publishData.notInSiteData); + + publishData.username = username; + publishData.password = password; + publishData.savePassword = savePassword; +} + +const kSupportedTextMimeTypes = [ + "text/plain", + "text/css", + "text/rdf", + "text/xsl", + "text/javascript", // obsolete type + "text/ecmascript", // obsolete type + "application/javascript", + "application/ecmascript", + "application/x-javascript", // obsolete type + "text/xul", // obsolete type + "application/vnd.mozilla.xul+xml", // obsolete type + "application/xhtml+xml", +]; + +function IsSupportedTextMimeType(aMimeType) { + for (var i = 0; i < kSupportedTextMimeTypes.length; i++) { + if (kSupportedTextMimeTypes[i] == aMimeType) { + return true; + } + } + return false; +} + +/* eslint-disable complexity */ +// throws an error or returns true if user attempted save; false if user canceled save +async function SaveDocument(aSaveAs, aSaveCopy, aMimeType) { + var editor = GetCurrentEditor(); + if (!aMimeType || !editor) { + throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED); + } + + var editorDoc = editor.document; + if (!editorDoc) { + throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED); + } + + // if we don't have the right editor type bail (we handle text and html) + var editorType = GetCurrentEditorType(); + if (!["text", "html", "htmlmail", "textmail"].includes(editorType)) { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } + + var saveAsTextFile = IsSupportedTextMimeType(aMimeType); + + // check if the file is to be saved is a format we don't understand; if so, bail + if ( + aMimeType != kHTMLMimeType && + aMimeType != kXHTMLMimeType && + !saveAsTextFile + ) { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } + + if (saveAsTextFile) { + aMimeType = "text/plain"; + } + + var urlstring = GetDocumentUrl(); + var mustShowFileDialog = + aSaveAs || IsUrlAboutBlank(urlstring) || urlstring == ""; + + // If editing a remote URL, force SaveAs dialog + if (!mustShowFileDialog && GetScheme(urlstring) != "file") { + mustShowFileDialog = true; + } + + var doUpdateURI = false; + var tempLocalFile = null; + + if (mustShowFileDialog) { + try { + // Prompt for title if we are saving to HTML + if (!saveAsTextFile && editorType == "html") { + var userContinuing = PromptAndSetTitleIfNone(); // not cancel + if (!userContinuing) { + return false; + } + } + + var dialogResult = await PromptForSaveLocation( + saveAsTextFile, + editorType, + aMimeType, + urlstring + ); + if (!dialogResult) { + return false; + } + + // What is this unused 'replacing' var supposed to be doing? + /* eslint-disable-next-line no-unused-vars */ + var replacing = + dialogResult.filepickerClick == nsIFilePicker.returnReplace; + + urlstring = dialogResult.resultingURIString; + tempLocalFile = dialogResult.resultingLocalFile; + + // update the new URL for the webshell unless we are saving a copy + if (!aSaveCopy) { + doUpdateURI = true; + } + } catch (e) { + Cu.reportError(e); + return false; + } + } // mustShowFileDialog + + var success = true; + try { + // if somehow we didn't get a local file but we did get a uri, + // attempt to create the localfile if it's a "file" url + var docURI; + if (!tempLocalFile) { + docURI = Services.io.newURI(urlstring, editor.documentCharacterSet); + + if (docURI.schemeIs("file")) { + var fileHandler = GetFileProtocolHandler(); + tempLocalFile = fileHandler + .getFileFromURLSpec(urlstring) + .QueryInterface(Ci.nsIFile); + } + } + + // this is the location where the related files will go + var relatedFilesDir = null; + + // Only change links or move files if pref is set + // and we are saving to a new location + if (Services.prefs.getBoolPref("editor.save_associated_files") && aSaveAs) { + try { + if (tempLocalFile) { + // if we are saving to the same parent directory, don't set relatedFilesDir + // grab old location, chop off file + // grab new location, chop off file, compare + var oldLocation = GetDocumentUrl(); + var oldLocationLastSlash = oldLocation.lastIndexOf("/"); + if (oldLocationLastSlash != -1) { + oldLocation = oldLocation.slice(0, oldLocationLastSlash); + } + + var relatedFilesDirStr = urlstring; + var newLocationLastSlash = relatedFilesDirStr.lastIndexOf("/"); + if (newLocationLastSlash != -1) { + relatedFilesDirStr = relatedFilesDirStr.slice( + 0, + newLocationLastSlash + ); + } + if ( + oldLocation == relatedFilesDirStr || + IsUrlAboutBlank(oldLocation) + ) { + relatedFilesDir = null; + } else { + relatedFilesDir = tempLocalFile.parent; + } + } else { + var lastSlash = urlstring.lastIndexOf("/"); + if (lastSlash != -1) { + var relatedFilesDirString = urlstring.slice(0, lastSlash + 1); // include last slash + relatedFilesDir = Services.io.newURI( + relatedFilesDirString, + editor.documentCharacterSet + ); + } + } + } catch (e) { + relatedFilesDir = null; + } + } + + let destinationLocation = tempLocalFile ? tempLocalFile : docURI; + + success = OutputFileWithPersistAPI( + editorDoc, + destinationLocation, + relatedFilesDir, + aMimeType + ); + } catch (e) { + success = false; + } + + if (success) { + try { + if (doUpdateURI) { + // If a local file, we must create a new uri from nsIFile + if (tempLocalFile) { + docURI = GetFileProtocolHandler().newFileURI(tempLocalFile); + } + + // We need to set new document uri before notifying listeners + SetDocumentURI(docURI); + } + + // Update window title to show possibly different filename + // This also covers problem that after undoing a title change, + // window title loses the extra [filename] part that this adds + UpdateWindowTitle(); + + if (!aSaveCopy) { + editor.resetModificationCount(); + } + // this should cause notification to listeners that document has changed + + // Set UI based on whether we're editing a remote or local url + SetSaveAndPublishUI(urlstring); + } catch (e) {} + } else { + Services.prompt.alert( + window, + GetString("SaveDocument"), + GetString("SaveFileFailed") + ); + } + return success; +} +/* eslint-enable complexity */ + +function SetDocumentURI(uri) { + try { + // XXX WE'LL NEED TO GET "CURRENT" CONTENT FRAME ONCE MULTIPLE EDITORS ARE ALLOWED + GetCurrentEditorElement().docShell.setCurrentURI(uri); + } catch (e) { + dump("SetDocumentURI:\n" + e + "\n"); + } +} + +// ------------------------------- Publishing +var gPublishData; +var gProgressDialog; +var gCommandAfterPublishing = null; +var gRestoreDocumentSource; + +function Publish(publishData) { + if (!publishData) { + return false; + } + + // Set data in global for username password requests + // and to do "post saving" actions after monitoring nsIWebProgressListener messages + // and we are sure file transfer was successful + gPublishData = publishData; + + gPublishData.docURI = CreateURIFromPublishData(publishData, true); + if (!gPublishData.docURI) { + Services.prompt.alert( + window, + GetString("Publish"), + GetString("PublishFailed") + ); + return false; + } + + if (gPublishData.publishOtherFiles) { + gPublishData.otherFilesURI = CreateURIFromPublishData(publishData, false); + } else { + gPublishData.otherFilesURI = null; + } + + if (gShowDebugOutputStateChange) { + dump( + "\n *** publishData: PublishUrl=" + + publishData.publishUrl + + ", BrowseUrl=" + + publishData.browseUrl + + ", Username=" + + publishData.username + + ", Dir=" + + publishData.docDir + + ", Filename=" + + publishData.filename + + "\n" + ); + dump( + " * gPublishData.docURI.spec w/o pass=" + + StripPassword(gPublishData.docURI.spec) + + ", PublishOtherFiles=" + + gPublishData.publishOtherFiles + + "\n" + ); + } + + // XXX Missing username will make FTP fail + // and it won't call us for prompt dialog (bug 132320) + // (It does prompt if just password is missing) + // So we should do the prompt ourselves before trying to publish + if (GetScheme(publishData.publishUrl) == "ftp" && !publishData.username) { + var message = GetString("PromptFTPUsernamePassword").replace( + /%host%/, + GetHost(publishData.publishUrl) + ); + var savePWobj = { value: publishData.savePassword }; + var userObj = { value: publishData.username }; + var pwObj = { value: publishData.password }; + if ( + !PromptUsernameAndPassword( + GetString("Prompt"), + message, + savePWobj, + userObj, + pwObj + ) + ) { + // User canceled out of dialog. + return false; + } + + // Reset data in URI objects + gPublishData.docURI.username = publishData.username; + gPublishData.docURI.password = publishData.password; + + if (gPublishData.otherFilesURI) { + gPublishData.otherFilesURI.username = publishData.username; + gPublishData.otherFilesURI.password = publishData.password; + } + } + + try { + // We launch dialog as a dependent + // Don't allow editing document! + SetDocumentEditable(false); + + // Start progress monitoring + gProgressDialog = window.openDialog( + "chrome://editor/content/EditorPublishProgress.xhtml", + "_blank", + "chrome,dependent,titlebar", + gPublishData, + gPersistObj + ); + } catch (e) {} + + // Network transfer is often too quick for the progress dialog to be initialized + // and we can completely miss messages for quickly-terminated bad URLs, + // so we can't call OutputFileWithPersistAPI right away. + // StartPublishing() is called at the end of the dialog's onload method + return true; +} + +function StartPublishing() { + var editor = GetCurrentEditor(); + if (editor && gPublishData && gPublishData.docURI && gProgressDialog) { + gRestoreDocumentSource = null; + + // Save backup document since nsIWebBrowserPersist changes image src urls + // but we only need to do this if publishing images and other related files + if (gPublishData.otherFilesURI) { + try { + gRestoreDocumentSource = editor.outputToString( + editor.contentsMIMEType, + kOutputEncodeW3CEntities + ); + } catch (e) {} + } + + OutputFileWithPersistAPI( + editor.document, + gPublishData.docURI, + gPublishData.otherFilesURI, + editor.contentsMIMEType + ); + return gPersistObj; + } + return null; +} + +function CancelPublishing() { + try { + gPersistObj.cancelSave(); + gProgressDialog.SetProgressStatusCancel(); + } catch (e) {} + + // If canceling publishing do not do any commands after this + gCommandAfterPublishing = null; + + if (gProgressDialog) { + // Close Progress dialog + // (this will call FinishPublishing()) + gProgressDialog.CloseDialog(); + } else { + FinishPublishing(); + } +} + +function FinishPublishing() { + SetDocumentEditable(true); + gProgressDialog = null; + gPublishData = null; + gRestoreDocumentSource = null; + + if (gCommandAfterPublishing) { + // Be sure to null out the global now in case of trouble when executing command + var command = gCommandAfterPublishing; + gCommandAfterPublishing = null; + goDoCommand(command); + } +} + +// Create a nsIURI object filled in with all required publishing info +function CreateURIFromPublishData(publishData, doDocUri) { + if (!publishData || !publishData.publishUrl) { + return null; + } + + var URI; + try { + var spec = publishData.publishUrl; + if (doDocUri) { + spec += FormatDirForPublishing(publishData.docDir) + publishData.filename; + } else { + spec += FormatDirForPublishing(publishData.otherDir); + } + + URI = Services.io.newURI(spec, GetCurrentEditor().documentCharacterSet); + + if (publishData.username) { + URI.username = publishData.username; + } + if (publishData.password) { + URI.password = publishData.password; + } + } catch (e) {} + + return URI; +} + +// Resolve the correct "http:" document URL when publishing via ftp +function GetDocUrlFromPublishData(publishData) { + if (!publishData || !publishData.filename || !publishData.publishUrl) { + return ""; + } + + // If user was previously editing an "ftp" url, then keep that as the new scheme + var url; + + // Always use the "HTTP" address if available + // XXX Should we do some more validation here for bad urls??? + // Let's at least check for a scheme! + if (!GetScheme(publishData.browseUrl)) { + url = publishData.publishUrl; + } else { + url = publishData.browseUrl; + } + + url += FormatDirForPublishing(publishData.docDir) + publishData.filename; + + if (GetScheme(url) == "ftp") { + url = InsertUsernameIntoUrl(url, publishData.username); + } + + return url; +} + +function SetSaveAndPublishUI(urlstring) { + // Be sure enabled state of toolbar buttons are correct + goUpdateCommand("cmd_save"); + goUpdateCommand("cmd_publish"); +} + +function SetDocumentEditable(isDocEditable) { + var editor = GetCurrentEditor(); + if (editor && editor.document) { + try { + var flags = editor.flags; + editor.flags = isDocEditable + ? (flags &= ~Ci.nsIEditor.eEditorReadonlyMask) + : flags | Ci.nsIEditor.eEditorReadonlyMask; + } catch (e) {} + + // update all commands + window.updateCommands("create"); + } +} + +// ****** end of save / publish **********// + +var nsPublishSettingsCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + if (GetCurrentEditor()) { + // Launch Publish Settings dialog + + window.ok = window.openDialog( + "chrome://editor/content/EditorPublishSettings.xhtml", + "_blank", + "chrome,close,titlebar,modal", + "" + ); + return window.ok; + } + return false; + }, +}; + +var nsRevertCommand = { + isCommandEnabled(aCommand, dummy) { + return ( + IsDocumentEditable() && + IsDocumentModified() && + !IsUrlAboutBlank(GetDocumentUrl()) + ); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + // Confirm with the user to abandon current changes + // Put the page title in the message string + let title = GetDocumentTitle(); + let msg = GetString("AbandonChanges").replace(/%title%/, title); + + let result = Services.prompt.confirmEx( + window, + GetString("RevertCaption"), + msg, + Services.prompt.BUTTON_TITLE_REVERT * Services.prompt.BUTTON_POS_0 + + Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1, + null, + null, + null, + null, + { value: 0 } + ); + + // Reload page if first button (Revert) was pressed + if (result == 0) { + CancelHTMLSource(); + EditorLoadUrl(GetDocumentUrl()); + } + }, +}; + +var nsCloseCommand = { + isCommandEnabled(aCommand, dummy) { + return GetCurrentEditor() != null; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + CloseWindow(); + }, +}; + +async function CloseWindow() { + // Check to make sure document is saved. "true" means allow "Don't Save" button, + // so user can choose to close without saving + if (await CheckAndSaveDocument("cmd_close", true)) { + if (window.InsertCharWindow) { + SwitchInsertCharToAnotherEditorOrClose(); + } + + try { + var basewin = window + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIBaseWindow); + basewin.destroy(); + } catch (e) {} + } +} + +var nsOpenRemoteCommand = { + isCommandEnabled(aCommand, dummy) { + // We can always do this. + return true; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + var params = { action: "2", url: "" }; + openDialog( + "chrome://communicator/content/openLocation.xhtml", + "_blank", + "chrome,modal,titlebar", + params + ); + var win = getTopWin(); + switch (params.action) { + case "0": // current window + win.focus(); + win.loadURI(params.url, null, null, true); + break; + case "1": // new window + openDialog( + getBrowserURL(), + "_blank", + "all,dialog=no", + params.url, + null, + null, + null, + true + ); + break; + case "2": // edit + editPage(params.url); + break; + case "3": // new tab + win.focus(); + var browser = win.getBrowser(); + browser.selectedTab = browser.addTab(params.url, { + allowThirdPartyFixup: true, + }); + break; + case "4": // private + openNewPrivateWith(params.url); + break; + default: + break; + } + }, +}; + +var nsPreviewCommand = { + isCommandEnabled(aCommand, dummy) { + return ( + IsDocumentEditable() && + IsHTMLEditor() && + (DocumentHasBeenSaved() || IsDocumentModified()) + ); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + async doCommand(aCommand) { + // Don't continue if user canceled during prompt for saving + // DocumentHasBeenSaved will test if we have a URL and suppress "Don't Save" button if not + if (!(await CheckAndSaveDocument("cmd_preview", DocumentHasBeenSaved()))) { + return; + } + + // Check if we saved again just in case? + if (DocumentHasBeenSaved()) { + let browser; + try { + // Find a browser with this URL + let enumerator = Services.wm.getEnumerator("navigator:browser"); + + var documentURI = GetDocumentUrl(); + while (enumerator.hasMoreElements()) { + browser = enumerator.getNext(); + if ( + browser && + !browser.closed && + documentURI == browser.getBrowser().currentURI.spec + ) { + break; + } + + browser = null; + } + } catch (ex) {} + + // If none found, open a new browser + if (!browser) { + browser = window.openDialog( + getBrowserURL(), + "_blank", + "chrome,all,dialog=no", + documentURI + ); + } else { + try { + browser.BrowserReloadSkipCache(); + browser.focus(); + } catch (ex) {} + } + } + }, +}; + +var nsSendPageCommand = { + isCommandEnabled(aCommand, dummy) { + return ( + IsDocumentEditable() && (DocumentHasBeenSaved() || IsDocumentModified()) + ); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + async doCommand(aCommand) { + // Don't continue if user canceled during prompt for saving + // DocumentHasBeenSaved will test if we have a URL and suppress "Don't Save" button if not + if ( + !(await CheckAndSaveDocument("cmd_editSendPage", DocumentHasBeenSaved())) + ) { + return; + } + + // Check if we saved again just in case? + if (DocumentHasBeenSaved()) { + // Launch Messenger Composer window with current page as contents + try { + openComposeWindow(GetDocumentUrl(), GetDocumentTitle()); + } catch (ex) { + dump("Cannot Send Page: " + ex + "\n"); + } + } + }, +}; + +var nsPrintCommand = { + isCommandEnabled(aCommand, dummy) { + return true; // we can always do this + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + // In editor.js + SetEditMode(gPreviousNonSourceDisplayMode); + try { + let browser = GetCurrentEditorElement(); + PrintUtils.printWindow(browser.outerWindowID, browser); + } catch (e) {} + }, +}; + +var nsPrintPreviewCommand = { + isCommandEnabled(aCommand, dummy) { + // We can always do this. + return true; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + // In editor.js + SetEditMode(gPreviousNonSourceDisplayMode); + try { + PrintUtils.printPreview("editor", PrintPreviewListener); + } catch (e) {} + }, +}; + +var nsPrintSetupCommand = { + isCommandEnabled(aCommand, dummy) { + return true; // we can always do this + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + // In editor.js + SetEditMode(gPreviousNonSourceDisplayMode); + PrintUtils.showPageSetup(); + }, +}; + +var nsFindReplaceCommand = { + isCommandEnabled(aCommand, editorElement) { + return editorElement.getEditor(editorElement.contentWindow) != null; + }, + + getCommandStateParams(aCommand, aParams, editorElement) {}, + doCommandParams(aCommand, aParams, editorElement) {}, + + doCommand(aCommand, editorElement) { + window.openDialog( + "chrome://editor/content/EdReplace.xhtml", + "_blank", + "chrome,modal,titlebar", + editorElement + ); + }, +}; + +var nsFindCommand = { + isCommandEnabled(aCommand, editorElement) { + return editorElement.getEditor(editorElement.contentWindow) != null; + }, + + getCommandStateParams(aCommand, aParams, editorElement) {}, + doCommandParams(aCommand, aParams, editorElement) {}, + + doCommand(aCommand, editorElement) { + document.getElementById("FindToolbar").onFindCommand(); + }, +}; + +var nsFindAgainCommand = { + isCommandEnabled(aCommand, editorElement) { + // we can only do this if the search pattern is non-empty. Not sure how + // to get that from here + return editorElement.getEditor(editorElement.contentWindow) != null; + }, + + getCommandStateParams(aCommand, aParams, editorElement) {}, + doCommandParams(aCommand, aParams, editorElement) {}, + + doCommand(aCommand, editorElement) { + let findPrev = aCommand == "cmd_findPrev"; + document.getElementById("FindToolbar").onFindAgainCommand(findPrev); + }, +}; + +var nsRewrapCommand = { + isCommandEnabled(aCommand, dummy) { + return ( + IsDocumentEditable() && + !IsInHTMLSourceMode() && + GetCurrentEditor() instanceof Ci.nsIEditorMailSupport + ); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + // We only want to respect new lines when using the web composer. + let respectNewLines = IsWebComposer(); + GetCurrentEditor() + .QueryInterface(Ci.nsIEditorMailSupport) + .rewrap(respectNewLines); + }, +}; + +var nsSpellingCommand = { + isCommandEnabled(aCommand, dummy) { + return ( + IsDocumentEditable() && !IsInHTMLSourceMode() && IsSpellCheckerInstalled() + ); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.cancelSendMessage = false; + try { + var skipBlockQuotes = + window.document.documentElement.getAttribute("windowtype") == + "msgcompose"; + window.openDialog( + "chrome://editor/content/EdSpellCheck.xhtml", + "_blank", + "dialog,close,titlebar,modal,resizable", + false, + skipBlockQuotes, + true + ); + } catch (ex) {} + }, +}; + +// Validate using http://validator.w3.org/file-upload.html +var URL2Validate; +var nsValidateCommand = { + isCommandEnabled(aCommand, dummy) { + return GetCurrentEditor() != null; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + async doCommand(aCommand) { + // If the document hasn't been modified, + // then just validate the current url. + if (IsDocumentModified() || IsHTMLSourceChanged()) { + if (!(await CheckAndSaveDocument("cmd_validate", false))) { + return; + } + + // Check if we saved again just in case? + if (!DocumentHasBeenSaved()) { + // user hit cancel? + return; + } + } + + URL2Validate = GetDocumentUrl(); + // See if it's a file: + var ifile; + try { + var fileHandler = GetFileProtocolHandler(); + ifile = fileHandler.getFileFromURLSpec(URL2Validate); + // nsIFile throws an exception if it's not a file url + } catch (e) { + ifile = null; + } + if (ifile) { + URL2Validate = ifile.path; + var vwin = window.open( + "http://validator.w3.org/file-upload.html", + "EditorValidate" + ); + // Window loads asynchronously, so pass control to the load listener: + vwin.addEventListener("load", this.validateFilePageLoaded); + } else { + window.open( + `http://validator.w3.org/check?uri=${URL2Validate}&doctype=Inline`, + "EditorValidate" + ); + // This does the validation, no need to wait for page loaded. + } + }, + validateFilePageLoaded(event) { + event.target.forms[0].uploaded_file.value = URL2Validate; + }, +}; + +var nsFormCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.openDialog( + "chrome://editor/content/EdFormProps.xhtml", + "_blank", + "chrome,close,titlebar,modal" + ); + }, +}; + +var nsInputTagCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.openDialog( + "chrome://editor/content/EdInputProps.xhtml", + "_blank", + "chrome,close,titlebar,modal" + ); + }, +}; + +var nsInputImageCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.openDialog( + "chrome://editor/content/EdInputImage.xhtml", + "_blank", + "chrome,close,titlebar,modal" + ); + }, +}; + +var nsTextAreaCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.openDialog( + "chrome://editor/content/EdTextAreaProps.xhtml", + "_blank", + "chrome,close,titlebar,modal" + ); + }, +}; + +var nsSelectCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.openDialog( + "chrome://editor/content/EdSelectProps.xhtml", + "_blank", + "chrome,close,titlebar,modal" + ); + }, +}; + +var nsButtonCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.openDialog( + "chrome://editor/content/EdButtonProps.xhtml", + "_blank", + "chrome,close,titlebar,modal" + ); + }, +}; + +var nsLabelCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + var tagName = "label"; + try { + var editor = GetCurrentEditor(); + // Find selected label or if start/end of selection is in label + var labelElement = editor.getSelectedElement(tagName); + if (!labelElement) { + labelElement = editor.getElementOrParentByTagName( + tagName, + editor.selection.anchorNode + ); + } + if (!labelElement) { + labelElement = editor.getElementOrParentByTagName( + tagName, + editor.selection.focusNode + ); + } + if (labelElement) { + // We only open the dialog for an existing label + window.openDialog( + "chrome://editor/content/EdLabelProps.xhtml", + "_blank", + "chrome,close,titlebar,modal", + labelElement + ); + } else { + EditorSetTextProperty(tagName, "", ""); + } + } catch (e) {} + }, +}; + +var nsFieldSetCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.openDialog( + "chrome://editor/content/EdFieldSetProps.xhtml", + "_blank", + "chrome,close,titlebar,modal" + ); + }, +}; + +var nsImageCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.openDialog( + "chrome://editor/content/EdImageProps.xhtml", + "_blank", + "chrome,close,titlebar,modal" + ); + }, +}; + +var nsHLineCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + // Inserting an HLine is different in that we don't use properties dialog + // unless we are editing an existing line's attributes + // We get the last-used attributes from the prefs and insert immediately + + var tagName = "hr"; + var editor = GetCurrentEditor(); + + var hLine; + try { + hLine = editor.getSelectedElement(tagName); + } catch (e) { + return; + } + + if (hLine) { + // We only open the dialog for an existing HRule + window.openDialog( + "chrome://editor/content/EdHLineProps.xhtml", + "_blank", + "chrome,close,titlebar,modal" + ); + } else { + try { + hLine = editor.createElementWithDefaults(tagName); + + // We change the default attributes to those saved in the user prefs + let align = Services.prefs.getIntPref("editor.hrule.align"); + if (align == 0) { + editor.setAttributeOrEquivalent(hLine, "align", "left", true); + } else if (align == 2) { + editor.setAttributeOrEquivalent(hLine, "align", "right", true); + } + + // Note: Default is center (don't write attribute) + + let width = Services.prefs.getIntPref("editor.hrule.width"); + if (Services.prefs.getBoolPref("editor.hrule.width_percent")) { + width = width + "%"; + } + + editor.setAttributeOrEquivalent(hLine, "width", width, true); + + let height = Services.prefs.getIntPref("editor.hrule.height"); + editor.setAttributeOrEquivalent(hLine, "size", String(height), true); + + if (Services.prefs.getBoolPref("editor.hrule.shading")) { + hLine.removeAttribute("noshade"); + } else { + hLine.setAttribute("noshade", "noshade"); + } + + editor.insertElementAtSelection(hLine, true); + } catch (e) {} + } + }, +}; + +var nsLinkCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + // If selected element is an image, launch that dialog instead + // since last tab panel handles link around an image + var element = GetObjectForProperties(); + if (element && element.nodeName.toLowerCase() == "img") { + window.openDialog( + "chrome://editor/content/EdImageProps.xhtml", + "_blank", + "chrome,close,titlebar,modal", + null, + true + ); + } else { + window.openDialog( + "chrome://editor/content/EdLinkProps.xhtml", + "_blank", + "chrome,close,titlebar,modal" + ); + } + }, +}; + +var nsAnchorCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.openDialog( + "chrome://editor/content/EdNamedAnchorProps.xhtml", + "_blank", + "chrome,close,titlebar,modal", + "" + ); + }, +}; + +var nsInsertHTMLWithDialogCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.openDialog( + "chrome://editor/content/EdInsSrc.xhtml", + "_blank", + "chrome,close,titlebar,modal,resizable", + "" + ); + }, +}; + +var nsInsertMathWithDialogCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.openDialog( + "chrome://editor/content/EdInsertMath.xhtml", + "_blank", + "chrome,close,titlebar,modal,resizable", + "" + ); + }, +}; + +var nsInsertCharsCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + EditorFindOrCreateInsertCharWindow(); + }, +}; + +var nsInsertBreakCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentEditor().insertHTML("<br>"); + } catch (e) {} + }, +}; + +var nsInsertBreakAllCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentEditor().insertHTML("<br clear='all'>"); + } catch (e) {} + }, +}; + +var nsGridCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.openDialog( + "chrome://editor/content/EdSnapToGrid.xhtml", + "_blank", + "chrome,close,titlebar,modal" + ); + }, +}; + +var nsListPropertiesCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.openDialog( + "chrome://editor/content/EdListProps.xhtml", + "_blank", + "chrome,close,titlebar,modal" + ); + }, +}; + +var nsPagePropertiesCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + var oldTitle = GetDocumentTitle(); + window.openDialog( + "chrome://editor/content/EdPageProps.xhtml", + "_blank", + "chrome,close,titlebar,modal", + "" + ); + + // Update main window title and + // recent menu data in prefs if doc title changed + if (GetDocumentTitle() != oldTitle) { + UpdateWindowTitle(); + } + }, +}; + +var nsObjectPropertiesCommand = { + isCommandEnabled(aCommand, dummy) { + var isEnabled = false; + if (IsDocumentEditable() && IsEditingRenderedHTML()) { + isEnabled = + GetObjectForProperties() != null || + GetCurrentEditor().getSelectedElement("href") != null; + } + return isEnabled; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + // Launch Object properties for appropriate selected element + var element = GetObjectForProperties(); + if (element) { + var name = element.nodeName.toLowerCase(); + switch (name) { + case "img": + goDoCommand("cmd_image"); + break; + case "hr": + goDoCommand("cmd_hline"); + break; + case "form": + goDoCommand("cmd_form"); + break; + case "input": + var type = element.getAttribute("type"); + if (type && type.toLowerCase() == "image") { + goDoCommand("cmd_inputimage"); + } else { + goDoCommand("cmd_inputtag"); + } + break; + case "textarea": + goDoCommand("cmd_textarea"); + break; + case "select": + goDoCommand("cmd_select"); + break; + case "button": + goDoCommand("cmd_button"); + break; + case "label": + goDoCommand("cmd_label"); + break; + case "fieldset": + goDoCommand("cmd_fieldset"); + break; + case "table": + EditorInsertOrEditTable(false); + break; + case "td": + case "th": + EditorTableCellProperties(); + break; + case "ol": + case "ul": + case "dl": + case "li": + goDoCommand("cmd_listProperties"); + break; + case "a": + if (element.name) { + goDoCommand("cmd_anchor"); + } else if (element.href) { + goDoCommand("cmd_link"); + } + break; + case "math": + goDoCommand("cmd_insertMathWithDialog"); + break; + default: + doAdvancedProperties(element); + break; + } + } else { + // We get a partially-selected link if asked for specifically + try { + element = GetCurrentEditor().getSelectedElement("href"); + } catch (e) {} + if (element) { + goDoCommand("cmd_link"); + } + } + }, +}; + +var nsSetSmiley = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) { + var smileyCode = aParams.getStringValue("state_attribute"); + + var strSml; + switch (smileyCode) { + case ":-)": + strSml = "s1"; + break; + case ":-(": + strSml = "s2"; + break; + case ";-)": + strSml = "s3"; + break; + case ":-P": + case ":-p": + case ":-b": + strSml = "s4"; + break; + case ":-D": + strSml = "s5"; + break; + case ":-[": + strSml = "s6"; + break; + case ":-/": + case ":/": + case ":-\\": + case ":\\": + strSml = "s7"; + break; + case "=-O": + case "=-o": + strSml = "s8"; + break; + case ":-*": + strSml = "s9"; + break; + case ">:o": + case ">:-o": + strSml = "s10"; + break; + case "8-)": + strSml = "s11"; + break; + case ":-$": + strSml = "s12"; + break; + case ":-!": + strSml = "s13"; + break; + case "O:-)": + case "o:-)": + strSml = "s14"; + break; + case ":'(": + strSml = "s15"; + break; + case ":-X": + case ":-x": + strSml = "s16"; + break; + default: + strSml = ""; + break; + } + + try { + var editor = GetCurrentEditor(); + var extElement = editor.createElementWithDefaults("span"); + extElement.setAttribute("class", "moz-smiley-" + strSml); + + var intElement = editor.createElementWithDefaults("span"); + if (!intElement) { + return; + } + + var txtElement = editor.document.createTextNode(smileyCode); + if (!txtElement) { + return; + } + + intElement.appendChild(txtElement); + extElement.appendChild(intElement); + + editor.insertElementAtSelection(extElement, true); + window.content.focus(); + } catch (e) { + dump("Exception occurred in smiley InsertElementAtSelection\n"); + } + }, + // This is now deprecated in favor of "doCommandParams" + doCommand(aCommand) {}, +}; + +function doAdvancedProperties(element) { + if (element) { + window.openDialog( + "chrome://editor/content/EdAdvancedEdit.xhtml", + "_blank", + "chrome,close,titlebar,modal,resizable=yes", + "", + element + ); + } +} + +var nsAdvancedPropertiesCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + // Launch AdvancedEdit dialog for the selected element + try { + var element = GetCurrentEditor().getSelectedElement(""); + doAdvancedProperties(element); + } catch (e) {} + }, +}; + +var nsColorPropertiesCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.openDialog( + "chrome://editor/content/EdColorProps.xhtml", + "_blank", + "chrome,close,titlebar,modal", + "" + ); + UpdateDefaultColors(); + }, +}; + +var nsIncreaseFontCommand = { + isCommandEnabled(aCommand, dummy) { + if (!(IsDocumentEditable() && IsEditingRenderedHTML())) { + return false; + } + var setIndex = getFontSizeIndex(); + return setIndex >= 0 && setIndex < 5; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + var setIndex = getFontSizeIndex(); + if (setIndex < 0 || setIndex >= 5) { + return; + } + var sizes = ["x-small", "small", "medium", "large", "x-large", "xx-large"]; + EditorSetFontSize(sizes[setIndex + 1]); + }, +}; + +var nsDecreaseFontCommand = { + isCommandEnabled(aCommand, dummy) { + if (!(IsDocumentEditable() && IsEditingRenderedHTML())) { + return false; + } + var setIndex = getFontSizeIndex(); + return setIndex > 0; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + var setIndex = getFontSizeIndex(); + if (setIndex <= 0) { + return; + } + var sizes = ["x-small", "small", "medium", "large", "x-large", "xx-large"]; + EditorSetFontSize(sizes[setIndex - 1]); + }, +}; + +var nsRemoveNamedAnchorsCommand = { + isCommandEnabled(aCommand, dummy) { + // We could see if there's any link in selection, but it doesn't seem worth the work! + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + EditorRemoveTextProperty("name", ""); + window.content.focus(); + }, +}; + +var nsEditLinkCommand = { + isCommandEnabled(aCommand, dummy) { + // Not really used -- this command is only in context menu, and we do enabling there + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + var element = GetCurrentEditor().getSelectedElement("href"); + if (element) { + editPage(element.href); + } + } catch (e) {} + window.content.focus(); + }, +}; + +var nsNormalModeCommand = { + isCommandEnabled(aCommand, dummy) { + return IsHTMLEditor() && IsDocumentEditable(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + SetEditMode(kDisplayModeNormal); + }, +}; + +var nsAllTagsModeCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsHTMLEditor(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + SetEditMode(kDisplayModeAllTags); + }, +}; + +var nsHTMLSourceModeCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsHTMLEditor(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + SetEditMode(kDisplayModeSource); + }, +}; + +var nsPreviewModeCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsHTMLEditor(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + SetEditMode(kDisplayModePreview); + }, +}; + +var nsInsertOrEditTableCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + if (IsInTableCell()) { + EditorTableCellProperties(); + } else { + EditorInsertOrEditTable(true); + } + }, +}; + +var nsEditTableCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTable(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + EditorInsertOrEditTable(false); + }, +}; + +var nsSelectTableCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTable(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentTableEditor().selectTable(); + } catch (e) {} + window.content.focus(); + }, +}; + +var nsSelectTableRowCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTableCell(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentTableEditor().selectTableRow(); + } catch (e) {} + window.content.focus(); + }, +}; + +var nsSelectTableColumnCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTableCell(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentTableEditor().selectTableColumn(); + } catch (e) {} + window.content.focus(); + }, +}; + +var nsSelectTableCellCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTableCell(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentTableEditor().selectTableCell(); + } catch (e) {} + window.content.focus(); + }, +}; + +var nsSelectAllTableCellsCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTable(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentTableEditor().selectAllTableCells(); + } catch (e) {} + window.content.focus(); + }, +}; + +var nsInsertTableCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + EditorInsertTable(); + }, +}; + +var nsInsertTableRowAboveCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTableCell(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentTableEditor().insertTableRow(1, false); + } catch (e) {} + window.content.focus(); + }, +}; + +var nsInsertTableRowBelowCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTableCell(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentTableEditor().insertTableRow(1, true); + } catch (e) {} + window.content.focus(); + }, +}; + +var nsInsertTableColumnBeforeCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTableCell(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentTableEditor().insertTableColumn(1, false); + } catch (e) {} + window.content.focus(); + }, +}; + +var nsInsertTableColumnAfterCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTableCell(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentTableEditor().insertTableColumn(1, true); + } catch (e) {} + window.content.focus(); + }, +}; + +var nsInsertTableCellBeforeCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTableCell(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentTableEditor().insertTableCell(1, false); + } catch (e) {} + window.content.focus(); + }, +}; + +var nsInsertTableCellAfterCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTableCell(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentTableEditor().insertTableCell(1, true); + } catch (e) {} + window.content.focus(); + }, +}; + +var nsDeleteTableCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTable(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentTableEditor().deleteTable(); + } catch (e) {} + window.content.focus(); + }, +}; + +var nsDeleteTableRowCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTableCell(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + var rows = GetNumberOfContiguousSelectedRows(); + // Delete at least one row + if (rows == 0) { + rows = 1; + } + + try { + var editor = GetCurrentTableEditor(); + editor.beginTransaction(); + + // Loop to delete all blocks of contiguous, selected rows + while (rows) { + editor.deleteTableRow(rows); + rows = GetNumberOfContiguousSelectedRows(); + } + } finally { + editor.endTransaction(); + } + window.content.focus(); + }, +}; + +var nsDeleteTableColumnCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTableCell(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + var columns = GetNumberOfContiguousSelectedColumns(); + // Delete at least one column + if (columns == 0) { + columns = 1; + } + + try { + var editor = GetCurrentTableEditor(); + editor.beginTransaction(); + + // Loop to delete all blocks of contiguous, selected columns + while (columns) { + editor.deleteTableColumn(columns); + columns = GetNumberOfContiguousSelectedColumns(); + } + } finally { + editor.endTransaction(); + } + window.content.focus(); + }, +}; + +var nsDeleteTableCellCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTableCell(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentTableEditor().deleteTableCell(1); + } catch (e) {} + window.content.focus(); + }, +}; + +var nsDeleteTableCellContentsCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTableCell(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentTableEditor().deleteTableCellContents(); + } catch (e) {} + window.content.focus(); + }, +}; + +var nsNormalizeTableCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTable(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + // Use nullptr to let editor find table enclosing current selection + try { + GetCurrentTableEditor().normalizeTable(null); + } catch (e) {} + window.content.focus(); + }, +}; + +var nsJoinTableCellsCommand = { + isCommandEnabled(aCommand, dummy) { + if (IsDocumentEditable() && IsEditingRenderedHTML()) { + try { + var editor = GetCurrentTableEditor(); + var tagNameObj = { value: "" }; + var countObj = { value: 0 }; + var cell = editor.getSelectedOrParentTableElement(tagNameObj, countObj); + + // We need a cell and either > 1 selected cell or a cell to the right + // (this cell may originate in a row spanned from above current row) + // Note that editor returns "td" for "th" also. + // (this is a pain! Editor and gecko use lowercase tagNames, JS uses uppercase!) + if (cell && tagNameObj.value == "td") { + // Selected cells + if (countObj.value > 1) { + return true; + } + + var colSpan = cell.getAttribute("colspan"); + + // getAttribute returns string, we need number + // no attribute means colspan = 1 + if (!colSpan) { + colSpan = Number(1); + } else { + colSpan = Number(colSpan); + } + + var rowObj = { value: 0 }; + var colObj = { value: 0 }; + editor.getCellIndexes(cell, rowObj, colObj); + + // Test if cell exists to the right of current cell + // (cells with 0 span should never have cells to the right + // if there is, user can select the 2 cells to join them) + return ( + colSpan && + editor.getCellAt(null, rowObj.value, colObj.value + colSpan) + ); + } + } catch (e) {} + } + return false; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + // Param: Don't merge non-contiguous cells + try { + GetCurrentTableEditor().joinTableCells(false); + } catch (e) {} + window.content.focus(); + }, +}; + +var nsSplitTableCellCommand = { + isCommandEnabled(aCommand, dummy) { + if (IsDocumentEditable() && IsEditingRenderedHTML()) { + var tagNameObj = { value: "" }; + var countObj = { value: 0 }; + var cell; + try { + cell = GetCurrentTableEditor().getSelectedOrParentTableElement( + tagNameObj, + countObj + ); + } catch (e) {} + + // We need a cell parent and there's just 1 selected cell + // or selection is entirely inside 1 cell + if ( + cell && + tagNameObj.value == "td" && + countObj.value <= 1 && + IsSelectionInOneCell() + ) { + var colSpan = cell.getAttribute("colspan"); + var rowSpan = cell.getAttribute("rowspan"); + if (!colSpan) { + colSpan = 1; + } + if (!rowSpan) { + rowSpan = 1; + } + return colSpan > 1 || rowSpan > 1 || colSpan == 0 || rowSpan == 0; + } + } + return false; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentTableEditor().splitTableCell(); + } catch (e) {} + window.content.focus(); + }, +}; + +var nsTableOrCellColorCommand = { + isCommandEnabled(aCommand, dummy) { + return IsInTable(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + EditorSelectColor("TableOrCell"); + }, +}; + +var nsPreferencesCommand = { + isCommandEnabled(aCommand, dummy) { + return true; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + goPreferences("composer_pane"); + }, +}; + +var nsFinishHTMLSource = { + isCommandEnabled(aCommand, dummy) { + return true; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + // In editor.js + SetEditMode(gPreviousNonSourceDisplayMode); + }, +}; + +var nsCancelHTMLSource = { + isCommandEnabled(aCommand, dummy) { + return true; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + // In editor.js + CancelHTMLSource(); + }, +}; + +var nsConvertToTable = { + isCommandEnabled(aCommand, dummy) { + if (IsDocumentEditable() && IsEditingRenderedHTML()) { + var selection; + try { + selection = GetCurrentEditor().selection; + } catch (e) {} + + if (selection && !selection.isCollapsed) { + // Don't allow if table or cell is the selection + var element; + try { + element = GetCurrentEditor().getSelectedElement(""); + } catch (e) {} + if (element) { + var name = element.nodeName.toLowerCase(); + if ( + name == "td" || + name == "th" || + name == "caption" || + name == "table" + ) { + return false; + } + } + + // Selection start and end must be in the same cell + // in same cell or both are NOT in a cell + if ( + GetParentTableCell(selection.focusNode) != + GetParentTableCell(selection.anchorNode) + ) { + return false; + } + + return true; + } + } + return false; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + if (this.isCommandEnabled()) { + window.openDialog( + "chrome://editor/content/EdConvertToTable.xhtml", + "_blank", + "chrome,close,titlebar,modal" + ); + } + }, +}; diff --git a/comm/suite/editor/base/content/EditorAllTags.css b/comm/suite/editor/base/content/EditorAllTags.css new file mode 100644 index 0000000000..656dffcdee --- /dev/null +++ b/comm/suite/editor/base/content/EditorAllTags.css @@ -0,0 +1,802 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Styles to alter look of things in the Editor content window + * for the "All Tags Edit Mode" Every HTML tag shows up as an icon. +*/ + +/* For "userdefined" or "unknown" tags + (Note that "_" must be escaped) +*/ + +*:not(a):not(abbr):not(acronym):not(address):not(applet):not(area):not(b):not(base):not(basefont):not(bdo):not(bgsound):not(big):not(blink):not(blockquote):not(body):not(br):not(button):not(canvas):not(caption):not(center):not(cite):not(code):not(col):not(colgroup):not(dd):not(del):not(dfn):not(dir):not(div):not(dl):not(dt):not(em):not(embed):not(fieldset):not(font):not(form):not(frame):not(frameset):not(h1):not(h2):not(h3):not(h4):not(h5):not(h6):not(head):not(hr):not(html):not(i):not(iframe):not(image):not(img):not(input):not(ins):not(isindex):not(kbd):not(keygen):not(label):not(legend):not(li):not(link):not(listing):not(map):not(marquee):not(menu):not(meta):not(multicol):not(nobr):not(noembed):not(noframes):not(noscript):not(object):not(ol):not(optgroup):not(option):not(p):not(param):not(plaintext):not(pre):not(q):not(s):not(samp):not(script):not(select):not(server):not(small):not(sound):not(spacer):not(span):not(strike):not(strong):not(style):not(sub):not(sup):not(table):not(tbody):not(td):not(textarea):not(tfoot):not(th):not(thead):not(title):not(tr):not(tt):not(u):not(ul):not(var):not(wbr):not(xmp) { + display: inline; + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 16px; + background-image: url(chrome://editor/content/images/tag-userdefined.png); + background-repeat: no-repeat; + background-position: top left; +} + +a:not([\_moz_anonclass]) { + min-height: 16px; margin-left: 2px; margin-top: 2px; + padding-left: 20px; + background-image: url(chrome://editor/content/images/tag-a.png); + background-repeat: no-repeat; + background-position: top left; +} + +abbr { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 35px; + background-image: url(chrome://editor/content/images/tag-abr.png); + background-repeat: no-repeat; + background-position: top left; +} + + +acronym { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 57px; + background-image: url(chrome://editor/content/images/tag-acr.png); + background-repeat: no-repeat; + background-position: top left; +} + +address { + min-height: 44px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-adr.png); + background-repeat: no-repeat; + background-position: top left; +} + +applet { + min-height: 35px; margin-top: 2px; + padding-left: 47px; + background-image: url(chrome://editor/content/images/tag-app.png); + background-repeat: no-repeat; + background-position: top left; +} + +area { + min-height: 35px; margin-top: 2px; + padding-left: 39px; + background-image: url(chrome://editor/content/images/tag-ara.png); + background-repeat: no-repeat; + background-position: top left; +} + +b { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 20px; + background-image: url(chrome://editor/content/images/tag-b.png); + background-repeat: no-repeat; + background-position: top left; +} + +basefont { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 57px; + background-image: url(chrome://editor/content/images/tag-bsf.png); + background-repeat: no-repeat; + background-position: top left; +} + +bdo { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 31px; + background-image: url(chrome://editor/content/images/tag-bdo.png); + background-repeat: no-repeat; + background-position: top left; +} + +big { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 31px; + background-image: url(chrome://editor/content/images/tag-big.png); + background-repeat: no-repeat; + background-position: top left; +} + +blockquote { + min-height: 44px; margin-left: 2px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-blq.png); + background-repeat: no-repeat; + background-position: top left; +} + +body { + min-height: 36px; margin-left: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-body.png); + background-repeat: no-repeat; + background-position: top left; +} + +br { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 23px; + background-image: url(chrome://editor/content/images/tag-br.png); + background-repeat: no-repeat; + background-position: top left; +} + +button { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 57px; + background-image: url(chrome://editor/content/images/tag-btn.png); + background-repeat: no-repeat; + background-position: top left; +} + +caption { + min-height: 35px; margin-top: 2px; + padding-left: 55px; + background-image: url(chrome://editor/content/images/tag-cpt.png); + background-repeat: no-repeat; + background-position: top left; +} + +center { + min-height: 44px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-ctr.png); + background-repeat: no-repeat; + background-position: top left; +} + +cite { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 39px; + background-image: url(chrome://editor/content/images/tag-cit.png); + background-repeat: no-repeat; + background-position: top left; +} + +code { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 39px; + background-image: url(chrome://editor/content/images/tag-cod.png); + background-repeat: no-repeat; + background-position: top left; +} + +col { + min-height: 35px; margin-left: 2px; + padding-left: 31px; + background-image: url(chrome://editor/content/images/tag-col.png); + background-repeat: no-repeat; + background-position: top left; +} + +colgroup { + min-height: 35px; margin-left: 2px; + padding-left: 51px; + background-image: url(chrome://editor/content/images/tag-clg.png); + background-repeat: no-repeat; + background-position: top left; +} + +dd { + min-height: 35px; margin-top: 2px; + padding-left: 23px; + background-image: url(chrome://editor/content/images/tag-dd.png); + background-repeat: no-repeat; + background-position: top left; +} + +del { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 31px; + background-image: url(chrome://editor/content/images/tag-del.png); + background-repeat: no-repeat; + background-position: top left; +} + +dfn { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 31px; + background-image: url(chrome://editor/content/images/tag-dfn.png); + background-repeat: no-repeat; + background-position: top left; +} + +dir { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 31px; + background-image: url(chrome://editor/content/images/tag-dir.png); + background-repeat: no-repeat; + background-position: top left; +} + +div { + min-height: 24px; margin-top: 2px; + /* TEMPORARY TO COMPENSATE FOR BUG */ + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-div.png); + background-repeat: no-repeat; + background-position: top left; +} + +input div { + min-height: 0px; margin-left: 0px; margin-top: 0px; + padding-left: 0px; + background-image: none; +} + +dl { + min-height: 20px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-dl.png); + background-repeat: no-repeat; + background-position: top left; +} + +dt { + min-height: 35px; margin-top: 2px; + padding-left: 23px; + background-image: url(chrome://editor/content/images/tag-dt.png); + background-repeat: no-repeat; + background-position: top left; +} + +em { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 23px; + background-image: url(chrome://editor/content/images/tag-em.png); + background-repeat: no-repeat; + background-position: top left; +} + +fieldset { + min-height: 44px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-fld.png); + background-repeat: no-repeat; + background-position: top left; +} + +font { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 39px; + background-image: url(chrome://editor/content/images/tag-fnt.png); + background-repeat: no-repeat; + background-position: top left; +} + +form { + min-height: 36px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-for.png); + background-repeat: no-repeat; + background-position: top left; +} + +frame { + min-height: 40px; margin-left: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-frm.png); + background-repeat: no-repeat; + background-position: top left; +} + +frameset { + min-height: 44px; margin-left: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-fst.png); + background-repeat: no-repeat; + background-position: top left; +} + +h1 { + min-height: 20px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-h1.png); + background-repeat: no-repeat; + background-position: top left; +} + +h2 { + min-height: 20px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-h2.png); + background-repeat: no-repeat; + background-position: top left; +} + +h3 { + min-height: 20px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-h3.png); + background-repeat: no-repeat; + background-position: top left; +} + +h4 { + min-height: 20px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-h4.png); + background-repeat: no-repeat; + background-position: top left; +} + +h5 { + min-height: 20px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-h5.png); + background-repeat: no-repeat; + background-position: top left; +} + +h6 { + min-height: 20px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-h6.png); + background-repeat: no-repeat; + background-position: top left; +} + +hr { + min-height: 20px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-hr.png); + background-repeat: no-repeat; + background-position: top left; +} + +i { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 20px; + background-image: url(chrome://editor/content/images/tag-i.png); + background-repeat: no-repeat; + background-position: top left; +} + +iframe { + min-height: 35px; margin-left: 2px; + padding-left: 47px; + background-image: url(chrome://editor/content/images/tag-ifr.png); + background-repeat: no-repeat; + background-position: top left; +} + +img:not([\_moz_anonclass]) { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 31px; + background-image: url(chrome://editor/content/images/tag-img.png); + background-repeat: no-repeat; + background-position: top left; +} + +input { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 39px; + background-image: url(chrome://editor/content/images/tag-inp.png); + background-repeat: no-repeat; + background-position: top left; +} + +ins { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 31px; + background-image: url(chrome://editor/content/images/tag-ins.png); + background-repeat: no-repeat; + background-position: top left; +} + +isindex { + min-height: 40px; margin-left: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-isx.png); + background-repeat: no-repeat; + background-position: top left; +} + +kbd { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 31px; + background-image: url(chrome://editor/content/images/tag-kbd.png); + background-repeat: no-repeat; + background-position: top left; +} + +label { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 39px; + background-image: url(chrome://editor/content/images/tag-lbl.png); + background-repeat: no-repeat; + background-position: top left; +} + +legend { + min-height: 35px; margin-top: 2px; + padding-left: 49px; + background-image: url(chrome://editor/content/images/tag-lgn.png); + background-repeat: no-repeat; + background-position: top left; +} + +li { + min-height: 35px; margin-top: 2px; + padding-left: 23px; + background-image: url(chrome://editor/content/images/tag-li.png); + background-repeat: no-repeat; + background-position: top left; +} + +listing { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 57px; + background-image: url(chrome://editor/content/images/tag-lst.png); + background-repeat: no-repeat; + background-position: top left; +} + +map { + min-height: 35px; margin-left: 2px; + padding-left: 31px; + background-image: url(chrome://editor/content/images/tag-map.png); + background-repeat: no-repeat; + background-position: top left; +} + +menu { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 39px; + background-image: url(chrome://editor/content/images/tag-men.png); + background-repeat: no-repeat; + background-position: top left; +} + +nobr { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 39px; + background-image: url(chrome://editor/content/images/tag-nbr.png); + background-repeat: no-repeat; + background-position: top left; +} + +noframes { + min-height: 44px; margin-left: 2px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-nfr.png); + background-repeat: no-repeat; + background-position: top left; +} + +noscript { + min-height: 44px; margin-left: 2px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-nsc.png); + background-repeat: no-repeat; + background-position: top left; +} + +object { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 49px; + background-image: url(chrome://editor/content/images/tag-obj.png); + background-repeat: no-repeat; + background-position: top left; +} + +ol { + min-height: 38px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-ol.png); + background-repeat: no-repeat; + background-position: top left; +} + +optgroup { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 51px; + background-image: url(chrome://editor/content/images/tag-opg.png); + background-repeat: no-repeat; + background-position: top left; +} + +option { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 47px; + background-image: url(chrome://editor/content/images/tag-opt.png); + background-repeat: no-repeat; + background-position: top left; +} + +p { + min-height: 38px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-p.png); + background-repeat: no-repeat; + background-position: top left; +} + +param { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 43px; + background-image: url(chrome://editor/content/images/tag-prm.png); + background-repeat: no-repeat; + background-position: top left; +} + +plaintext { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 57px; + background-image: url(chrome://editor/content/images/tag-pln.png); + background-repeat: no-repeat; + background-position: top left; +} + +pre { + min-height: 24px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-pre.png); + background-repeat: no-repeat; + background-position: top left; +} + +q { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 20px; + background-image: url(chrome://editor/content/images/tag-q.png); + background-repeat: no-repeat; + background-position: top left; +} + +s { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 20px; + background-image: url(chrome://editor/content/images/tag-s.png); + background-repeat: no-repeat; + background-position: top left; +} + +samp { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 39px; + background-image: url(chrome://editor/content/images/tag-smp.png); + background-repeat: no-repeat; + background-position: top left; +} + +script { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 45px; + background-image: url(chrome://editor/content/images/tag-scr.png); + background-repeat: no-repeat; + background-position: top left; +} + +select { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 47px; + background-image: url(chrome://editor/content/images/tag-slc.png); + background-repeat: no-repeat; + background-position: top left; +} + +small { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 41px; + background-image: url(chrome://editor/content/images/tag-sml.png); + background-repeat: no-repeat; + background-position: top left; +} + +span:not([\_moz_anonclass]) { + min-height: 35px; margin-left: 2px; margin-top: 2px; + /* TEMPORARY TO COMPENSATE FOR BUG */ + padding-left: 39px; + background-image: url(chrome://editor/content/images/tag-spn.png); + background-repeat: no-repeat; + background-position: top left; +} + +strike { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 45px; + background-image: url(chrome://editor/content/images/tag-stk.png); + background-repeat: no-repeat; + background-position: top left; +} + +strong { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 51px; + background-image: url(chrome://editor/content/images/tag-stn.png); + background-repeat: no-repeat; + background-position: top left; +} + +sub { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 31px; + background-image: url(chrome://editor/content/images/tag-sub.png); + background-repeat: no-repeat; + background-position: top left; +} + +sup { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 31px; + background-image: url(chrome://editor/content/images/tag-sup.png); + background-repeat: no-repeat; + background-position: top left; +} + +/* The background image technique is not working for + some table elements. Trying the "before" strategy +*/ + +table { + min-height: 40px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-tbl.png); + background-repeat: no-repeat; + background-position: top left; +} + +tbody { + min-height: 42px; margin-left: 2px; margin-top: 1px; + padding-left: 17px; + content: url(chrome://editor/content/images/tag-tbd.png); + background-repeat: no-repeat; + background-position: top left; +} + +td { + min-height: 22px; margin-left: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-td.png); + + background-repeat: no-repeat; + background-position: top left; +} + +textarea { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 59px; + background-image: url(chrome://editor/content/images/tag-txt.png); + background-repeat: no-repeat; + background-position: top left; +} + +tfoot { + min-height: 42px; margin-left: 2px; margin-top: 1px; + padding-left: 17px; + content: url(chrome://editor/content/images/tag-tft.png); + background-repeat: no-repeat; + background-position: top left; +} + +th { + min-height: 22px; margin-left: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-th.png); + background-repeat: no-repeat; + background-position: top left; +} + +thead { + min-height: 42px; margin-left: 2px; margin-top: 1px; + padding-left: 17px; + content: url(chrome://editor/content/images/tag-thd.png); + background-repeat: no-repeat; + background-position: top left; +} + +tr { + min-height: 22px; margin-left: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-tr.png); + background-repeat: no-repeat; + background-position: top left; +} + +tt { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 23px; + background-image: url(chrome://editor/content/images/tag-tt.png); + background-repeat: no-repeat; + background-position: top left; +} + +u { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 23px; + background-image: url(chrome://editor/content/images/tag-u.png); + background-repeat: no-repeat; + background-position: top left; +} + +ul { + min-height: 20px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-ul.png); + background-repeat: no-repeat; + background-position: top left; +} + +var { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 31px; + background-image: url(chrome://editor/content/images/tag-var.png); + background-repeat: no-repeat; + background-position: top left; +} + +xmp { + min-height: 35px; margin-left: 2px; margin-top: 2px; + padding-left: 31px; + background-image: url(chrome://editor/content/images/tag-xmp.png); + background-repeat: no-repeat; + background-position: top left; +} + + +/* These are tags that we DON'T want to show icons for. + We have images for them in case we want to utilize them + for some other purpose than the "All Tags" editor mode + +html { + min-height: 36px; margin-left: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-html.png); + background-repeat: no-repeat; + background-position: top left; +} + +head { + min-height: 36px; margin-left: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-hed.png); + background-repeat: no-repeat; + background-position: top left; +} + +These are tags that are ONLY allowed as children of HEAD: + +title { + min-height: 40px; margin-left: 2px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-ttl.png); + background-repeat: no-repeat; + background-position: top left; +} + +base { + min-height: 36px; margin-left: 2px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-bas.png); + background-repeat: no-repeat; + background-position: top left; +} + +style { + min-height: 40px; margin-left: 2px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-stl.png); + background-repeat: no-repeat; + background-position: top left; +} + +meta { + min-height: 36px; margin-left: 2px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-met.png); + background-repeat: no-repeat; + background-position: top left; +} + +link { + min-height: 30px; margin-left: 2px; margin-top: 2px; + padding-left: 17px; + background-image: url(chrome://editor/content/images/tag-lnk.png); + background-repeat: no-repeat; + background-position: top left; +} +*/ diff --git a/comm/suite/editor/base/content/EditorContent.css b/comm/suite/editor/base/content/EditorContent.css new file mode 100644 index 0000000000..fee8af21de --- /dev/null +++ b/comm/suite/editor/base/content/EditorContent.css @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Styles to alter look of things in the Editor content window + * for the "Normal Edit Mode" These settings will be removed + * when we display in completely WYSIWYG "Edit Preview" mode + * Anything that should never change, like cursors, should be + * place in EditorOverride.css, instead of here. +*/ + +@import url(chrome://communicator/skin/smileys.css); + +a[name] { + min-height: 17px; margin-left: 2px; margin-top: 2px; + padding-left: 20px; + background-image: url(chrome://editor/content/images/tag-anchor.png); + background-repeat: no-repeat; + background-position: top left; +} + +/* Force border display for empty cells + and tables with 0 border +*/ +table { + empty-cells: show; +} + +/* give a red dotted border to tables and cells with no border + otherwise they are invisible +*/ +table[empty-cells], + table[border="0"], + /* next two selectors on line below for the case where tbody is omitted */ + table[border="0"] > tr > td, table[border="0"] > tr > th, + table[border="0"] > thead > tr > td, table[border="0"] > tbody > tr > td, table[border="0"] > tfoot > tr > td, + table[border="0"] > thead > tr > th, table[border="0"] > tbody > tr > th, table[border="0"] > tfoot > tr > th, + table:not([border]), + /* next two selectors on line below for the case where tbody is omitted */ + table:not([border]) > tr > td, table:not([border]) > tr > th, + table:not([border]) > thead > tr > td, table:not([border]) > tbody > tr > td, table:not([border]) > tfoot > tr > td, + table:not([border]) > thead > tr > th, table:not([border]) > tbody > tr > th, table:not([border]) > tfoot > tr > th +{ + border: 1px dotted red; +} + +/* give a green dashed border to forms otherwise they are invisible +*/ +form +{ + border: 2px dashed green; +} +/* give a green dotted border to labels otherwise they are invisible +*/ +label +{ + border: 1px dotted green; +} + +img { + -moz-force-broken-image-icon: 1; +} diff --git a/comm/suite/editor/base/content/EditorContextMenu.js b/comm/suite/editor/base/content/EditorContextMenu.js new file mode 100644 index 0000000000..93dedfc0b1 --- /dev/null +++ b/comm/suite/editor/base/content/EditorContextMenu.js @@ -0,0 +1,122 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyGetter(this, "InlineSpellCheckerUI", function() { + let tmp = {}; + ChromeUtils.import("resource://gre/modules/InlineSpellChecker.jsm", tmp); + return new tmp.InlineSpellChecker(); +}); + +// Overrides the main contentAreaContext onpopupshowing so needs to do +// everything that does plus call Composer specific code. +function editorContextPopupShowing(aNode) +{ + gContextMenu = new nsContextMenu(aNode); + if (gContextMenu.shouldDisplay) + { + var showExtra = top.document.commandDispatcher.focusedWindow == content; + gContextMenu.initEditorItems(showExtra); + return true; + } + return false; +} + +// Extends the main nsContextMenu for Composer. +nsContextMenu.prototype.initEditorItems = function (aShow) +{ + var isInLink = false; + var objectName; + var inSourceMode = IsInHTMLSourceMode(); + var showSpell = !inSourceMode && !IsInPreviewMode() && + InlineSpellCheckerUI.canSpellCheck; + this.showItem("spell-check-enabled", showSpell); + this.showItem("spell-separator", showSpell); + + aShow = aShow && !inSourceMode; + this.hideDisabledItem("menu_pasteNoFormatting_cm", aShow); + + // Only do this stuff when not in source mode or sidebar. + if (aShow) + { + // Setup object property command element. + objectName = InitObjectPropertiesMenuitem(); + isInLink = objectName == "href"; + + InitRemoveStylesMenuitems("removeStylesMenuitem_cm", + "removeLinksMenuitem_cm", + "removeNamedAnchorsMenuitem_cm"); + + // Set appropriate text for join cells command. + InitJoinCellMenuitem("joinTableCells_cm"); + + // Update enable states for all table commands. + goUpdateTableMenuItems(document.getElementById("composerTableMenuItems")); + + this.hideDisabledItem("context-undo", true); + this.hideDisabledItem("context-redo", true); + this.hideDisabledItem("context-cut", true); + this.hideDisabledItem("context-copy", true); + this.hideDisabledItem("context-paste", true); + this.hideDisabledItem("context-delete", true); + + this.showItem("context-sep-undo", + this.shouldShowSeparator("context-sep-undo")); + this.showItem("context-sep-paste", + this.shouldShowSeparator("context-sep-paste")); + } + + this.hideDisabledItem("objectProperties_cm", aShow); + + // Show "Create Link" if not in a link and not in source mode or sidebar. + this.showItem("createLink_cm", aShow && !isInLink); + + // Show "Edit link in new Composer" if in a link and + // not in source mode or sidebar. + this.showItem("editLink_cm", aShow && isInLink); + + this.hideDisabledItem("removeStylesMenuitem_cm", aShow); + this.hideDisabledItem("removeLinksMenuitem_cm", aShow); + this.hideDisabledItem("removeNamedAnchorsMenuitem_cm", aShow); + + this.hideDisabledItem("joinTableCells_cm", aShow); + this.hideDisabledItem("splitTableCell_cm", aShow); + this.hideDisabledItem("tableOrCellColor_cm", aShow); + + var inCell = aShow && IsInTableCell(); + // Remove table submenus if not in table. + this.showItem("tableInsertMenu_cm", inCell); + this.showItem("tableSelectMenu_cm", inCell); + this.showItem("tableDeleteMenu_cm", inCell); + + this.showItem("context-sep-selectall", aShow); + this.showItem("context-sep-properites", aShow && !!objectName); + this.showItem("frame-sep", aShow && IsInTable()); +}; + +nsContextMenu.prototype.hideDisabledItem = function(aId, aShow) +{ + this.showItem(aId, aShow && IsItemOrCommandEnabled(aId)); +}; + +function IsItemOrCommandEnabled(aId) +{ + var item = document.getElementById(aId); + if (!item) + return false; + + var command = item.getAttribute("command"); + if (command) { + // If possible, query the command controller directly + var controller = document.commandDispatcher + .getControllerForCommand(command); + if (controller) + return controller.isCommandEnabled(command); + } + + // Fall back on the inefficient observed disabled attribute + return item.getAttribute("disabled") != "true"; +} diff --git a/comm/suite/editor/base/content/EditorContextMenuOverlay.xhtml b/comm/suite/editor/base/content/EditorContextMenuOverlay.xhtml new file mode 100644 index 0000000000..36c23f0c5e --- /dev/null +++ b/comm/suite/editor/base/content/EditorContextMenuOverlay.xhtml @@ -0,0 +1,171 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<!DOCTYPE overlay SYSTEM "chrome://editor/locale/editorOverlay.dtd"> + +<overlay id="ComposerContextMenuOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"> + +<script src="chrome://editor/content/EditorContextMenu.js"/> +<script src="chrome://editor/content/StructBarContextMenu.js"/> + + <menupopup id="contentAreaContextMenu" + onpopupshowing="return event.target != this || + editorContextPopupShowing(this);"> + <menuitem id="menu_pasteNoFormatting_cm" + insertafter="context-paste" + command="cmd_pasteNoFormatting"/> + + <!-- label and accesskey set at runtime from strings --> + <menuitem id="removeStylesMenuitem_cm" + insertafter="context-sep-selectall" + command="cmd_removeStyles"/> + <menuitem id="createLink_cm" + insertafter="removeStylesMenuitem_cm" + label="&createLinkCmd.label;" + accesskey="&createLinkCmd.accesskey;" + command="cmd_link"/> + <!-- label and accesskey set at runtime from strings --> + <menuitem id="removeLinksMenuitem_cm" + insertafter="createLink_cm" + command="cmd_removeLinks"/> + <menuitem id="removeNamedAnchorsMenuitem_cm" + insertafter="removeLinksMenuitem_cm" + label="&formatRemoveNamedAnchors.label;" + accesskey="&formatRemoveNamedAnchors.accesskey;" + command="cmd_removeNamedAnchors"/> + + <!-- label and accesskey are set in InitObjectProperties --> + <menuitem id="objectProperties_cm" + insertafter="context-sep-properties" + command="cmd_objectProperties"/> + <menuitem id="editLink_cm" + insertafter="objectProperties_cm" + label="&editLinkCmd.label;" + accesskey="&editLinkCmd.accesskey;" + command="cmd_editLink"/> + + <!-- Can't get submenus to load from a shared overlay --> + <menu id="tableInsertMenu_cm" + insertafter="frame-sep" + label="&tableInsertMenu2.label;" + accesskey="&tableInsertMenu2.accesskey;"> + <menupopup> + <menuitem label="&insertTableCmd.label;" + accesskey="&insertTableCmd.accesskey;" + command="cmd_InsertTable"/> + <menuseparator/> + <menuitem label="&tableRowAbove.label;" + accesskey="&tableRowAbove.accesskey;" + command="cmd_InsertRowAbove"/> + <menuitem label="&tableRowBelow.label;" + accesskey="&tableRowBelow.accesskey;" + command="cmd_InsertRowBelow"/> + <menuseparator/> + <menuitem label="&tableColumnBefore.label;" + accesskey="&tableColumnBefore.accesskey;" + command="cmd_InsertColumnBefore"/> + <menuitem label="&tableColumnAfter.label;" + accesskey="&tableColumnAfter.accesskey;" + command="cmd_InsertColumnAfter"/> + <menuseparator/> + <menuitem label="&tableCellBefore.label;" + accesskey="&tableCellBefore.accesskey;" + command="cmd_InsertCellBefore"/> + <menuitem label="&tableCellAfter.label;" + accesskey="&tableCellAfter.accesskey;" + command="cmd_InsertCellAfter"/> + </menupopup> + </menu> + <menu id="tableSelectMenu_cm" + insertafter="tableInsertMenu_cm" + label="&tableSelectMenu2.label;" + accesskey="&tableSelectMenu2.accesskey;"> + <menupopup> + <menuitem id="menu_SelectTable_cm" + label="&tableTable.label;" + accesskey="&tableTable.accesskey;" + command="cmd_SelectTable"/> + <menuitem id="menu_SelectRow_cm" + label="&tableRow.label;" + accesskey="&tableRow.accesskey;" + command="cmd_SelectRow"/> + <menuitem id="menu_SelectColumn_cm" + label="&tableColumn.label;" + accesskey="&tableColumn.accesskey;" + command="cmd_SelectColumn"/> + <menuitem id="menu_SelectCell_cm" + label="&tableCell.label;" + accesskey="&tableCell.accesskey;" + command="cmd_SelectCell"/> + <menuitem id="menu_SelectAllCells_cm" + label="&tableAllCells.label;" + accesskey="&tableAllCells.accesskey;" + command="cmd_SelectAllCells"/> + </menupopup> + </menu> + <menu id="tableDeleteMenu_cm" + insertafter="tableSelectMenu_cm" + label="&tableDeleteMenu2.label;" + accesskey="&tableDeleteMenu2.accesskey;"> + <menupopup> + <menuitem id="menu_DeleteTable_cm" + label="&tableTable.label;" + accesskey="&tableTable.accesskey;" + command="cmd_DeleteTable"/> + <menuitem id="menu_DeleteRow_cm" + label="&tableRows.label;" + accesskey="&tableRow.accesskey;" + command="cmd_DeleteRow"/> + <menuitem id="menu_DeleteColumn_cm" + label="&tableColumns.label;" + accesskey="&tableColumn.accesskey;" + command="cmd_DeleteColumn"/> + <menuitem id="menu_DeleteCell_cm" + label="&tableCells.label;" + accesskey="&tableCell.accesskey;" + command="cmd_DeleteCell"/> + <menuitem id="menu_DeleteCellContents_cm" + label="&tableCellContents.label;" + accesskey="&tableCellContents.accesskey;" + command="cmd_DeleteCellContents"/> + </menupopup> + </menu> + <!-- menu label is set in InitTableMenu --> + <menuitem id="joinTableCells_cm" + insertafter="tableDeleteMenu_cm" + label="&tableJoinCells.label;" + accesskey="&tableJoinCells.accesskey;" + command="cmd_JoinTableCells"/> + <menuitem id="splitTableCell_cm" + insertafter="joinTableCells_cm" + label="&tableSplitCell.label;" + accesskey="&tableSplitCell.accesskey;" + command="cmd_SplitTableCell"/> + <menuitem id="tableOrCellColor_cm" + insertafter="splitTableCell_cm" + label="&tableOrCellColor.label;" + accesskey="&tableOrCellColor.accesskey;" + command="cmd_TableOrCellColor"/> + </menupopup> + + <menupopup id="structToolbarContext"> + <menuitem id="structSelect" label="&structSelect.label;" + accesskey="&structSelect.accesskey;" + oncommand="StructSelectTag()"/> + <menuseparator/> + <menuitem id="structRemoveTag" label="&structRemoveTag.label;" + accesskey="&structRemoveTag.accesskey;" + oncommand="StructRemoveTag()"/> + <menuitem id="structChangeTag" label="&structChangeTag.label;" + accesskey="&structChangeTag.accesskey;" + oncommand="StructChangeTag()"/> + <menuseparator/> + <menuitem id="advancedPropsTag" label="&advancedPropertiesCmd.label;" + accesskey="&advancedPropertiesCmd.accesskey;" + oncommand="OpenAdvancedProperties()"/> + </menupopup> + +</overlay> diff --git a/comm/suite/editor/base/content/StructBarContextMenu.js b/comm/suite/editor/base/content/StructBarContextMenu.js new file mode 100644 index 0000000000..19f5498355 --- /dev/null +++ b/comm/suite/editor/base/content/StructBarContextMenu.js @@ -0,0 +1,179 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var gContextMenuNode; +var gContextMenuFiringDocumentElement; + +function InitStructBarContextMenu(button, docElement) +{ + gContextMenuFiringDocumentElement = docElement; + gContextMenuNode = button; + + var tag = docElement.nodeName.toLowerCase(); + + var structRemoveTag = document.getElementById("structRemoveTag"); + var enableRemove; + + switch (tag) { + case "body": + case "tbody": + case "thead": + case "tfoot": + case "col": + case "colgroup": + case "tr": + case "th": + case "td": + case "caption": + enableRemove = false; + break; + default: + enableRemove = true; + break; + } + SetElementEnabled(structRemoveTag, enableRemove); + + var structChangeTag = document.getElementById("structChangeTag"); + SetElementEnabled(structChangeTag, (tag != "body")); +} + +function TableCellFilter(node) +{ + switch (node.nodeName.toLowerCase()) + { + case "td": + case "th": + case "caption": + return NodeFilter.FILTER_ACCEPT; + break; + default: + return NodeFilter.FILTER_SKIP; + break; + } + return NodeFilter.FILTER_SKIP; +} + +function StructRemoveTag() +{ + var editor = GetCurrentEditor(); + if (!editor) return; + + var element = gContextMenuFiringDocumentElement; + var offset = 0; + var childNodes = element.parentNode.childNodes; + + while (childNodes[offset] != element) { + ++offset; + } + + editor.beginTransaction(); + + try { + + var tag = element.nodeName.toLowerCase(); + if (tag != "table") { + MoveChildNodesAfterElement(editor, element, element, offset); + } + else { + + var nodeIterator = document.createTreeWalker(element, + NodeFilter.SHOW_ELEMENT, + TableCellFilter, + true); + var node = nodeIterator.lastChild(); + while (node) { + MoveChildNodesAfterElement(editor, node, element, offset); + node = nodeIterator.previousSibling(); + } + + } + editor.deleteNode(element); + } + catch (e) {}; + + editor.endTransaction(); +} + +function MoveChildNodesAfterElement(editor, element, targetElement, targetOffset) +{ + var childNodes = element.childNodes; + var childNodesLength = childNodes.length; + var i; + for (i = childNodesLength - 1; i >= 0; i--) { + var clone = childNodes.item(i).cloneNode(true); + editor.insertNode(clone, targetElement.parentNode, targetOffset + 1); + } +} + +function StructChangeTag() +{ + var textbox = document.createXULElement("textbox"); + textbox.setAttribute("value", gContextMenuNode.getAttribute("value")); + textbox.setAttribute("width", gContextMenuNode.getBoundingClientRect().width); + textbox.className = "struct-textbox"; + + gContextMenuNode.parentNode.replaceChild(textbox, gContextMenuNode); + + textbox.addEventListener("keypress", OnKeyPress); + textbox.addEventListener("blur", ResetStructToolbar, true); + + textbox.select(); +} + +function StructSelectTag() +{ + SelectFocusNodeAncestor(gContextMenuFiringDocumentElement); +} + +function OpenAdvancedProperties() +{ + doAdvancedProperties(gContextMenuFiringDocumentElement); +} + +function OnKeyPress(event) +{ + var editor = GetCurrentEditor(); + + var keyCode = event.keyCode; + if (keyCode == 13) { + var newTag = event.target.value; + + var element = gContextMenuFiringDocumentElement; + + var offset = 0; + var childNodes = element.parentNode.childNodes; + while (childNodes.item(offset) != element) { + offset++; + } + + editor.beginTransaction(); + + try { + var newElt = editor.document.createXULElement(newTag); + if (newElt) { + childNodes = element.childNodes; + var childNodesLength = childNodes.length; + var i; + for (i = 0; i < childNodesLength; i++) { + var clone = childNodes.item(i).cloneNode(true); + newElt.appendChild(clone); + } + editor.insertNode(newElt, element.parentNode, offset+1); + editor.deleteNode(element); + editor.selectElement(newElt); + + window.content.focus(); + } + } + catch (e) {} + + editor.endTransaction(); + + } + else if (keyCode == 27) { + // if the user hits Escape, we discard the changes + window.content.focus(); + } +} diff --git a/comm/suite/editor/base/content/composerOverlay.xhtml b/comm/suite/editor/base/content/composerOverlay.xhtml new file mode 100644 index 0000000000..1d36db50b1 --- /dev/null +++ b/comm/suite/editor/base/content/composerOverlay.xhtml @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<!DOCTYPE overlay [ +<!ENTITY % editorDTD SYSTEM "chrome://editor/locale/editor.dtd"> +%editorDTD; +]> + +<overlay id="composerOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"> + + <!-- Items in the File menu used only by Composer app --> + <menupopup id="menu_FilePopup"> + <menuitem id="fileExportToText" + insertafter="sep_print" + command="cmd_exportToText"/> + <menuitem id="previewInBrowser" + label="&previewCmd.label;" + accesskey="&previewCmd.accesskey;" + insertafter="fileExportToText" + command="cmd_preview"/> + <!-- menuitem id="menu_SendPage" is merged here from mailEditorOverlay.xhtml, + where "position" is assumed to be just after 'previewInBrowser' --> + </menupopup> + +</overlay> diff --git a/comm/suite/editor/base/content/editingOverlay.js b/comm/suite/editor/base/content/editingOverlay.js new file mode 100644 index 0000000000..19b41d321b --- /dev/null +++ b/comm/suite/editor/base/content/editingOverlay.js @@ -0,0 +1,387 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var gUntitledString; + +function TextEditorOnLoad() +{ + var url = "about:blank"; + // See if argument was passed. + if (window.arguments && window.arguments[0]) + { + // Opened via window.openDialog with URL as argument. + url = window.arguments[0]; + } + // Continue with normal startup. + EditorStartup(url); +} + +function EditorOnLoad() +{ + var url = "about:blank"; + var charset; + // See if argument was passed. + if (window.arguments) + { + if (window.arguments[0]) + { + // Opened via window.openDialog with URL as argument. + url = window.arguments[0]; + } + + // get default character set if provided + if (window.arguments.length > 1 && window.arguments[1]) + { + if (window.arguments[1].includes("charset=")) + { + var arrayArgComponents = window.arguments[1].split("="); + if (arrayArgComponents) + charset = arrayArgComponents[1]; + } + } + } + + // XUL elements we use when switching from normal editor to edit source. + gContentWindowDeck = document.getElementById("ContentWindowDeck"); + gFormatToolbar = document.getElementById("FormatToolbar"); + + // Continue with normal startup. + EditorStartup(url, charset); + + // Hide Highlight button if we are in an HTML editor with CSS mode off + // and tell the editor if a CR in a paragraph creates a new paragraph. + var cmd = document.getElementById("cmd_highlight"); + if (cmd) { + if (!Services.prefs.getBoolPref(kUseCssPref)) + cmd.collapsed = true; + } + + // Initialize our source text <editor> + try { + gSourceContentWindow = document.getElementById("content-source"); + gSourceContentWindow.makeEditable("text", false); + gSourceTextEditor = gSourceContentWindow.getEditor(gSourceContentWindow.contentWindow); + gSourceTextEditor.enableUndo(false); + gSourceTextEditor.rootElement.style.fontFamily = "-moz-fixed"; + gSourceTextEditor.rootElement.style.whiteSpace = "pre"; + gSourceTextEditor.rootElement.style.margin = 0; + var controller = Cc["@mozilla.org/embedcomp/base-command-controller;1"] + .createInstance(Ci.nsIControllerContext); + controller.setCommandContext(gSourceContentWindow); + gSourceContentWindow.contentWindow.controllers.insertControllerAt(0, controller); + var commandTable = controller.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIControllerCommandTable); + commandTable.registerCommand("cmd_findReplace", nsFindReplaceCommand); + commandTable.registerCommand("cmd_find", nsFindCommand); + commandTable.registerCommand("cmd_findNext", nsFindAgainCommand); + commandTable.registerCommand("cmd_findPrev", nsFindAgainCommand); + } catch (e) { + dump("makeEditable failed: "+e+"\n"); + } +} + +function toggleAffectedChrome(aHide) +{ + // chrome to toggle includes: + // (*) menubar + // (*) toolbox + // (*) sidebar + // (*) statusbar + + if (!gChromeState) + gChromeState = new Object; + + var statusbar = document.getElementById("status-bar"); + + // sidebar states map as follows: + // hidden => hide/show nothing + // collapsed => hide/show only the splitter + // shown => hide/show the splitter and the box + if (aHide) + { + // going into print preview mode + gChromeState.sidebar = SidebarGetState(); + SidebarSetState("hidden"); + + // deal with the Status Bar + gChromeState.statusbarWasHidden = statusbar.hidden; + statusbar.hidden = true; + } + else + { + // restoring normal mode (i.e., leaving print preview mode) + SidebarSetState(gChromeState.sidebar); + + // restore the Status Bar + statusbar.hidden = gChromeState.statusbarWasHidden; + } + + // if we are unhiding and sidebar used to be there rebuild it + if (!aHide && gChromeState.sidebar == "visible") + SidebarRebuild(); + + document.getElementById("EditorToolbox").hidden = aHide; + document.getElementById("appcontent").collapsed = aHide; +} + +var PrintPreviewListener = { + getPrintPreviewBrowser: function () { + var browser = document.getElementById("ppBrowser"); + if (!browser) { + browser = document.createXULElement("browser"); + browser.setAttribute("id", "ppBrowser"); + browser.setAttribute("flex", "1"); + browser.setAttribute("disablehistory", "true"); + browser.setAttribute("disablesecurity", "true"); + browser.setAttribute("type", "content"); + document.getElementById("sidebar-parent"). + insertBefore(browser, document.getElementById("appcontent")); + } + return browser; + }, + getSourceBrowser: function () { + return GetCurrentEditorElement(); + }, + getNavToolbox: function () { + return document.getElementById("EditorToolbox"); + }, + onEnter: function () { + toggleAffectedChrome(true); + }, + onExit: function () { + document.getElementById("ppBrowser").collapsed = true; + toggleAffectedChrome(false); + } +} + +function EditorStartup(aUrl, aCharset) +{ + gUntitledString = GetFormattedString("untitledTitle", GetNextUntitledValue()); + + var ds = GetCurrentEditorElement().docShell; + ds.useErrorPages = false; + var root = ds.QueryInterface(Ci.nsIDocShellTreeItem) + .rootTreeItem.QueryInterface(Ci.nsIDocShell); + + root.QueryInterface(Ci.nsIDocShell).appType = + Ci.nsIDocShell.APP_TYPE_EDITOR; + + // EditorSharedStartup also used by Message Composer. + EditorSharedStartup(); + + // Commands specific to the Composer Application window, + // (i.e., not embedded editors) + // such as file-related commands, HTML Source editing, Edit Modes... + SetupComposerWindowCommands(); + + gCSSPrefListener = new nsPrefListener(kUseCssPref); + gReturnInParagraphPrefListener = new nsPrefListener(kCRInParagraphsPref); + Services.obs.addObserver(EditorCanClose, "quit-application-requested"); + + root.charset = aCharset; + + // Get url for editor content and load it. The editor gets instantiated by + // the editingSession when the URL has finished loading. + EditorLoadUrl(aUrl); + + // Before and after callbacks for the customizeToolbar code. + var editorToolbox = getEditorToolbox(); + editorToolbox.customizeInit = EditorToolboxCustomizeInit; + editorToolbox.customizeDone = EditorToolboxCustomizeDone; + editorToolbox.customizeChange = EditorToolboxCustomizeChange; +} + +function EditorShutdown() +{ + Services.obs.removeObserver(EditorCanClose, "quit-application-requested"); + + gCSSPrefListener.shutdown(); + gReturnInParagraphPrefListener.shutdown(); + + try + { + var commandManager = GetCurrentCommandManager(); + commandManager.removeCommandObserver(gEditorDocumentObserver, + "obs_documentCreated"); + commandManager.removeCommandObserver(gEditorDocumentObserver, + "obs_documentWillBeDestroyed"); + commandManager.removeCommandObserver(gEditorDocumentObserver, + "obs_documentLocationChanged"); + } catch (e) { dump (e); } +} + +// --------------------------- File menu --------------------------- + +// Check for changes to document and allow saving before closing +// This is hooked up to the OS's window close widget (e.g., "X" for Windows) +async function EditorCanClose(aCancelQuit, aTopic, aData) +{ + if (aTopic == "quit-application-requested" && + aCancelQuit instanceof Ci.nsISupportsPRBool && + aCancelQuit.data) + return false; + + // Returns FALSE only if user cancels save action + + // "true" means allow "Don't Save" button + var canClose = await CheckAndSaveDocument("cmd_close", true); + + // This is our only hook into closing via the "X" in the caption + // or "Quit" (or other paths?) + // so we must shift association to another + // editor or close any non-modal windows now + if (canClose && "InsertCharWindow" in window && window.InsertCharWindow) + SwitchInsertCharToAnotherEditorOrClose(); + + if (!canClose && aTopic == "quit-application-requested") + aCancelQuit.data = true; + + return canClose; +} + +function BuildRecentPagesMenu() +{ + var editor = GetCurrentEditor(); + if (!editor) + return; + + var popup = document.getElementById("menupopup_RecentFiles"); + if (!popup || !editor.document) + return; + + // Delete existing menu + while (popup.hasChildNodes()) + popup.lastChild.remove(); + + // Current page is the "0" item in the list we save in prefs, + // but we don't include it in the menu. + var curUrl = StripPassword(GetDocumentUrl()); + var historyCount = Services.prefs.getIntPref("editor.history.url_maximum", 10); + + var menuIndex = 1; + for (var i = 0; i < historyCount; i++) + { + var url = Services.prefs.getStringPref("editor.history_url_" + i, ""); + + // Skip over current url + if (url && url != curUrl) + { + // Build the menu + var title = Services.prefs.getStringPref("editor.history_title_" + i, ""); + var fileType = Services.prefs.getStringPref("editor.history_type_" + i, ""); + AppendRecentMenuitem(popup, title, url, fileType, menuIndex); + menuIndex++; + } + } +} + +function AppendRecentMenuitem(aPopup, aTitle, aUrl, aFileType, aIndex) +{ + if (!aPopup) + return; + + var menuItem = document.createXULElement("menuitem"); + if (!menuItem) + return; + + var accessKey = aIndex <= 10 ? String(aIndex % 10) : " "; + + // Show "title [url]" or just the URL. + var itemString = aTitle ? aTitle + " [" + aUrl + "]" : aUrl; + + menuItem.setAttribute("label", accessKey + " " + itemString); + menuItem.setAttribute("crop", "center"); + menuItem.setAttribute("tooltiptext", aUrl); + menuItem.setAttribute("value", aUrl); + menuItem.setAttribute("fileType", aFileType); + if (accessKey != " ") + menuItem.setAttribute("accesskey", accessKey); + aPopup.appendChild(menuItem); +} + +function EditorInitFileMenu() +{ + // Disable "Save" menuitem when editing remote url. User should use "Save As" + + var docUrl = GetDocumentUrl(); + var scheme = GetScheme(docUrl); + if (scheme && scheme != "file") + SetElementEnabledById("menu_saveCmd", false); + + // Enable recent pages submenu if there are any history entries in prefs. + var historyUrl = ""; + + if (Services.prefs.getIntPref("editor.history.url_maximum", 10)) + { + historyUrl = Services.prefs.getStringPref("editor.history_url_0", ""); + + // See if there's more if current file is only entry in history list. + if (historyUrl && historyUrl == docUrl) + historyUrl = Services.prefs.getStringPref("editor.history_url_1", ""); + } + SetElementEnabledById("menu_RecentFiles", historyUrl != ""); +} + +function EditorUpdateCharsetMenu(aMenuPopup) +{ + if (IsDocumentModified() && !IsDocumentEmpty()) + { + for (var i = 0; i < aMenuPopup.childNodes.length; i++) + aMenuPopup.childNodes[i].setAttribute("disabled", "true"); + } + + UpdateCharsetMenu(content.document.characterSet, aMenuPopup); +} + +// Zoom support. +function getBrowser() +{ + return IsInHTMLSourceMode() ? gSourceContentWindow : GetCurrentEditorElement(); +} + +// override the site-specific zoom object in viewZoomOverlay.js +var FullZoom = { + init: function() {}, + reduce: function() { ZoomManager.reduce(); }, + enlarge: function() { ZoomManager.enlarge(); }, + zoom: function(aZoomValue) { ZoomManager.zoom = aZoomValue; }, + reset: function() { ZoomManager.zoom = 1; }, + setOther: function() { openZoomDialog(); } +}; + +function hideEditorUI(aHide) { + for (let id of ["EditModeToolbar", "content-source", "content-frame"]) { + let element = document.getElementById(id); + if (!element) + continue; + + if (aHide) { + element.setAttribute("moz-collapsed", true); + } else { + element.removeAttribute("moz-collapsed"); + } + } +} + +function getEditorToolbox() { + return document.getElementById("EditorToolbox"); +} + +function EditorToolboxCustomizeInit() { + if (document.commandDispatcher.focusedWindow == content) + window.focus(); + hideEditorUI(true); + toolboxCustomizeInit("main-menubar"); +} + +function EditorToolboxCustomizeDone(aToolboxChanged) { + toolboxCustomizeDone("main-menubar", getEditorToolbox(), aToolboxChanged); + hideEditorUI(false); + gContentWindow.focus(); +} + +function EditorToolboxCustomizeChange(aEvent) { + toolboxCustomizeChange(getEditorToolbox(), aEvent); +} diff --git a/comm/suite/editor/base/content/editingOverlay.xhtml b/comm/suite/editor/base/content/editingOverlay.xhtml new file mode 100644 index 0000000000..21833857a6 --- /dev/null +++ b/comm/suite/editor/base/content/editingOverlay.xhtml @@ -0,0 +1,247 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xul-overlay href="chrome://communicator/content/viewZoomOverlay.xhtml"?> + +<!DOCTYPE overlay SYSTEM "chrome://editor/locale/editingOverlay.dtd"> + +<overlay id="editingOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"> + + <script src="chrome://editor/content/editingOverlay.js"/> + + <stringbundleset> + <stringbundle id="bundle_viewZoom"/> + </stringbundleset> + + <keyset id="editorKeys"> + <key id="key_openRemoteEditor" + key="&openRemoteCmd.key;" + command="cmd_openRemote" + modifiers="accel,shift"/> + <key id="key_openEditor" + key="&openFileCmd.key;" + command="cmd_open" + modifiers="accel"/> + <key id="key_publish" + key="&publishCmd.key;" + command="cmd_publish" + modifiers="accel,shift"/> + <keyset id="viewZoomKeys"/> + </keyset> + + <!-- commands updated when the editor gets created --> + <commandset id="commonEditorMenuItems"> + <command id="cmd_open" + oncommand="goDoCommand('cmd_open');"/> + <command id="cmd_openRemote" + oncommand="goDoCommand('cmd_openRemote');"/> + </commandset> + + <commandset id="composerSaveMenuItems" + commandupdater="true" + events="create, save" + oncommandupdate="goUpdateComposerMenuItems(this);"> + <command id="cmd_save" + label="&saveCmd.label;" + oncommand="goDoCommand('cmd_save');"/> + <command id="cmd_saveAs" + oncommand="goDoCommand('cmd_saveAs');"/> + <command id="cmd_saveAndChangeEncoding" + oncommand="goDoCommand('cmd_saveAndChangeEncoding');"/> + <command id="cmd_publish" + label="&publishCmd.label;" + oncommand="goDoCommand('cmd_publish');"/> + <command id="cmd_publishAs" + oncommand="goDoCommand('cmd_publishAs');"/> + <command id="cmd_revert" + oncommand="goDoCommand('cmd_revert');"/> + </commandset> + + <commandset id="composerEditMenuItems"> + <command id="cmd_publishSettings" + oncommand="goDoCommand('cmd_publishSettings');"/> + </commandset> + + <commandset id="composerMenuItems"> + <command id="cmd_form" + oncommand="goDoCommand('cmd_form');"/> + <command id="cmd_inputtag" + oncommand="goDoCommand('cmd_inputtag');"/> + <command id="cmd_inputimage" + oncommand="goDoCommand('cmd_inputimage');"/> + <command id="cmd_textarea" + oncommand="goDoCommand('cmd_textarea');"/> + <command id="cmd_select" + oncommand="goDoCommand('cmd_select');"/> + <command id="cmd_button" + oncommand="goDoCommand('cmd_button');"/> + <command id="cmd_label" + oncommand="goDoCommand('cmd_label');"/> + <command id="cmd_fieldset" + oncommand="goDoCommand('cmd_fieldset');"/> + </commandset> + + <commandset id="editorCommands"> + <command id="cmd_CustomizeToolbars" + oncommand="goCustomizeToolbar(getEditorToolbox());"/> + <commandset id="viewZoomCommands"/> + </commandset> + + <!-- File menu items --> + <menu id="menu_File"> + <menupopup id="menu_FilePopup" onpopupshowing="EditorInitFileMenu();"> + <menu id="menu_New" class="menu-iconic"> + <menupopup id="menu_NewPopup"> + <menuitem id="menu_newEditor"/> + <menuseparator id="sep_NewPopup"/> + <menuitem id="menu_newNavigator"/> + <menuitem id="menu_newPrivateWindow"/> + </menupopup> + </menu> + <menuitem id="menu_openRemote" + label="&openRemoteCmd.label;" + accesskey="&openRemoteCmd.accesskey;" + key="key_openRemoteEditor" + command="cmd_openRemote"/> + <menuitem id="menu_openFile" + label="&openFileCmd.label;" + accesskey="&openFileCmd.accesskey;" + key="key_openEditor" + command="cmd_open"/> + <menu id="menu_RecentFiles" + label="&fileRecentMenu.label;" + accesskey="&fileRecentMenu.accesskey;" + onpopupshowing="BuildRecentPagesMenu();"> + <menupopup id="menupopup_RecentFiles" + oncommand="editPage(event.target.getAttribute('value'), + event.target.getAttribute('fileType'));"/> + <!-- menuitems appended at runtime --> + </menu> + <menuitem id="menu_close" class="menuitem-iconic"/> + <menuseparator id="sep_close"/> + <menuitem id="menu_saveCmd" + accesskey="&saveCmd.accesskey;" + key="key_save" + command="cmd_save" + class="menuitem-iconic"/> + <menuitem id="menu_saveAsCmd" + label="&saveAsCmd.label;" + accesskey="&saveAsCmd.accesskey;" + command="cmd_saveAs" + class="menuitem-iconic"/> + <menuitem id="menu_saveAsChangeEncoding" + label="&saveAsChangeEncodingCmd2.label;" + accesskey="&saveAsChangeEncodingCmd2.accesskey;" + command="cmd_saveAndChangeEncoding"/> + <menuseparator id="sep_saveCmd"/> + <menuitem id="menu_publish" + accesskey="&publishCmd.accesskey;" + key="key_publish" + command="cmd_publish"/> + <menuitem id="menu_publishAs" + label="&publishAsCmd.label;" + accesskey="&publishAsCmd.accesskey;" + command="cmd_publishAs"/> + <menuseparator id="sep_publishAs"/> + <menuitem id="menu_fileRevert" + label="&fileRevert.label;" + accesskey="&fileRevert.accesskey;" + command="cmd_revert"/> + <menuseparator id="sep_print"/> + <!-- menuitems are merged in here from composerOverlay.xhtml --> + <menuitem id="menu_printSetup"/> + <menuitem id="menu_printPreview" class="menuitem-iconic"/> + <menuitem id="menu_print" class="menuitem-iconic"/> + <!-- The Exit/Quit item is merged from platformGlobalOverlay.xhtml --> + </menupopup> + </menu> + + <!-- Edit menu items --> + <menupopup id="menu_EditPopup"> + <menuitem id="menu_inlineSpellCheck" + oncommand="InlineSpellCheckerUI.enabled = !InlineSpellCheckerUI.enabled" + class="menuitem-iconic"/> + <menuitem id="menu_publishSettings" + insertafter="sep_preferences" + label="&publishSettings.label;" + accesskey="&publishSettings.accesskey;" + command="cmd_publishSettings"/> + </menupopup> + + <menupopup id="menu_View_Popup"> + <menu id="menu_zoom" insertbefore="charsetMenu"/> + </menupopup> + + <menupopup id="insertMenuPopup"> + <menu id="insertFormMenu" + insertafter="insertTOC" + label="&insertFormMenu.label;" + accesskey="&insertFormMenu.accesskey;"> + <menupopup id="formMenuPopup"> + <menuitem label="&insertFormCmd.label;" + accesskey="&insertFormCmd.accesskey;" + command="cmd_form"/> + <menuseparator/> + <menuitem label="&insertInputTagCmd.label;" + accesskey="&insertInputTagCmd.accesskey;" + command="cmd_inputtag"/> + <menuitem label="&insertInputImageCmd.label;" + accesskey="&insertInputImageCmd.accesskey;" + command="cmd_inputimage"/> + <menuitem label="&insertTextAreaCmd.label;" + accesskey="&insertTextAreaCmd.accesskey;" + command="cmd_textarea"/> + <menuitem label="&insertSelectCmd.label;" + accesskey="&insertSelectCmd.accesskey;" + command="cmd_select"/> + <menuitem label="&insertButtonCmd.label;" + accesskey="&insertButtonCmd.accesskey;" + command="cmd_button"/> + <menuitem label="&insertLabelCmd.label;" + accesskey="&insertLabelCmd.accesskey;" + command="cmd_label"/> + <menuitem label="&insertFieldSetCmd.label;" + accesskey="&insertFieldSetCmd.accesskey;" + command="cmd_fieldset"/> + </menupopup> + </menu> + </menupopup> + + <!-- Toolbar buttons/items --> + <toolbarbutton id="newButton" + class="toolbarbutton-1" + label="&newToolbarCmd.label;" + removable="true" + command="cmd_newEditor" + tooltiptext="&newToolbarCmd.tooltip;"/> + <toolbarbutton id="openButton" + class="toolbarbutton-1" + label="&openToolbarCmd.label;" + removable="true" + command="cmd_open" + tooltiptext="&openToolbarCmd.tooltip;"/> + <toolbarbutton id="saveButton" + class="toolbarbutton-1" + removable="true" + command="cmd_save" + tooltiptext="&saveToolbarCmd.tooltip;"/> + <toolbarbutton id="publishButton" + class="toolbarbutton-1" + removable="true" + command="cmd_publish" + tooltiptext="&publishToolbarCmd.tooltip;"/> + <toolbarbutton id="print-button" + label="&printToolbarCmd.label;" + removable="true" + tooltiptext="&printToolbarCmd.tooltip;"/> + <!-- 'print-button' is merged in here from utilityOverlay.xhtml --> + <toolbarbutton id="formButton" + class="toolbarbutton-1" + removable="true" + label="&formToolbarCmd.label;" + command="cmd_form" + tooltiptext="&formToolbarCmd.tooltip;"/> +</overlay> diff --git a/comm/suite/editor/base/content/editor.js b/comm/suite/editor/base/content/editor.js new file mode 100644 index 0000000000..34270d057a --- /dev/null +++ b/comm/suite/editor/base/content/editor.js @@ -0,0 +1,3383 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../../../mail/base/content/utilityOverlay.js */ +/* import-globals-from ComposerCommands.js */ +/* import-globals-from editorUtilities.js */ +/* globals InlineSpellCheckerUI */ + +var { GetNextUntitledValue } = ChromeUtils.import( + "resource:///modules/editorUtilities.jsm" +); +var { Async } = ChromeUtils.import("resource://services-common/async.js"); +var { AppConstants } = ChromeUtils.import( + "resource://gre/modules/AppConstants.jsm" +); + +/* Main Composer window UI control */ + +var gComposerWindowControllerID = 0; +var prefAuthorString = ""; + +var kDisplayModeNormal = 0; +var kDisplayModeAllTags = 1; +var kDisplayModeSource = 2; +var kDisplayModePreview = 3; + +const kDisplayModeMenuIDs = [ + "viewNormalMode", + "viewAllTagsMode", + "viewSourceMode", + "viewPreviewMode", +]; +const kDisplayModeTabIDS = [ + "NormalModeButton", + "TagModeButton", + "SourceModeButton", + "PreviewModeButton", +]; +const kNormalStyleSheet = "chrome://editor/content/EditorContent.css"; +const kAllTagsStyleSheet = "chrome://editor/content/EditorAllTags.css"; +const kContentEditableStyleSheet = "resource://gre/res/contenteditable.css"; + +var kTextMimeType = "text/plain"; +var kHTMLMimeType = "text/html"; +var kXHTMLMimeType = "application/xhtml+xml"; + +var gPreviousNonSourceDisplayMode = 1; +var gEditorDisplayMode = -1; +var gDocWasModified = false; // Check if clean document, if clean then unload when user "Opens" +var gContentWindow = 0; +var gSourceContentWindow = 0; +var gSourceTextEditor = null; +var gContentWindowDeck; +var gFormatToolbar; +var gFormatToolbarHidden = false; +var gChromeState; +var gColorObj = { + LastTextColor: "", + LastBackgroundColor: "", + LastHighlightColor: "", + Type: "", + SelectedType: "", + NoDefault: false, + Cancel: false, + HighlightColor: "", + BackgroundColor: "", + PageColor: "", + TextColor: "", + TableColor: "", + CellColor: "", +}; +var gDefaultTextColor = ""; +var gDefaultBackgroundColor = ""; +var gCSSPrefListener; +var gReturnInParagraphPrefListener; +var gLocalFonts = null; + +var gLastFocusNode = null; +var gLastFocusNodeWasSelected = false; + +// These must be kept in synch with the XUL <options> lists +var gFontSizeNames = [ + "xx-small", + "x-small", + "small", + "medium", + "large", + "x-large", + "xx-large", +]; + +var nsIFilePicker = Ci.nsIFilePicker; + +var kUseCssPref = "editor.use_css"; +var kCRInParagraphsPref = "editor.CR_creates_new_p"; + +function nsPrefListener(prefName) { + this.startup(prefName); +} + +// implements nsIObserver +nsPrefListener.prototype = { + domain: "", + startup(prefName) { + this.domain = prefName; + try { + Services.prefs.addObserver(this.domain, this); + } catch (ex) { + dump("Failed to observe prefs: " + ex + "\n"); + } + }, + shutdown() { + try { + Services.prefs.removeObserver(this.domain, this); + } catch (ex) { + dump("Failed to remove pref observers: " + ex + "\n"); + } + }, + observe(subject, topic, prefName) { + if (!IsHTMLEditor()) { + return; + } + // verify that we're changing a button pref + if (topic != "nsPref:changed") { + return; + } + + let editor = GetCurrentEditor(); + if (prefName == kUseCssPref) { + let cmd = document.getElementById("cmd_highlight"); + if (cmd) { + let useCSS = Services.prefs.getBoolPref(prefName); + + if (useCSS && editor) { + let mixedObj = {}; + let state = editor.getHighlightColorState(mixedObj); + cmd.setAttribute("state", state); + cmd.collapsed = false; + } else { + cmd.setAttribute("state", "transparent"); + cmd.collapsed = true; + } + + if (editor) { + editor.isCSSEnabled = useCSS; + } + } + } else if (editor && prefName == kCRInParagraphsPref) { + editor.returnInParagraphCreatesNewParagraph = Services.prefs.getBoolPref( + prefName + ); + } + }, +}; + +const gSourceTextListener = { + NotifyDocumentCreated() {}, + NotifyDocumentWillBeDestroyed() {}, + NotifyDocumentStateChanged(isChanged) { + window.updateCommands("save"); + }, +}; + +const gSourceTextObserver = { + observe(aSubject, aTopic, aData) { + // we currently only use this to update undo + window.updateCommands("undo"); + }, +}; + +// This should be called by all editor users when they close their window. +function EditorCleanup() { + SwitchInsertCharToAnotherEditorOrClose(); +} + +var DocumentReloadListener = { + NotifyDocumentCreated() {}, + NotifyDocumentWillBeDestroyed() {}, + + NotifyDocumentStateChanged(isNowDirty) { + var editor = GetCurrentEditor(); + try { + // unregister the listener to prevent multiple callbacks + editor.removeDocumentStateListener(DocumentReloadListener); + + var charset = editor.documentCharacterSet; + + // update the META charset with the current presentation charset + editor.documentCharacterSet = charset; + } catch (e) {} + }, +}; + +// implements nsIObserver +var gEditorDocumentObserver = { + observe(aSubject, aTopic, aData) { + // Should we allow this even if NOT the focused editor? + var commandManager = GetCurrentCommandManager(); + if (commandManager != aSubject) { + return; + } + + var editor = GetCurrentEditor(); + switch (aTopic) { + case "obs_documentCreated": + // Just for convenience + gContentWindow = window.content; + + // Get state to see if document creation succeeded + var params = newCommandParams(); + if (!params) { + return; + } + + try { + commandManager.getCommandState(aTopic, gContentWindow, params); + var errorStringId = 0; + var editorStatus = params.getLongValue("state_data"); + if (!editor && editorStatus == nsIEditingSession.eEditorOK) { + dump( + "\n ****** NO EDITOR BUT NO EDITOR ERROR REPORTED ******* \n\n" + ); + editorStatus = nsIEditingSession.eEditorErrorUnknown; + } + + switch (editorStatus) { + case nsIEditingSession.eEditorErrorCantEditFramesets: + errorStringId = "CantEditFramesetMsg"; + break; + case nsIEditingSession.eEditorErrorCantEditMimeType: + errorStringId = "CantEditMimeTypeMsg"; + break; + case nsIEditingSession.eEditorErrorUnknown: + errorStringId = "CantEditDocumentMsg"; + break; + // Note that for "eEditorErrorFileNotFound, + // network code popped up an alert dialog, so we don't need to + } + if (errorStringId) { + Services.prompt.alert(window, "", GetString(errorStringId)); + } + } catch (e) { + dump("EXCEPTION GETTING obs_documentCreated state " + e + "\n"); + } + + // We have a bad editor -- nsIEditingSession will rebuild an editor + // with a blank page, so simply abort here + if (editorStatus) { + return; + } + + if (!("InsertCharWindow" in window)) { + window.InsertCharWindow = null; + } + + try { + editor.QueryInterface(nsIEditorStyleSheets); + + // and extra styles for showing anchors, table borders, smileys, etc + editor.addOverrideStyleSheet(kNormalStyleSheet); + + // remove contenteditable stylesheets if they were applied by the + // editingSession + editor.removeOverrideStyleSheet(kContentEditableStyleSheet); + } catch (e) {} + + // Things for just the Web Composer application + if (IsWebComposer()) { + InlineSpellCheckerUI.init(editor); + document + .getElementById("menu_inlineSpellCheck") + .setAttribute("disabled", !InlineSpellCheckerUI.canSpellCheck); + + editor.returnInParagraphCreatesNewParagraph = Services.prefs.getBoolPref( + kCRInParagraphsPref + ); + + // Set focus to content window if not a mail composer + // Race conditions prevent us from setting focus here + // when loading a url into blank window + setTimeout(SetFocusOnStartup, 0); + + // Call EditorSetDefaultPrefsAndDoctype first so it gets the default author before initing toolbars + editor.enableUndo(false); + EditorSetDefaultPrefsAndDoctype(); + editor.resetModificationCount(); + editor.enableUndo(true); + + // We may load a text document into an html editor, + // so be sure editortype is set correctly + // XXX We really should use the "real" plaintext editor for this! + if (editor.contentsMIMEType == "text/plain") { + try { + GetCurrentEditorElement().editortype = "text"; + } catch (e) { + dump(e) + "\n"; + } + + // Hide or disable UI not used for plaintext editing + HideItem("FormatToolbar"); + HideItem("EditModeToolbar"); + HideItem("formatMenu"); + HideItem("tableMenu"); + HideItem("menu_validate"); + HideItem("sep_validate"); + HideItem("previewButton"); + HideItem("imageButton"); + HideItem("linkButton"); + HideItem("namedAnchorButton"); + HideItem("hlineButton"); + HideItem("tableButton"); + + HideItem("fileExportToText"); + HideItem("previewInBrowser"); + + /* XXX When paste actually converts formatted rich text to pretty formatted plain text + and pasteNoFormatting is fixed to paste the text without formatting (what paste + currently does), then this item shouldn't be hidden: */ + HideItem("menu_pasteNoFormatting"); + + HideItem("cmd_viewEditModeToolbar"); + + HideItem("viewSep1"); + HideItem("viewNormalMode"); + HideItem("viewAllTagsMode"); + HideItem("viewSourceMode"); + HideItem("viewPreviewMode"); + + HideItem("structSpacer"); + + // Hide everything in "Insert" except for "Symbols" + let menuPopupChildren = document.querySelectorAll( + '[id="insertMenuPopup"] > :not(#insertChars)' + ); + for (let i = 0; i < menuPopupChildren.length; i++) { + menuPopupChildren.item(i).hidden = true; + } + } + + // Set window title + UpdateWindowTitle(); + + // We must wait until document is created to get proper Url + // (Windows may load with local file paths) + SetSaveAndPublishUI(GetDocumentUrl()); + + // Start in "Normal" edit mode + SetDisplayMode(kDisplayModeNormal); + } + + // Add mouse click watcher if right type of editor + if (IsHTMLEditor()) { + // Force color widgets to update + onFontColorChange(); + onBackgroundColorChange(); + } + break; + + case "cmd_setDocumentModified": + window.updateCommands("save"); + break; + + case "obs_documentWillBeDestroyed": + dump("obs_documentWillBeDestroyed notification\n"); + break; + + case "obs_documentLocationChanged": + // Ignore this when editor doesn't exist, + // which happens once when page load starts + if (editor) { + try { + editor.updateBaseURL(); + } catch (e) { + dump(e); + } + } + break; + + case "cmd_bold": + // Update all style items + // cmd_bold is a proxy; see EditorSharedStartup (above) for details + window.updateCommands("style"); + window.updateCommands("undo"); + break; + } + }, +}; + +function SetFocusOnStartup() { + gContentWindow.focus(); +} + +function EditorLoadUrl(url) { + try { + if (url) { + let loadURIOptions = { + loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE, + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + }; + GetCurrentEditorElement().webNavigation.loadURI(url, loadURIOptions); + } + } catch (e) { + dump(" EditorLoadUrl failed: " + e + "\n"); + } +} + +// This should be called by all Composer types +function EditorSharedStartup() { + // Just for convenience + gContentWindow = window.content; + + // Disable DNS Prefetching on the docshell - we don't need it for composer + // type windows. + GetCurrentEditorElement().docShell.allowDNSPrefetch = false; + + // Set up the mime type and register the commands. + if (IsHTMLEditor()) { + SetupHTMLEditorCommands(); + } else { + SetupTextEditorCommands(); + } + + // add observer to be called when document is really done loading + // and is modified + // Note: We're really screwed if we fail to install this observer! + try { + var commandManager = GetCurrentCommandManager(); + commandManager.addCommandObserver( + gEditorDocumentObserver, + "obs_documentCreated" + ); + commandManager.addCommandObserver( + gEditorDocumentObserver, + "cmd_setDocumentModified" + ); + commandManager.addCommandObserver( + gEditorDocumentObserver, + "obs_documentWillBeDestroyed" + ); + commandManager.addCommandObserver( + gEditorDocumentObserver, + "obs_documentLocationChanged" + ); + + // Until nsIControllerCommandGroup-based code is implemented, + // we will observe just the bold command to trigger update of + // all toolbar style items + commandManager.addCommandObserver(gEditorDocumentObserver, "cmd_bold"); + } catch (e) { + dump(e); + } + + var isMac = AppConstants.platform == "macosx"; + + // Set platform-specific hints for how to select cells + // Mac uses "Cmd", all others use "Ctrl" + var tableKey = GetString(isMac ? "XulKeyMac" : "TableSelectKey"); + var dragStr = tableKey + GetString("Drag"); + var clickStr = tableKey + GetString("Click"); + + var delStr = GetString(isMac ? "Clear" : "Del"); + + SafeSetAttribute("menu_SelectCell", "acceltext", clickStr); + SafeSetAttribute("menu_SelectRow", "acceltext", dragStr); + SafeSetAttribute("menu_SelectColumn", "acceltext", dragStr); + SafeSetAttribute("menu_SelectAllCells", "acceltext", dragStr); + // And add "Del" or "Clear" + SafeSetAttribute("menu_DeleteCellContents", "acceltext", delStr); + + // Set text for indent, outdent keybinding + + // hide UI that we don't have components for + RemoveInapplicableUIElements(); + + // Use browser colors as initial values for editor's default colors + var BrowserColors = GetDefaultBrowserColors(); + if (BrowserColors) { + gDefaultTextColor = BrowserColors.TextColor; + gDefaultBackgroundColor = BrowserColors.BackgroundColor; + } + + // For new window, no default last-picked colors + gColorObj.LastTextColor = ""; + gColorObj.LastBackgroundColor = ""; + gColorObj.LastHighlightColor = ""; +} + +function SafeSetAttribute(nodeID, attributeName, attributeValue) { + var theNode = document.getElementById(nodeID); + if (theNode) { + theNode.setAttribute(attributeName, attributeValue); + } +} + +function DocumentHasBeenSaved() { + var fileurl = ""; + try { + fileurl = GetDocumentUrl(); + } catch (e) { + return false; + } + + if (!fileurl || IsUrlAboutBlank(fileurl)) { + return false; + } + + // We have a file URL already + return true; +} + +async function CheckAndSaveDocument(command, allowDontSave) { + var document; + try { + // if we don't have an editor or an document, bail + var editor = GetCurrentEditor(); + document = editor.document; + if (!document) { + return true; + } + } catch (e) { + return true; + } + + if (!IsDocumentModified() && !IsHTMLSourceChanged()) { + return true; + } + + // call window.focus, since we need to pop up a dialog + // and therefore need to be visible (to prevent user confusion) + top.document.commandDispatcher.focusedWindow.focus(); + + var scheme = GetScheme(GetDocumentUrl()); + var doPublish = scheme && scheme != "file"; + + var strID; + switch (command) { + case "cmd_close": + strID = "BeforeClosing"; + break; + case "cmd_preview": + strID = "BeforePreview"; + break; + case "cmd_editSendPage": + strID = "SendPageReason"; + break; + case "cmd_validate": + strID = "BeforeValidate"; + break; + } + + var reasonToSave = strID ? GetString(strID) : ""; + + var title = document.title || GetString("untitledDefaultFilename"); + + var dialogTitle = GetString(doPublish ? "PublishPage" : "SaveDocument"); + var dialogMsg = GetString(doPublish ? "PublishPrompt" : "SaveFilePrompt"); + dialogMsg = dialogMsg + .replace(/%title%/, title) + .replace(/%reason%/, reasonToSave); + + let result = { value: 0 }; + let promptFlags = + Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1; + let button1Title = null; + let button3Title = null; + + if (doPublish) { + promptFlags += + Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0; + button1Title = GetString("Publish"); + button3Title = GetString("DontPublish"); + } else { + promptFlags += + Services.prompt.BUTTON_TITLE_SAVE * Services.prompt.BUTTON_POS_0; + } + + // If allowing "Don't..." button, add that + if (allowDontSave) { + promptFlags += doPublish + ? Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_2 + : Services.prompt.BUTTON_TITLE_DONT_SAVE * Services.prompt.BUTTON_POS_2; + } + + result = Services.prompt.confirmEx( + window, + dialogTitle, + dialogMsg, + promptFlags, + button1Title, + null, + button3Title, + null, + { value: 0 } + ); + + if (result == 0) { + // Save, but first finish HTML source mode + SetEditMode(gPreviousNonSourceDisplayMode); + if (doPublish) { + // We save the command the user wanted to do in a global + // and return as if user canceled because publishing is asynchronous + // This command will be fired when publishing finishes + gCommandAfterPublishing = command; + goDoCommand("cmd_publish"); + return false; + } + + // Save to local disk + return SaveDocument(false, false, editor.contentsMIMEType); + } + + if (result == 2) { + // "Don't Save" + return true; + } + + // Default or result == 1 (Cancel) + return false; +} + +// --------------------------- View menu --------------------------- + +function EditorSetCharacterSet(aEvent) { + try { + var editor = GetCurrentEditor(); + if (aEvent.target.hasAttribute("charset")) { + editor.documentCharacterSet = aEvent.target.getAttribute("charset"); + } + var docUrl = GetDocumentUrl(); + if (!IsUrlAboutBlank(docUrl)) { + // reloading the document will reverse any changes to the META charset, + // we need to put them back in, which is achieved by a dedicated listener + editor.addDocumentStateListener(DocumentReloadListener); + EditorLoadUrl(docUrl); + } + } catch (e) {} +} + +// --------------------------- Text style --------------------------- + +function onParagraphFormatChange(paraMenuList, commandID) { + if (!paraMenuList) { + return; + } + + var commandNode = document.getElementById(commandID); + var state = commandNode.getAttribute("state"); + + // force match with "normal" + if (state == "body") { + state = ""; + } + + if (state == "mixed") { + // Selection is the "mixed" ( > 1 style) state + paraMenuList.selectedItem = null; + paraMenuList.setAttribute("label", GetString("Mixed")); + } else { + var menuPopup = document.getElementById("ParagraphPopup"); + var menuItems = menuPopup.childNodes; + for (var i = 0; i < menuItems.length; i++) { + var menuItem = menuItems.item(i); + if ("value" in menuItem && menuItem.value == state) { + paraMenuList.selectedItem = menuItem; + break; + } + } + } +} + +/** + * Selects the current font face in the menulist. + * + * @param fontFaceMenuList The menulist element containing the list of fonts. + * @param commandID The commandID which holds the current font name + * in its "state" attribute. + */ +function onFontFaceChange(fontFaceMenuList, commandID) { + var commandNode = document.getElementById(commandID); + var editorFont = commandNode.getAttribute("state"); + + // Strip quotes in font names. Experiments have shown that we only + // ever get double quotes around the font name, never single quotes, + // even if they were in the HTML source. Also single or double + // quotes within the font name are never returned. + editorFont = editorFont.replace(/"/g, ""); + + switch (editorFont) { + case "mixed": + // Selection is the "mixed" ( > 1 style) state. + fontFaceMenuList.selectedItem = null; + fontFaceMenuList.setAttribute("label", GetString("Mixed")); + return; + case "": + case "serif": + case "sans-serif": + // Generic variable width. + fontFaceMenuList.selectedIndex = 0; + return; + case "tt": + case "monospace": + // Generic fixed width. + fontFaceMenuList.selectedIndex = 1; + return; + default: + } + + let menuPopup = fontFaceMenuList.menupopup; + let menuItems = menuPopup.childNodes; + + const genericFamilies = [ + "serif", + "sans-serif", + "monospace", + "fantasy", + "cursive", + ]; + // Bug 1139524: Normalise before we compare: Make it lower case + // and replace ", " with "," so that entries like + // "Helvetica, Arial, sans-serif" are always recognised correctly + let editorFontToLower = editorFont.toLowerCase().replace(/, /g, ","); + let foundFont = null; + let exactMatch = false; + let usedFontsSep = menuPopup.querySelector( + "menuseparator.fontFaceMenuAfterUsedFonts" + ); + let editorFontOptions = editorFontToLower.split(","); + let editorOptionsCount = editorFontOptions.length; + let matchedFontIndex = editorOptionsCount; // initialise to high invalid value + + // The font menu has this structure: + // 0: Variable Width + // 1: Fixed Width + // 2: Separator + // 3: Helvetica, Arial (stored as Helvetica, Arial, sans-serif) + // 4: Times (stored as Times New Roman, Times, serif) + // 5: Courier (stored as Courier New, Courier, monospace) + // 6: Separator, "menuseparator.fontFaceMenuAfterDefaultFonts" + // from 7: Used Font Section (for quick selection) + // followed by separator, "menuseparator.fontFaceMenuAfterUsedFonts" + // followed by all other available fonts. + // The following variable keeps track of where we are when we loop over the menu. + let afterUsedFontSection = false; + + // The menu items not only have "label" and "value", but also some other attributes: + // "value_parsed": Is the toLowerCase() and space-stripped value. + // "value_cache": Is a concatenation of all editor fonts that were ever mapped + // onto this menu item. This is done for optimization. + // "used": This item is in the used font section. + + for (let i = 0; i < menuItems.length; i++) { + let menuItem = menuItems.item(i); + if ( + menuItem.hasAttribute("label") && + menuItem.hasAttribute("value_parsed") + ) { + // The element seems to represent a font <menuitem>. + let fontMenuValue = menuItem.getAttribute("value_parsed"); + if ( + fontMenuValue == editorFontToLower || + (menuItem.hasAttribute("value_cache") && + menuItem + .getAttribute("value_cache") + .split("|") + .includes(editorFontToLower)) + ) { + // This menuitem contains the font we are looking for. + foundFont = menuItem; + exactMatch = true; + break; + } else if (editorOptionsCount > 1 && afterUsedFontSection) { + // Once we are in the list of all other available fonts, + // we will find the one that best matches one of the options. + let matchPos = editorFontOptions.indexOf(fontMenuValue); + if (matchPos >= 0 && matchPos < matchedFontIndex) { + // This menu font comes earlier in the list of options, + // so prefer it. + matchedFontIndex = matchPos; + foundFont = menuItem; + // If we matched the first option, we don't need to look for + // a better match. + if (matchPos == 0) { + break; + } + } + } + } else if (menuItem == usedFontsSep) { + // Some other element type. + // We have now passed the section of used fonts and are now in the list of all. + afterUsedFontSection = true; + } + } + + if (foundFont) { + let defaultFontsSep = menuPopup.querySelector( + "menuseparator.fontFaceMenuAfterDefaultFonts" + ); + if (exactMatch) { + if (afterUsedFontSection) { + // Copy the matched font into the section of used fonts. + // We insert after the separator following the default fonts, + // so right at the beginning of the used fonts section. + let copyItem = foundFont.cloneNode(true); + menuPopup.insertBefore(copyItem, defaultFontsSep.nextSibling); + usedFontsSep.hidden = false; + foundFont = copyItem; + foundFont.setAttribute("used", "true"); + } + } else { + // Keep only the found font and generic families in the font string. + editorFont = editorFont + .replace(/, /g, ",") + .split(",") + .filter( + font => + font.toLowerCase() == foundFont.getAttribute("value_parsed") || + genericFamilies.includes(font) + ) + .join(","); + + // Check if such an item is already in the used font section. + if (afterUsedFontSection) { + foundFont = menuPopup.querySelector( + 'menuitem[used="true"][value_parsed="' + + editorFont.toLowerCase() + + '"]' + ); + } + // If not, create a new entry which will be inserted into that section. + if (!foundFont) { + foundFont = createFontFaceMenuitem(editorFont, editorFont, menuPopup); + } + + // Add the editor font string into the 'cache' attribute in the element + // so we can later find it quickly without building the reduced string again. + let fontCache = ""; + if (foundFont.hasAttribute("value_cache")) { + fontCache = foundFont.getAttribute("value_cache"); + } + foundFont.setAttribute( + "value_cache", + fontCache + "|" + editorFontToLower + ); + + // If we created a new item, set it up and insert. + if (!foundFont.hasAttribute("used")) { + foundFont.setAttribute("used", "true"); + usedFontsSep.hidden = false; + menuPopup.insertBefore(foundFont, defaultFontsSep.nextSibling); + } + } + } else { + // The editor encountered a font that is not installed on this system. + // Add it to the font menu now, in the used-fonts section right at the + // bottom before the separator of the section. + let fontLabel = GetFormattedString("NotInstalled", editorFont); + foundFont = createFontFaceMenuitem(fontLabel, editorFont, menuPopup); + foundFont.setAttribute("used", "true"); + usedFontsSep.hidden = false; + menuPopup.insertBefore(foundFont, usedFontsSep); + } + fontFaceMenuList.selectedItem = foundFont; +} + +/** + * Clears the used fonts list from all the font face menulists. + */ +function ClearUsedFonts() { + let userFontSeps = document.querySelectorAll( + "menuseparator.fontFaceMenuAfterDefaultFonts" + ); + for (let userFontSep of userFontSeps) { + while (true) { + let nextNode = userFontSep.nextSibling; + if (nextNode.tagName != "menuseparator") { + nextNode.remove(); + } else if (nextNode.classList.contains("fontFaceMenuAfterUsedFonts")) { + nextNode.hidden = true; + break; + } + } + } +} + +function EditorSelectFontSize() { + var select = document.getElementById("FontSizeSelect"); + if (select) { + if (select.selectedIndex == -1) { + return; + } + + EditorSetFontSize(gFontSizeNames[select.selectedIndex]); + } +} + +function onFontSizeChange(fontSizeMenulist, commandID) { + // If we don't match anything, set to "0 (normal)" + var newIndex = 2; + var size = fontSizeMenulist.getAttribute("size"); + if (size == "mixed") { + // No single type selected + newIndex = -1; + } else { + for (var i = 0; i < gFontSizeNames.length; i++) { + if (gFontSizeNames[i] == size) { + newIndex = i; + break; + } + } + } + if (fontSizeMenulist.selectedIndex != newIndex) { + fontSizeMenulist.selectedIndex = newIndex; + } +} + +function EditorSetFontSize(size) { + if (size == "0" || size == "normal" || size == "medium") { + EditorRemoveTextProperty("font", "size"); + // Also remove big and small, + // else it will seem like size isn't changing correctly + EditorRemoveTextProperty("small", ""); + EditorRemoveTextProperty("big", ""); + } else { + // Temp: convert from new CSS size strings to old HTML size strings + switch (size) { + case "xx-small": + case "x-small": + size = "-2"; + break; + case "small": + size = "-1"; + break; + case "large": + size = "+1"; + break; + case "x-large": + size = "+2"; + break; + case "xx-large": + size = "+3"; + break; + } + EditorSetTextProperty("font", "size", size); + } + gContentWindow.focus(); +} + +function initFontFaceMenu(menuPopup) { + initLocalFontFaceMenu(menuPopup); + + if (menuPopup) { + var children = menuPopup.childNodes; + if (!children) { + return; + } + + var mixed = { value: false }; + var editorFont = GetCurrentEditor().getFontFaceState(mixed); + + // Strip quotes in font names. Experiments have shown that we only + // ever get double quotes around the font name, never single quotes, + // even if they were in the HTML source. Also single or double + // quotes within the font name are never returned. + editorFont = editorFont.replace(/"/g, ""); + + if (!mixed.value) { + switch (editorFont) { + case "": + case "serif": + case "sans-serif": + // Generic variable width. + editorFont = ""; + break; + case "tt": + case "monospace": + // Generic fixed width. + editorFont = "tt"; + break; + default: + editorFont = editorFont.toLowerCase().replace(/, /g, ","); // bug 1139524 + } + } + + var editorFontOptions = editorFont.split(","); + var matchedOption = editorFontOptions.length; // initialise to high invalid value + for (var i = 0; i < children.length; i++) { + var menuItem = children[i]; + if (menuItem.localName == "menuitem") { + var matchFound = false; + if (!mixed.value) { + var menuFont = menuItem + .getAttribute("value") + .toLowerCase() + .replace(/, /g, ","); + + // First compare the entire font string to match items that contain commas. + if (menuFont == editorFont) { + menuItem.setAttribute("checked", "true"); + break; + } else if (editorFontOptions.length > 1) { + // Next compare the individual options. + var matchPos = editorFontOptions.indexOf(menuFont); + if (matchPos >= 0 && matchPos < matchedOption) { + // This menu font comes earlier in the list of options, + // so prefer it. + menuItem.setAttribute("checked", "true"); + + // If we matched the first option, we don't need to look for + // a better match. + if (matchPos == 0) { + break; + } + + matchedOption = matchPos; + matchFound = true; + } + } + } + + // In case this item doesn't match, make sure we've cleared the checkmark. + if (!matchFound) { + menuItem.removeAttribute("checked"); + } + } + } + } +} + +// Number of fixed font face menuitems, these are: +// Variable Width +// Fixed Width +// ==separator +// Helvetica, Arial +// Times +// Courier +// ==separator +// ==separator +const kFixedFontFaceMenuItems = 8; + +function initLocalFontFaceMenu(menuPopup) { + if (!gLocalFonts) { + // Build list of all local fonts once per editor + try { + var enumerator = Cc["@mozilla.org/gfx/fontenumerator;1"].getService( + Ci.nsIFontEnumerator + ); + gLocalFonts = enumerator.EnumerateAllFonts(); + } catch (e) {} + } + + // Don't use radios for menulists. + let useRadioMenuitems = menuPopup.parentNode.localName == "menu"; + menuPopup.setAttribute("useRadios", useRadioMenuitems); + if (menuPopup.childNodes.length == kFixedFontFaceMenuItems) { + if (gLocalFonts.length == 0) { + menuPopup.querySelector(".fontFaceMenuAfterDefaultFonts").hidden = true; + } + for (let i = 0; i < gLocalFonts.length; ++i) { + // Remove Linux system generic fonts that collide with CSS generic fonts. + if ( + gLocalFonts[i] != "" && + gLocalFonts[i] != "serif" && + gLocalFonts[i] != "sans-serif" && + gLocalFonts[i] != "monospace" + ) { + let itemNode = createFontFaceMenuitem( + gLocalFonts[i], + gLocalFonts[i], + menuPopup + ); + menuPopup.appendChild(itemNode); + } + } + } +} + +/** + * Creates a menuitem element for the font faces menulist. Returns the menuitem + * but does not add it automatically to the menupopup. + * + * @param aFontLabel Label to be displayed for the item. + * @param aFontName The font face value to be used for the item. + * Will be used in <font face="value"> in the edited document. + * @param aMenuPopup The menupopup for which this menuitem is created. + */ +function createFontFaceMenuitem(aFontLabel, aFontName, aMenuPopup) { + let itemNode = document.createXULElement("menuitem"); + itemNode.setAttribute("label", aFontLabel); + itemNode.setAttribute("value", aFontName); + itemNode.setAttribute( + "value_parsed", + aFontName.toLowerCase().replace(/, /g, ",") + ); + itemNode.setAttribute("tooltiptext", aFontLabel); + if (aMenuPopup.getAttribute("useRadios") == "true") { + itemNode.setAttribute("type", "radio"); + itemNode.setAttribute("observes", "cmd_renderedHTMLEnabler"); + } + return itemNode; +} + +/** + * Helper function + */ +function getFontSizeIndex() { + var firstHas = { value: false }; + var anyHas = { value: false }; + var allHas = { value: false }; + + var fontSize = EditorGetTextProperty( + "font", + "size", + null, + firstHas, + anyHas, + allHas + ); + + // If the element has no size attribute and no size was found at all, + // we assume "medium" size. This is highly problematic since + // CSS sizes are not recognised and will show as "medium" as well. + // Currently we can't distinguish between "no attribute" which + // can imply "medium" and "CSS attribute present" which should not + // imply "medium". + if (!anyHas.value) { + return 2; + } + + // Mixed selection. + if (!allHas.value) { + return -1; + } + + switch (fontSize) { + case "-3": + case "-2": + case "0": + case "1": + // x-small. + return 0; + case "-1": + case "2": + // small. + return 1; + case "3": + // medium. + return 2; + case "+1": + case "4": + // large. + return 3; + case "+2": + case "5": + // x-large. + return 4; + case "+3": + case "+4": + case "6": + case "7": + // xx-large. + return 5; + } + + // We shouldn't get here. All the selection has a value we don't understand. + return -1; +} + +function initFontSizeMenu(menuPopup, fullMenu) { + if (menuPopup) { + var children = menuPopup.childNodes; + if (!children) { + return; + } + + // Fixed size items start after menu separator depending on whether it is + // a full menu. + var menuIndex = fullMenu ? 3 : 0; + + var setIndex = getFontSizeIndex(); + if (setIndex >= 0) { + children[menuIndex + setIndex].setAttribute("checked", true); + } else { + // In case of mixed, clear all items. + for (var i = menuIndex; i < children.length; i++) { + children[i].setAttribute("checked", false); + } + } + + // Some configurations might not have the "small/big" indicator as + // last item. If there is no indicator, we are done. + if (!menuPopup.lastChild.id.includes("smallBigInfo")) { + return; + } + + // While it would be better to show the number of levels, + // at least this tells user if either of them are set. + var firstHas = { value: false }; + var anyHas = { value: false }; + var allHas = { value: false }; + + // Show "small"/"big" indicator. + var htmlInfo = ""; + EditorGetTextProperty("small", "", "", firstHas, anyHas, allHas); + if (anyHas.value) { + htmlInfo = "<small>"; + } + EditorGetTextProperty("big", "", "", firstHas, anyHas, allHas); + if (anyHas.value) { + htmlInfo += "<big>"; + } + + if (htmlInfo) { + menuPopup.lastChild.hidden = false; + menuPopup.lastChild.setAttribute("label", "HTML: " + htmlInfo); + menuPopup.lastChild.setAttribute("checked", true); + } else { + menuPopup.lastChild.hidden = true; + } + } +} + +function onHighlightColorChange() { + ChangeButtonColor("cmd_highlight", "HighlightColorButton", "transparent"); +} + +function onFontColorChange() { + ChangeButtonColor("cmd_fontColor", "TextColorButton", gDefaultTextColor); +} + +function onBackgroundColorChange() { + ChangeButtonColor( + "cmd_backgroundColor", + "BackgroundColorButton", + gDefaultBackgroundColor + ); +} + +/* Helper function that changes the button color. + * commandID - The ID of the command element. + * id - The ID of the button needing to be changed. + * defaultColor - The default color the button gets set to. + */ +function ChangeButtonColor(commandID, id, defaultColor) { + var commandNode = document.getElementById(commandID); + if (commandNode) { + var color = commandNode.getAttribute("state"); + var button = document.getElementById(id); + if (button) { + button.setAttribute("color", color); + + // No color or a mixed color - get color set on page or other defaults. + if (!color || color == "mixed") { + color = defaultColor; + } + + button.setAttribute("style", "background-color:" + color + " !important"); + } + } +} + +// Call this when user changes text and/or background colors of the page +function UpdateDefaultColors() { + var BrowserColors = GetDefaultBrowserColors(); + var bodyelement = GetBodyElement(); + var defTextColor = gDefaultTextColor; + var defBackColor = gDefaultBackgroundColor; + + if (bodyelement) { + var color = bodyelement.getAttribute("text"); + if (color) { + gDefaultTextColor = color; + } else if (BrowserColors) { + gDefaultTextColor = BrowserColors.TextColor; + } + + color = bodyelement.getAttribute("bgcolor"); + if (color) { + gDefaultBackgroundColor = color; + } else if (BrowserColors) { + gDefaultBackgroundColor = BrowserColors.BackgroundColor; + } + } + + // Trigger update on toolbar + if (defTextColor != gDefaultTextColor) { + goUpdateCommandState("cmd_fontColor"); + onFontColorChange(); + } + if (defBackColor != gDefaultBackgroundColor) { + goUpdateCommandState("cmd_backgroundColor"); + onBackgroundColorChange(); + } +} + +function GetBackgroundElementWithColor() { + var editor = GetCurrentTableEditor(); + if (!editor) { + return null; + } + + gColorObj.Type = ""; + gColorObj.PageColor = ""; + gColorObj.TableColor = ""; + gColorObj.CellColor = ""; + gColorObj.BackgroundColor = ""; + gColorObj.SelectedType = ""; + + var tagNameObj = { value: "" }; + var element; + try { + element = editor.getSelectedOrParentTableElement(tagNameObj, { value: 0 }); + } catch (e) {} + + if (element && tagNameObj && tagNameObj.value) { + gColorObj.BackgroundColor = GetHTMLOrCSSStyleValue( + element, + "bgcolor", + "background-color" + ); + gColorObj.BackgroundColor = ConvertRGBColorIntoHEXColor( + gColorObj.BackgroundColor + ); + if (tagNameObj.value.toLowerCase() == "td") { + gColorObj.Type = "Cell"; + gColorObj.CellColor = gColorObj.BackgroundColor; + + // Get any color that might be on parent table + var table = GetParentTable(element); + gColorObj.TableColor = GetHTMLOrCSSStyleValue( + table, + "bgcolor", + "background-color" + ); + gColorObj.TableColor = ConvertRGBColorIntoHEXColor(gColorObj.TableColor); + } else { + gColorObj.Type = "Table"; + gColorObj.TableColor = gColorObj.BackgroundColor; + } + gColorObj.SelectedType = gColorObj.Type; + } else { + let IsCSSPrefChecked = Services.prefs.getBoolPref(kUseCssPref); + if (IsCSSPrefChecked && IsHTMLEditor()) { + let selection = editor.selection; + if (selection) { + element = selection.focusNode; + while (!editor.nodeIsBlock(element)) { + element = element.parentNode; + } + } else { + element = GetBodyElement(); + } + } else { + element = GetBodyElement(); + } + if (element) { + gColorObj.Type = "Page"; + gColorObj.BackgroundColor = GetHTMLOrCSSStyleValue( + element, + "bgcolor", + "background-color" + ); + if (gColorObj.BackgroundColor == "") { + gColorObj.BackgroundColor = "transparent"; + } else { + gColorObj.BackgroundColor = ConvertRGBColorIntoHEXColor( + gColorObj.BackgroundColor + ); + } + gColorObj.PageColor = gColorObj.BackgroundColor; + } + } + return element; +} + +function SetSmiley(smileyText) { + try { + GetCurrentEditor().insertText(smileyText); + gContentWindow.focus(); + } catch (e) {} +} + +/* eslint-disable complexity */ +function EditorSelectColor(colorType, mouseEvent) { + var editor = GetCurrentEditor(); + if (!editor || !gColorObj) { + return; + } + + // Shift + mouse click automatically applies last color, if available + var useLastColor = mouseEvent + ? mouseEvent.button == 0 && mouseEvent.shiftKey + : false; + var element; + var table; + var currentColor = ""; + var commandNode; + + if (!colorType) { + colorType = ""; + } + + if (colorType == "Text") { + gColorObj.Type = colorType; + + // Get color from command node state + commandNode = document.getElementById("cmd_fontColor"); + currentColor = commandNode.getAttribute("state"); + currentColor = ConvertRGBColorIntoHEXColor(currentColor); + gColorObj.TextColor = currentColor; + + if (useLastColor && gColorObj.LastTextColor) { + gColorObj.TextColor = gColorObj.LastTextColor; + } else { + useLastColor = false; + } + } else if (colorType == "Highlight") { + gColorObj.Type = colorType; + + // Get color from command node state + commandNode = document.getElementById("cmd_highlight"); + currentColor = commandNode.getAttribute("state"); + currentColor = ConvertRGBColorIntoHEXColor(currentColor); + gColorObj.HighlightColor = currentColor; + + if (useLastColor && gColorObj.LastHighlightColor) { + gColorObj.HighlightColor = gColorObj.LastHighlightColor; + } else { + useLastColor = false; + } + } else { + element = GetBackgroundElementWithColor(); + if (!element) { + return; + } + + // Get the table if we found a cell + if (gColorObj.Type == "Table") { + table = element; + } else if (gColorObj.Type == "Cell") { + table = GetParentTable(element); + } + + // Save to avoid resetting if not necessary + currentColor = gColorObj.BackgroundColor; + + if (colorType == "TableOrCell" || colorType == "Cell") { + if (gColorObj.Type == "Cell") { + gColorObj.Type = colorType; + } else if (gColorObj.Type != "Table") { + return; + } + } else if (colorType == "Table" && gColorObj.Type == "Page") { + return; + } + + if (colorType == "" && gColorObj.Type == "Cell") { + // Using empty string for requested type means + // we can let user select cell or table + gColorObj.Type = "TableOrCell"; + } + + if (useLastColor && gColorObj.LastBackgroundColor) { + gColorObj.BackgroundColor = gColorObj.LastBackgroundColor; + } else { + useLastColor = false; + } + } + // Save the type we are really requesting + colorType = gColorObj.Type; + + if (!useLastColor) { + // Avoid the JS warning + gColorObj.NoDefault = false; + + // Launch the ColorPicker dialog + // TODO: Figure out how to position this under the color buttons on the toolbar + window.openDialog( + "chrome://editor/content/EdColorPicker.xhtml", + "_blank", + "chrome,close,titlebar,modal", + "", + gColorObj + ); + + // User canceled the dialog + if (gColorObj.Cancel) { + return; + } + } + + if (gColorObj.Type == "Text") { + if (currentColor != gColorObj.TextColor) { + if (gColorObj.TextColor) { + EditorSetTextProperty("font", "color", gColorObj.TextColor); + } else { + EditorRemoveTextProperty("font", "color"); + } + } + // Update the command state (this will trigger color button update) + goUpdateCommandState("cmd_fontColor"); + } else if (gColorObj.Type == "Highlight") { + if (currentColor != gColorObj.HighlightColor) { + if (gColorObj.HighlightColor) { + EditorSetTextProperty("font", "bgcolor", gColorObj.HighlightColor); + } else { + EditorRemoveTextProperty("font", "bgcolor"); + } + } + // Update the command state (this will trigger color button update) + goUpdateCommandState("cmd_highlight"); + } else if (element) { + if (gColorObj.Type == "Table") { + // Set background on a table + // Note that we shouldn't trust "currentColor" because of "TableOrCell" behavior + if (table) { + var bgcolor = table.getAttribute("bgcolor"); + if (bgcolor != gColorObj.BackgroundColor) { + try { + if (gColorObj.BackgroundColor) { + editor.setAttributeOrEquivalent( + table, + "bgcolor", + gColorObj.BackgroundColor, + false + ); + } else { + editor.removeAttributeOrEquivalent(table, "bgcolor", false); + } + } catch (e) {} + } + } + } else if (currentColor != gColorObj.BackgroundColor && IsHTMLEditor()) { + editor.beginTransaction(); + try { + editor.setBackgroundColor(gColorObj.BackgroundColor); + + if (gColorObj.Type == "Page" && gColorObj.BackgroundColor) { + // Set all page colors not explicitly set, + // else you can end up with unreadable pages + // because viewer's default colors may not be same as page author's + var bodyelement = GetBodyElement(); + if (bodyelement) { + var defColors = GetDefaultBrowserColors(); + if (defColors) { + if (!bodyelement.getAttribute("text")) { + editor.setAttributeOrEquivalent( + bodyelement, + "text", + defColors.TextColor, + false + ); + } + + // The following attributes have no individual CSS declaration counterparts + // Getting rid of them in favor of CSS implies CSS rules management + if (!bodyelement.getAttribute("link")) { + editor.setAttribute(bodyelement, "link", defColors.LinkColor); + } + + if (!bodyelement.getAttribute("alink")) { + editor.setAttribute( + bodyelement, + "alink", + defColors.ActiveLinkColor + ); + } + + if (!bodyelement.getAttribute("vlink")) { + editor.setAttribute( + bodyelement, + "vlink", + defColors.VisitedLinkColor + ); + } + } + } + } + } catch (e) {} + + editor.endTransaction(); + } + + goUpdateCommandState("cmd_backgroundColor"); + } + gContentWindow.focus(); +} +/* eslint-enable complexity */ + +function GetParentTable(element) { + var node = element; + while (node) { + if (node.nodeName.toLowerCase() == "table") { + return node; + } + + node = node.parentNode; + } + return node; +} + +function GetParentTableCell(element) { + var node = element; + while (node) { + if ( + node.nodeName.toLowerCase() == "td" || + node.nodeName.toLowerCase() == "th" + ) { + return node; + } + + node = node.parentNode; + } + return node; +} + +function EditorDblClick(event) { + // We check event.explicitOriginalTarget here because .target will never + // be a textnode (bug 193689) + if (event.explicitOriginalTarget) { + // Only bring up properties if clicked on an element or selected link + var element; + try { + element = event.explicitOriginalTarget; + } catch (e) {} + + // We use "href" instead of "a" to not be fooled by named anchor + if (!element) { + try { + element = GetCurrentEditor().getSelectedElement("href"); + } catch (e) {} + } + + // Don't fire for body/p and other block elements. + // It's common that people try to double-click + // to select a word, but the click hits an empty area. + if ( + element && + ![ + "body", + "p", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "blockquote", + "div", + "pre", + ].includes(element.nodeName.toLowerCase()) + ) { + goDoCommand("cmd_objectProperties"); + event.preventDefault(); + } + } +} + +function EditorClick(event) { + // For Web Composer: In Show All Tags Mode, + // single click selects entire element, + // except for body and table elements + if (gEditorDisplayMode == kDisplayModeAllTags) { + try { + // We check event.explicitOriginalTarget here because .target will never + // be a textnode (bug 193689) + var element = event.explicitOriginalTarget; + var name = element.localName; + if (!["body", "caption", "table", "td", "th", "tr"].includes(name)) { + GetCurrentEditor().selectElement(event.explicitOriginalTarget); + event.preventDefault(); + } + } catch (e) {} + } +} + +/* TODO: We need an oncreate hook to do enabling/disabling for the + Format menu. There should be code like this for the + object-specific "Properties" item +*/ +// For property dialogs, we want the selected element, +// but will accept a parent link, list, or table cell if inside one +function GetObjectForProperties() { + var editor = GetCurrentEditor(); + if (!editor || !IsHTMLEditor()) { + return null; + } + + var element; + try { + element = editor.getSelectedElement(""); + } catch (e) {} + if (element) { + if (element.namespaceURI == "http://www.w3.org/1998/Math/MathML") { + // If the object is a MathML element, we collapse the selection on it and + // we return its <math> ancestor. Hence the math dialog will be used. + GetCurrentEditor().selection.collapse(element, 0); + } else { + return element; + } + } + + // Find nearest parent of selection anchor node + // that is a link, list, table cell, or table + + var anchorNode; + var node; + try { + anchorNode = editor.selection.anchorNode; + if (anchorNode.firstChild) { + // Start at actual selected node + var offset = editor.selection.anchorOffset; + // Note: If collapsed, offset points to element AFTER caret, + // thus node may be null + node = anchorNode.childNodes.item(offset); + } + if (!node) { + node = anchorNode; + } + } catch (e) {} + + while (node) { + if (node.nodeName) { + var nodeName = node.nodeName.toLowerCase(); + + // Done when we hit the body or #text. + if (nodeName == "body" || nodeName == "#text") { + break; + } + + if ( + (nodeName == "a" && node.href) || + nodeName == "ol" || + nodeName == "ul" || + nodeName == "dl" || + nodeName == "td" || + nodeName == "th" || + nodeName == "table" || + nodeName == "math" + ) { + return node; + } + } + node = node.parentNode; + } + return null; +} + +function SetEditMode(mode) { + if (!IsHTMLEditor()) { + return; + } + + var bodyElement = GetBodyElement(); + if (!bodyElement) { + dump("SetEditMode: We don't have a body node!\n"); + return; + } + + // must have editor if here! + var editor = GetCurrentEditor(); + var inlineSpellCheckItem = document.getElementById("menu_inlineSpellCheck"); + + // Switch the UI mode before inserting contents + // so user can't type in source window while new window is being filled + var previousMode = gEditorDisplayMode; + if (!SetDisplayMode(mode)) { + return; + } + + if (mode == kDisplayModeSource) { + // Display the DOCTYPE as a non-editable string above edit area + var domdoc; + try { + domdoc = editor.document; + } catch (e) { + dump(e + "\n"); + } + if (domdoc) { + var doctypeNode = document.getElementById("doctype-text"); + var dt = domdoc.doctype; + if (doctypeNode) { + if (dt) { + doctypeNode.collapsed = false; + var doctypeText = "<!DOCTYPE " + domdoc.doctype.name; + if (dt.publicId) { + doctypeText += ' PUBLIC "' + domdoc.doctype.publicId; + } + if (dt.systemId) { + doctypeText += ' "' + dt.systemId; + } + doctypeText += '">'; + doctypeNode.setAttribute("value", doctypeText); + } else { + doctypeNode.collapsed = true; + } + } + } + // Get the entire document's source string + + var flags = + editor.documentCharacterSet == "ISO-8859-1" + ? kOutputEncodeLatin1Entities + : kOutputEncodeBasicEntities; + try { + let encodeEntity = Services.prefs.getCharPref("editor.encode_entity"); + switch (encodeEntity) { + case "basic": + flags = kOutputEncodeBasicEntities; + break; + case "latin1": + flags = kOutputEncodeLatin1Entities; + break; + case "html": + flags = kOutputEncodeHTMLEntities; + break; + case "none": + flags = 0; + break; + } + } catch (e) {} + + if (Services.prefs.getBoolPref("editor.prettyprint")) { + flags |= kOutputFormatted; + } + + flags |= kOutputLFLineBreak; + var source = editor.outputToString(editor.contentsMIMEType, flags); + var start = source.search(/<html/i); + if (start == -1) { + start = 0; + } + gSourceTextEditor.insertText(source.slice(start)); + gSourceTextEditor.resetModificationCount(); + gSourceTextEditor.addDocumentStateListener(gSourceTextListener); + gSourceTextEditor.enableUndo(true); + gSourceContentWindow.commandManager.addCommandObserver( + gSourceTextObserver, + "cmd_undo" + ); + gSourceContentWindow.contentWindow.focus(); + goDoCommand("cmd_moveTop"); + } else if (previousMode == kDisplayModeSource) { + // Only rebuild document if a change was made in source window + if (IsHTMLSourceChanged()) { + // Disable spell checking when rebuilding source + InlineSpellCheckerUI.enabled = false; + inlineSpellCheckItem.removeAttribute("checked"); + + // Reduce the undo count so we don't use too much memory + // during multiple uses of source window + // (reinserting entire doc caches all nodes) + editor.clearUndoRedo(); + + editor.beginTransaction(); + try { + // We are coming from edit source mode, + // so transfer that back into the document + source = gSourceTextEditor + .outputToString(kTextMimeType, kOutputLFLineBreak) + .trim(); + if (editor.contentsMIMEType != kXHTMLMimeType) { + editor.rebuildDocumentFromSource(source); // This is undoable + } else { + /* eslint-disable-next-line no-unsanitized/method */ + var fragment = editor.document + .createRange() + .createContextualFragment(source); + GetBodyElement().remove(); + editor.document.replaceChild( + fragment.firstChild, + editor.document.documentElement + ); + // We touched the DOM tree without a transaction here so that we + // broke undoable transactions. However, we cleared all undoable + // things above. Therefore nothing must be in the undo stack. + } + + // Get the text for the <title> from the newly-parsed document + // (must do this for proper conversion of "escaped" characters) + let titleNode = editor.document.querySelector("title"); + SetDocumentTitle(titleNode ? titleNode.textContent : ""); + } catch (ex) { + dump(ex); + } + // If the MIME type is kXHTMLMimeType, we don't put any undoable + // transaction. Then, this endTransaction() call does not allow to + // live empty transaction. Therefore, the unnecessary empty transaction + // will be cleared here automatically. + editor.endTransaction(); + } + + // Clear out the string buffers + gSourceContentWindow.commandManager.removeCommandObserver( + gSourceTextObserver, + "cmd_undo" + ); + gSourceTextEditor.removeDocumentStateListener(gSourceTextListener); + gSourceTextEditor.enableUndo(false); + gSourceTextEditor.selectAll(); + gSourceTextEditor.deleteSelection( + gSourceTextEditor.eNone, + gSourceTextEditor.eStrip + ); + gSourceTextEditor.resetModificationCount(); + + gContentWindow.focus(); + // goDoCommand("cmd_moveTop"); + } + + switch (mode) { + case kDisplayModePreview: + // Disable spell checking when previewing + InlineSpellCheckerUI.enabled = false; + inlineSpellCheckItem.removeAttribute("checked"); + inlineSpellCheckItem.setAttribute("disabled", true); + break; + case kDisplayModeSource: + inlineSpellCheckItem.setAttribute("disabled", true); + goSetCommandEnabled("cmd_pasteQuote", false); + break; + default: + inlineSpellCheckItem.setAttribute( + "disabled", + !InlineSpellCheckerUI.canSpellCheck + ); + break; + } +} + +function CancelHTMLSource() { + // Don't convert source text back into the DOM document + gSourceTextEditor.resetModificationCount(); + SetDisplayMode(gPreviousNonSourceDisplayMode); +} + +function SetDisplayMode(mode) { + if (!IsHTMLEditor()) { + return false; + } + + // Already in requested mode: + // return false to indicate we didn't switch + if (mode == gEditorDisplayMode) { + return false; + } + + var previousMode = gEditorDisplayMode; + gEditorDisplayMode = mode; + + ResetStructToolbar(); + if (mode == kDisplayModeSource) { + // Switch to the sourceWindow (second in the deck) + gContentWindowDeck.selectedIndex = 1; + + // Hide the formatting toolbar if not already hidden + gFormatToolbarHidden = gFormatToolbar.hidden; + gFormatToolbar.hidden = true; + gFormatToolbar.setAttribute("hideinmenu", "true"); + + gSourceContentWindow.contentWindow.focus(); + } else { + // Save the last non-source mode so we can cancel source editing easily + gPreviousNonSourceDisplayMode = mode; + + // Load/unload appropriate override style sheet + try { + var editor = GetCurrentEditor(); + editor.QueryInterface(nsIEditorStyleSheets); + editor instanceof Ci.nsIHTMLObjectResizer; + + switch (mode) { + case kDisplayModePreview: + // Disable all extra "edit mode" style sheets + editor.enableStyleSheet(kNormalStyleSheet, false); + editor.enableStyleSheet(kAllTagsStyleSheet, false); + editor.objectResizingEnabled = true; + break; + + case kDisplayModeNormal: + editor.addOverrideStyleSheet(kNormalStyleSheet); + // Disable ShowAllTags mode + editor.enableStyleSheet(kAllTagsStyleSheet, false); + editor.objectResizingEnabled = true; + break; + + case kDisplayModeAllTags: + editor.addOverrideStyleSheet(kNormalStyleSheet); + editor.addOverrideStyleSheet(kAllTagsStyleSheet); + // don't allow resizing in AllTags mode because the visible tags + // change the computed size of images and tables... + if (editor.resizedObject) { + editor.hideResizers(); + } + editor.objectResizingEnabled = false; + break; + } + } catch (e) {} + + // Switch to the normal editor (first in the deck) + gContentWindowDeck.selectedIndex = 0; + + // Restore menus and toolbars + if (previousMode == kDisplayModeSource) { + gFormatToolbar.hidden = gFormatToolbarHidden; + gFormatToolbar.removeAttribute("hideinmenu"); + } + + gContentWindow.focus(); + } + + // update commands to disable or re-enable stuff + window.updateCommands("mode_switch"); + + // Set the selected tab at bottom of window: + // (Note: Setting "selectedIndex = mode" won't redraw tabs when menu is used.) + document.getElementById( + "EditModeTabs" + ).selectedItem = document.getElementById(kDisplayModeTabIDS[mode]); + + // Uncheck previous menuitem and set new check since toolbar may have been used + if (previousMode >= 0) { + document + .getElementById(kDisplayModeMenuIDs[previousMode]) + .setAttribute("checked", "false"); + } + document + .getElementById(kDisplayModeMenuIDs[mode]) + .setAttribute("checked", "true"); + + return true; +} + +function UpdateWindowTitle() { + try { + var filename = ""; + var windowTitle = ""; + var title = GetDocumentTitle(); + + // Append just the 'leaf' filename to the Doc. Title for the window caption + var docUrl = GetDocumentUrl(); + if (docUrl && !IsUrlAboutBlank(docUrl)) { + var scheme = GetScheme(docUrl); + filename = GetFilename(docUrl); + if (filename) { + windowTitle = " [" + scheme + ":/.../" + filename + "]"; + } + + var fileType = IsHTMLEditor() ? "html" : "text"; + // Save changed title in the recent pages data in prefs + SaveRecentFilesPrefs(title, fileType); + } + + // Set window title with " - Composer" or " - Text Editor" appended. + var xulWin = document.documentElement; + + document.title = + (title || filename || window.gUntitledString) + + windowTitle + + xulWin.getAttribute("titlemenuseparator") + + xulWin.getAttribute("titlemodifier"); + } catch (e) { + dump(e); + } +} + +function SaveRecentFilesPrefs(aTitle, aFileType) { + var curUrl = StripPassword(GetDocumentUrl()); + var historyCount = Services.prefs.getIntPref("editor.history.url_maximum"); + + var titleArray = []; + var urlArray = []; + var typeArray = []; + + if (historyCount && !IsUrlAboutBlank(curUrl) && GetScheme(curUrl) != "data") { + titleArray.push(aTitle); + urlArray.push(curUrl); + typeArray.push(aFileType); + } + + for (let i = 0; i < historyCount && urlArray.length < historyCount; i++) { + let url = Services.prefs.getStringPref("editor.history_url_" + i, ""); + + // Continue if URL pref is missing because + // a URL not found during loading may have been removed + + // Skip over current an "data" URLs + if (url && url != curUrl && GetScheme(url) != "data") { + let title = Services.prefs.getStringPref("editor.history_title_" + i, ""); + let fileType = Services.prefs.getStringPref( + "editor.history_type_" + i, + "" + ); + titleArray.push(title); + urlArray.push(url); + typeArray.push(fileType); + } + } + + // Resave the list back to prefs in the new order + for (let i = 0; i < urlArray.length; i++) { + SetStringPref("editor.history_title_" + i, titleArray[i]); + SetStringPref("editor.history_url_" + i, urlArray[i]); + SetStringPref("editor.history_type_" + i, typeArray[i]); + } +} + +function EditorInitFormatMenu() { + try { + InitObjectPropertiesMenuitem(); + InitRemoveStylesMenuitems( + "removeStylesMenuitem", + "removeLinksMenuitem", + "removeNamedAnchorsMenuitem" + ); + } catch (ex) {} +} + +function InitObjectPropertiesMenuitem() { + // Set strings and enable for the [Object] Properties item + // Note that we directly do the enabling instead of + // using goSetCommandEnabled since we already have the command. + var cmd = document.getElementById("cmd_objectProperties"); + if (!cmd) { + return null; + } + + var element; + var menuStr = GetString("AdvancedProperties"); + var name; + + if (IsEditingRenderedHTML()) { + element = GetObjectForProperties(); + } + + if (element && element.nodeName) { + var objStr = ""; + cmd.removeAttribute("disabled"); + name = element.nodeName.toLowerCase(); + switch (name) { + case "img": + // Check if img is enclosed in link + // (use "href" to not be fooled by named anchor) + try { + if (GetCurrentEditor().getElementOrParentByTagName("href", element)) { + objStr = GetString("ImageAndLink"); + // Return "href" so it is detected as a link. + name = "href"; + } + } catch (e) {} + + if (objStr == "") { + objStr = GetString("Image"); + } + break; + case "hr": + objStr = GetString("HLine"); + break; + case "table": + objStr = GetString("Table"); + break; + case "th": + name = "td"; + // Falls through + case "td": + objStr = GetString("TableCell"); + break; + case "ol": + case "ul": + case "dl": + objStr = GetString("List"); + break; + case "li": + objStr = GetString("ListItem"); + break; + case "form": + objStr = GetString("Form"); + break; + case "input": + var type = element.getAttribute("type"); + if (type && type.toLowerCase() == "image") { + objStr = GetString("InputImage"); + } else { + objStr = GetString("InputTag"); + } + break; + case "textarea": + objStr = GetString("TextArea"); + break; + case "select": + objStr = GetString("Select"); + break; + case "button": + objStr = GetString("Button"); + break; + case "label": + objStr = GetString("Label"); + break; + case "fieldset": + objStr = GetString("FieldSet"); + break; + case "a": + if (element.name) { + objStr = GetString("NamedAnchor"); + name = "anchor"; + } else if (element.href) { + objStr = GetString("Link"); + name = "href"; + } + break; + } + if (objStr) { + menuStr = GetString("ObjectProperties").replace(/%obj%/, objStr); + } + } else { + // We show generic "Properties" string, but disable the command. + cmd.setAttribute("disabled", "true"); + } + cmd.setAttribute("label", menuStr); + cmd.setAttribute("accesskey", GetString("ObjectPropertiesAccessKey")); + return name; +} + +function InitParagraphMenu() { + var mixedObj = { value: null }; + var state; + try { + state = GetCurrentEditor().getParagraphState(mixedObj); + } catch (e) {} + var IDSuffix; + + // PROBLEM: When we get blockquote, it masks other styles contained by it + // We need a separate method to get blockquote state + + // We use "x" as uninitialized paragraph state + if (!state || state == "x") { + // No paragraph container. + IDSuffix = "bodyText"; + } else { + IDSuffix = state; + } + + // Set "radio" check on one item, but... + var menuItem = document.getElementById("menu_" + IDSuffix); + menuItem.setAttribute("checked", "true"); + + // ..."bodyText" is returned if mixed selection, so remove checkmark + if (mixedObj.value) { + menuItem.setAttribute("checked", "false"); + } +} + +function GetListStateString() { + try { + var editor = GetCurrentEditor(); + + var mixedObj = { value: null }; + var hasOL = { value: false }; + var hasUL = { value: false }; + var hasDL = { value: false }; + editor.getListState(mixedObj, hasOL, hasUL, hasDL); + + if (mixedObj.value) { + return "mixed"; + } + if (hasOL.value) { + return "ol"; + } + if (hasUL.value) { + return "ul"; + } + + if (hasDL.value) { + var hasLI = { value: false }; + var hasDT = { value: false }; + var hasDD = { value: false }; + editor.getListItemState(mixedObj, hasLI, hasDT, hasDD); + if (mixedObj.value) { + return "mixed"; + } + if (hasLI.value) { + return "li"; + } + if (hasDT.value) { + return "dt"; + } + if (hasDD.value) { + return "dd"; + } + } + } catch (e) {} + + // return "noList" if we aren't in a list at all + return "noList"; +} + +function InitListMenu() { + if (!IsHTMLEditor()) { + return; + } + + var IDSuffix = GetListStateString(); + + // Set enable state for the "None" menuitem + goSetCommandEnabled("cmd_removeList", IDSuffix != "noList"); + + // Set "radio" check on one item, but... + // we won't find a match if it's "mixed" + var menuItem = document.getElementById("menu_" + IDSuffix); + if (menuItem) { + menuItem.setAttribute("checked", "true"); + } +} + +function GetAlignmentString() { + var mixedObj = { value: null }; + var alignObj = { value: null }; + try { + GetCurrentEditor().getAlignment(mixedObj, alignObj); + } catch (e) {} + + if (mixedObj.value) { + return "mixed"; + } + if (alignObj.value == nsIHTMLEditor.eLeft) { + return "left"; + } + if (alignObj.value == nsIHTMLEditor.eCenter) { + return "center"; + } + if (alignObj.value == nsIHTMLEditor.eRight) { + return "right"; + } + if (alignObj.value == nsIHTMLEditor.eJustify) { + return "justify"; + } + + // return "left" if we got here + return "left"; +} + +function InitAlignMenu() { + if (!IsHTMLEditor()) { + return; + } + + var IDSuffix = GetAlignmentString(); + + // we won't find a match if it's "mixed" + var menuItem = document.getElementById("menu_" + IDSuffix); + if (menuItem) { + menuItem.setAttribute("checked", "true"); + } +} + +function EditorSetDefaultPrefsAndDoctype() { + var editor = GetCurrentEditor(); + + var domdoc; + try { + domdoc = editor.document; + } catch (e) { + dump(e + "\n"); + } + if (!domdoc) { + dump("EditorSetDefaultPrefsAndDoctype: EDITOR DOCUMENT NOT FOUND\n"); + return; + } + + // Insert a doctype element + // if it is missing from existing doc + if (!domdoc.doctype) { + var newdoctype = domdoc.implementation.createDocumentType( + "HTML", + "-//W3C//DTD HTML 4.01 Transitional//EN", + "" + ); + if (newdoctype) { + domdoc.insertBefore(newdoctype, domdoc.firstChild); + } + } + + // search for head; we'll need this for meta tag additions + let headelement = domdoc.querySelector("head"); + if (!headelement) { + headelement = domdoc.createElement("head"); + if (headelement) { + domdoc.insertAfter(headelement, domdoc.firstChild); + } + } + + /* only set default prefs for new documents */ + if (!IsUrlAboutBlank(GetDocumentUrl())) { + return; + } + + // search for author meta tag. + // if one is found, don't do anything. + // if not, create one and make it a child of the head tag + // and set its content attribute to the value of the editor.author preference. + + if (domdoc.querySelector("meta")) { + // we should do charset first since we need to have charset before + // hitting other 8-bit char in other meta tags + // grab charset pref and make it the default charset + var element; + var prefCharsetString = Services.prefs.getCharPref( + "intl.charset.fallback.override" + ); + if (prefCharsetString) { + editor.documentCharacterSet = prefCharsetString; + } + + // let's start by assuming we have an author in case we don't have the pref + + var prefAuthorString = null; + let authorFound = domdoc.querySelector('meta[name="author"]'); + try { + prefAuthorString = Services.prefs.getStringPref("editor.author"); + } catch (ex) {} + if ( + prefAuthorString && + prefAuthorString != 0 && + !authorFound && + headelement + ) { + // create meta tag with 2 attributes + element = domdoc.createElement("meta"); + if (element) { + element.setAttribute("name", "author"); + element.setAttribute("content", prefAuthorString); + headelement.appendChild(element); + } + } + } + + // add title tag if not present + if (headelement && !editor.document.querySelector("title")) { + var titleElement = domdoc.createElement("title"); + if (titleElement) { + headelement.appendChild(titleElement); + } + } + + // find body node + var bodyelement = GetBodyElement(); + if (bodyelement) { + if (Services.prefs.getBoolPref("editor.use_custom_colors")) { + let text_color = Services.prefs.getCharPref("editor.text_color"); + let background_color = Services.prefs.getCharPref( + "editor.background_color" + ); + + // add the color attributes to the body tag. + // and use them for the default text and background colors if not empty + editor.setAttributeOrEquivalent(bodyelement, "text", text_color, true); + gDefaultTextColor = text_color; + editor.setAttributeOrEquivalent( + bodyelement, + "bgcolor", + background_color, + true + ); + gDefaultBackgroundColor = background_color; + bodyelement.setAttribute( + "link", + Services.prefs.getCharPref("editor.link_color") + ); + bodyelement.setAttribute( + "alink", + Services.prefs.getCharPref("editor.active_link_color") + ); + bodyelement.setAttribute( + "vlink", + Services.prefs.getCharPref("editor.followed_link_color") + ); + } + // Default image is independent of Custom colors??? + try { + let background_image = Services.prefs.getCharPref( + "editor.default_background_image" + ); + if (background_image) { + editor.setAttributeOrEquivalent( + bodyelement, + "background", + background_image, + true + ); + } + } catch (e) { + dump("BACKGROUND EXCEPTION: " + e + "\n"); + } + } + // auto-save??? +} + +function GetBodyElement() { + try { + return GetCurrentEditor().rootElement; + } catch (ex) { + dump("no body tag found?!\n"); + // better have one, how can we blow things up here? + } + return null; +} + +// --------------------------- Logging stuff --------------------------- + +function EditorGetNodeFromOffsets(offsets) { + var node = null; + try { + node = GetCurrentEditor().document; + + for (var i = 0; i < offsets.length; i++) { + node = node.childNodes[offsets[i]]; + } + } catch (e) {} + return node; +} + +function EditorSetSelectionFromOffsets(selRanges) { + try { + var editor = GetCurrentEditor(); + var selection = editor.selection; + selection.removeAllRanges(); + + var rangeArr, start, end, node, offset; + for (var i = 0; i < selRanges.length; i++) { + rangeArr = selRanges[i]; + start = rangeArr[0]; + end = rangeArr[1]; + + var range = editor.document.createRange(); + + node = EditorGetNodeFromOffsets(start[0]); + offset = start[1]; + + range.setStart(node, offset); + + node = EditorGetNodeFromOffsets(end[0]); + offset = end[1]; + + range.setEnd(node, offset); + + selection.addRange(range); + } + } catch (e) {} +} + +// -------------------------------------------------------------------- +function initFontStyleMenu(menuPopup) { + for (var i = 0; i < menuPopup.childNodes.length; i++) { + var menuItem = menuPopup.childNodes[i]; + var theStyle = menuItem.getAttribute("state"); + if (theStyle) { + menuItem.setAttribute("checked", theStyle); + } + } +} + +// -------------------------------------------------------------------- +function onButtonUpdate(button, commmandID) { + var commandNode = document.getElementById(commmandID); + var state = commandNode.getAttribute("state"); + button.checked = state == "true"; +} + +// -------------------------------------------------------------------- +function onStateButtonUpdate(button, commmandID, onState) { + var commandNode = document.getElementById(commmandID); + var state = commandNode.getAttribute("state"); + + button.checked = state == onState; +} + +// --------------------------- Status calls --------------------------- +function getColorAndSetColorWell(ColorPickerID, ColorWellID) { + var colorWell; + if (ColorWellID) { + colorWell = document.getElementById(ColorWellID); + } + + var colorPicker = document.getElementById(ColorPickerID); + if (colorPicker) { + // Extract color from colorPicker and assign to colorWell. + var color = colorPicker.getAttribute("color"); + + if (colorWell && color) { + // Use setAttribute so colorwell can be a XUL element, such as button + colorWell.setAttribute("style", "background-color: " + color); + } + } + return color; +} + +// ----------------------------------------------------------------------------------- +function IsSpellCheckerInstalled() { + return true; // Always installed. +} + +// ----------------------------------------------------------------------------------- +function IsFindInstalled() { + return ( + "@mozilla.org/embedcomp/rangefind;1" in Cc && + "@mozilla.org/find/find_service;1" in Cc + ); +} + +// ----------------------------------------------------------------------------------- +function RemoveInapplicableUIElements() { + // For items that are in their own menu block, remove associated separator + // (we can't use "hidden" since class="hide-in-IM" CSS rule interferes) + + // if no find, remove find ui + if (!IsFindInstalled()) { + HideItem("menu_find"); + HideItem("menu_findnext"); + HideItem("menu_replace"); + HideItem("menu_find"); + RemoveItem("sep_find"); + } + + // if no spell checker, remove spell checker ui + if (!IsSpellCheckerInstalled()) { + HideItem("spellingButton"); + HideItem("menu_checkspelling"); + RemoveItem("sep_checkspelling"); + } + + // Remove menu items (from overlay shared with HTML editor) in non-HTML. + if (!IsHTMLEditor()) { + HideItem("insertAnchor"); + HideItem("insertImage"); + HideItem("insertHline"); + HideItem("insertTable"); + HideItem("insertHTML"); + HideItem("insertFormMenu"); + HideItem("fileExportToText"); + HideItem("viewEditModeToolbar"); + } +} + +function HideItem(id) { + var item = document.getElementById(id); + if (item) { + item.hidden = true; + } +} + +function RemoveItem(id) { + var item = document.getElementById(id); + if (item) { + item.remove(); + } +} + +// Command Updating Strategy: +// Don't update on on selection change, only when menu is displayed, +// with this "oncreate" handler: +function EditorInitTableMenu() { + try { + InitJoinCellMenuitem("menu_JoinTableCells"); + } catch (ex) {} + + // Set enable states for all table commands + goUpdateTableMenuItems(document.getElementById("composerTableMenuItems")); +} + +function InitJoinCellMenuitem(id) { + // Change text on the "Join..." item depending if we + // are joining selected cells or just cell to right + // TODO: What to do about normal selection that crosses + // table border? Try to figure out all cells + // included in the selection? + var menuText; + var menuItem = document.getElementById(id); + if (!menuItem) { + return; + } + + // Use "Join selected cells if there's more than 1 cell selected + var numSelected; + var foundElement; + + try { + var tagNameObj = {}; + var countObj = { value: 0 }; + foundElement = GetCurrentTableEditor().getSelectedOrParentTableElement( + tagNameObj, + countObj + ); + numSelected = countObj.value; + } catch (e) {} + if (foundElement && numSelected > 1) { + menuText = GetString("JoinSelectedCells"); + } else { + menuText = GetString("JoinCellToRight"); + } + + menuItem.setAttribute("label", menuText); + menuItem.setAttribute("accesskey", GetString("JoinCellAccesskey")); +} + +function InitRemoveStylesMenuitems( + removeStylesId, + removeLinksId, + removeNamedAnchorsId +) { + var editor = GetCurrentEditor(); + if (!editor) { + return; + } + + // Change wording of menuitems depending on selection + var stylesItem = document.getElementById(removeStylesId); + var linkItem = document.getElementById(removeLinksId); + + var isCollapsed = editor.selection.isCollapsed; + if (stylesItem) { + stylesItem.setAttribute( + "label", + isCollapsed ? GetString("StopTextStyles") : GetString("RemoveTextStyles") + ); + stylesItem.setAttribute( + "accesskey", + GetString("RemoveTextStylesAccesskey") + ); + } + if (linkItem) { + linkItem.setAttribute( + "label", + isCollapsed ? GetString("StopLinks") : GetString("RemoveLinks") + ); + linkItem.setAttribute("accesskey", GetString("RemoveLinksAccesskey")); + // Note: disabling text style is a pain since there are so many - forget it! + + // Disable if not in a link, but always allow "Remove" + // if selection isn't collapsed since we only look at anchor node + try { + SetElementEnabled( + linkItem, + !isCollapsed || editor.getElementOrParentByTagName("href", null) + ); + } catch (e) {} + } + // Disable if selection is collapsed + SetElementEnabledById(removeNamedAnchorsId, !isCollapsed); +} + +function goUpdateTableMenuItems(commandset) { + var editor = GetCurrentTableEditor(); + if (!editor) { + dump("goUpdateTableMenuItems: too early, not initialized\n"); + return; + } + + var enabled = false; + var enabledIfTable = false; + + var flags = editor.flags; + if (!(flags & Ci.nsIEditor.eEditorReadonlyMask) && IsEditingRenderedHTML()) { + var tagNameObj = { value: "" }; + var element; + try { + element = editor.getSelectedOrParentTableElement(tagNameObj, { + value: 0, + }); + } catch (e) {} + + if (element) { + // Value when we need to have a selected table or inside a table + enabledIfTable = true; + + // All others require being inside a cell or selected cell + enabled = tagNameObj.value == "td"; + } + } + + // Loop through command nodes + for (var i = 0; i < commandset.childNodes.length; i++) { + var commandID = commandset.childNodes[i].getAttribute("id"); + if (commandID) { + if ( + commandID == "cmd_InsertTable" || + commandID == "cmd_JoinTableCells" || + commandID == "cmd_SplitTableCell" || + commandID == "cmd_ConvertToTable" + ) { + // Call the update method in the command class + goUpdateCommand(commandID); + } else if ( + commandID == "cmd_DeleteTable" || + commandID == "cmd_NormalizeTable" || + commandID == "cmd_editTable" || + commandID == "cmd_TableOrCellColor" || + commandID == "cmd_SelectTable" + ) { + // Directly set with the values calculated here + goSetCommandEnabled(commandID, enabledIfTable); + } else { + goSetCommandEnabled(commandID, enabled); + } + } + } +} + +// ----------------------------------------------------------------------------------- +// Helpers for inserting and editing tables: + +function IsInTable() { + var editor = GetCurrentEditor(); + try { + var flags = editor.flags; + return ( + IsHTMLEditor() && + !(flags & Ci.nsIEditor.eEditorReadonlyMask) && + IsEditingRenderedHTML() && + null != editor.getElementOrParentByTagName("table", null) + ); + } catch (e) {} + return false; +} + +function IsInTableCell() { + try { + var editor = GetCurrentEditor(); + var flags = editor.flags; + return ( + IsHTMLEditor() && + !(flags & Ci.nsIEditor.eEditorReadonlyMask) && + IsEditingRenderedHTML() && + null != editor.getElementOrParentByTagName("td", null) + ); + } catch (e) {} + return false; +} + +function IsSelectionInOneCell() { + try { + var editor = GetCurrentEditor(); + var selection = editor.selection; + + if (selection.rangeCount == 1) { + // We have a "normal" single-range selection + if ( + !selection.isCollapsed && + selection.anchorNode != selection.focusNode + ) { + // Check if both nodes are within the same cell + var anchorCell = editor.getElementOrParentByTagName( + "td", + selection.anchorNode + ); + var focusCell = editor.getElementOrParentByTagName( + "td", + selection.focusNode + ); + return ( + focusCell != null && anchorCell != null && focusCell == anchorCell + ); + } + // Collapsed selection or anchor == focus (thus must be in 1 cell) + return true; + } + } catch (e) {} + return false; +} + +// Call this with insertAllowed = true to allow inserting if not in existing table, +// else use false to do nothing if not in a table +function EditorInsertOrEditTable(insertAllowed) { + if (IsInTable()) { + // Edit properties of existing table + window.openDialog( + "chrome://editor/content/EdTableProps.xhtml", + "_blank", + "chrome,close,titlebar,modal", + "", + "TablePanel" + ); + gContentWindow.focus(); + } else if (insertAllowed) { + try { + if (GetCurrentEditor().selection.isCollapsed) { + // If we have a caret, insert a blank table... + EditorInsertTable(); + } else { + // Else convert the selection into a table. + goDoCommand("cmd_ConvertToTable"); + } + } catch (e) {} + } +} + +function EditorInsertTable() { + // Insert a new table + window.openDialog( + "chrome://editor/content/EdInsertTable.xhtml", + "_blank", + "chrome,close,titlebar,modal", + "" + ); + gContentWindow.focus(); +} + +function EditorTableCellProperties() { + if (!IsHTMLEditor()) { + return; + } + + try { + var cell = GetCurrentEditor().getElementOrParentByTagName("td", null); + if (cell) { + // Start Table Properties dialog on the "Cell" panel + window.openDialog( + "chrome://editor/content/EdTableProps.xhtml", + "_blank", + "chrome,close,titlebar,modal", + "", + "CellPanel" + ); + gContentWindow.focus(); + } + } catch (e) {} +} + +function GetNumberOfContiguousSelectedRows() { + if (!IsHTMLEditor()) { + return 0; + } + + var rows = 0; + try { + var editor = GetCurrentTableEditor(); + var rowObj = { value: 0 }; + var colObj = { value: 0 }; + var cell = editor.getFirstSelectedCellInTable(rowObj, colObj); + if (!cell) { + return 0; + } + + // We have at least one row + rows++; + + var lastIndex = rowObj.value; + do { + cell = editor.getNextSelectedCell({ value: 0 }); + if (cell) { + editor.getCellIndexes(cell, rowObj, colObj); + var index = rowObj.value; + if (index == lastIndex + 1) { + lastIndex = index; + rows++; + } + } + } while (cell); + } catch (e) {} + + return rows; +} + +function GetNumberOfContiguousSelectedColumns() { + if (!IsHTMLEditor()) { + return 0; + } + + var columns = 0; + try { + var editor = GetCurrentTableEditor(); + var colObj = { value: 0 }; + var rowObj = { value: 0 }; + var cell = editor.getFirstSelectedCellInTable(rowObj, colObj); + if (!cell) { + return 0; + } + + // We have at least one column + columns++; + + var lastIndex = colObj.value; + do { + cell = editor.getNextSelectedCell({ value: 0 }); + if (cell) { + editor.getCellIndexes(cell, rowObj, colObj); + var index = colObj.value; + if (index == lastIndex + 1) { + lastIndex = index; + columns++; + } + } + } while (cell); + } catch (e) {} + + return columns; +} + +function EditorOnFocus() { + // Current window already has the InsertCharWindow + if ("InsertCharWindow" in window && window.InsertCharWindow) { + return; + } + + // Find window with an InsertCharsWindow and switch association to this one + var windowWithDialog = FindEditorWithInsertCharDialog(); + if (windowWithDialog) { + // Switch the dialog to current window + // this sets focus to dialog, so bring focus back to editor window + if (SwitchInsertCharToThisWindow(windowWithDialog)) { + top.document.commandDispatcher.focusedWindow.focus(); + } + } +} + +function SwitchInsertCharToThisWindow(windowWithDialog) { + if ( + windowWithDialog && + "InsertCharWindow" in windowWithDialog && + windowWithDialog.InsertCharWindow + ) { + // Move dialog association to the current window + window.InsertCharWindow = windowWithDialog.InsertCharWindow; + windowWithDialog.InsertCharWindow = null; + + // Switch the dialog's opener to current window's + window.InsertCharWindow.opener = window; + + // Bring dialog to the foreground + window.InsertCharWindow.focus(); + return true; + } + return false; +} + +function FindEditorWithInsertCharDialog() { + try { + // Find window with an InsertCharsWindow and switch association to this one + let enumerator = Services.wm.getEnumerator(null); + + while (enumerator.hasMoreElements()) { + var tempWindow = enumerator.getNext(); + + if ( + !tempWindow.closed && + tempWindow != window && + "InsertCharWindow" in tempWindow && + tempWindow.InsertCharWindow + ) { + return tempWindow; + } + } + } catch (e) {} + return null; +} + +function EditorFindOrCreateInsertCharWindow() { + if ("InsertCharWindow" in window && window.InsertCharWindow) { + window.InsertCharWindow.focus(); + } else { + // Since we switch the dialog during EditorOnFocus(), + // this should really never be found, but it's good to be sure + var windowWithDialog = FindEditorWithInsertCharDialog(); + if (windowWithDialog) { + SwitchInsertCharToThisWindow(windowWithDialog); + } else { + // The dialog will set window.InsertCharWindow to itself + window.openDialog( + "chrome://editor/content/EdInsertChars.xhtml", + "_blank", + "chrome,close,titlebar", + "" + ); + } + } +} + +// Find another HTML editor window to associate with the InsertChar dialog +// or close it if none found (May be a mail composer) +function SwitchInsertCharToAnotherEditorOrClose() { + if ("InsertCharWindow" in window && window.InsertCharWindow) { + var enumerator; + try { + enumerator = Services.wm.getEnumerator(null); + } catch (e) {} + if (!enumerator) { + return; + } + + // TODO: Fix this to search for command controllers and look for "cmd_InsertChars" + // For now, detect just Web Composer and HTML Mail Composer + while (enumerator.hasMoreElements()) { + var tempWindow = enumerator.getNext(); + if ( + !tempWindow.closed && + tempWindow != window && + tempWindow != window.InsertCharWindow && + "GetCurrentEditor" in tempWindow && + tempWindow.GetCurrentEditor() + ) { + tempWindow.InsertCharWindow = window.InsertCharWindow; + window.InsertCharWindow = null; + tempWindow.InsertCharWindow.opener = tempWindow; + return; + } + } + // Didn't find another editor - close the dialog + window.InsertCharWindow.close(); + } +} + +function ResetStructToolbar() { + gLastFocusNode = null; + UpdateStructToolbar(); +} + +function newCommandListener(element) { + return function() { + return SelectFocusNodeAncestor(element); + }; +} + +function newContextmenuListener(button, element) { + /* globals InitStructBarContextMenu */ // SeaMonkey only. + return function() { + return InitStructBarContextMenu(button, element); + }; +} + +function UpdateStructToolbar() { + var editor = GetCurrentEditor(); + if (!editor) { + return; + } + + var mixed = GetSelectionContainer(); + if (!mixed) { + return; + } + var element = mixed.node; + var oneElementSelected = mixed.oneElementSelected; + + if (!element) { + return; + } + + if ( + element == gLastFocusNode && + oneElementSelected == gLastFocusNodeWasSelected + ) { + return; + } + + gLastFocusNode = element; + gLastFocusNodeWasSelected = mixed.oneElementSelected; + + var toolbar = document.getElementById("structToolbar"); + if (!toolbar) { + return; + } + // We need to leave the <label> to flex the buttons to the left. + for (let node of toolbar.querySelectorAll("toolbarbutton,textbox")) { + node.remove(); + } + + toolbar.removeAttribute("label"); + + if (IsInHTMLSourceMode()) { + // we have destroyed the contents of the status bar and are + // about to recreate it ; but we don't want to do that in + // Source mode + return; + } + + var tag, button; + var bodyElement = GetBodyElement(); + var isFocusNode = true; + var tmp; + do { + tag = element.nodeName.toLowerCase(); + + button = document.createXULElement("toolbarbutton"); + button.setAttribute("label", "<" + tag + ">"); + button.setAttribute("value", tag); + button.setAttribute("context", "structToolbarContext"); + button.className = "struct-button"; + + toolbar.insertBefore(button, toolbar.firstChild); + + button.addEventListener("command", newCommandListener(element)); + + button.addEventListener( + "contextmenu", + newContextmenuListener(button, element) + ); + + if (isFocusNode && oneElementSelected) { + button.setAttribute("checked", "true"); + isFocusNode = false; + } + + tmp = element; + element = element.parentNode; + } while (element && tmp != bodyElement); +} + +function SelectFocusNodeAncestor(element) { + var editor = GetCurrentEditor(); + if (editor) { + if (element == GetBodyElement()) { + editor.selectAll(); + } else { + editor.selectElement(element); + } + } + ResetStructToolbar(); +} + +function GetSelectionContainer() { + var editor = GetCurrentEditor(); + if (!editor) { + return null; + } + + var selection; + try { + selection = editor.selection; + if (!selection) { + return null; + } + } catch (e) { + return null; + } + + var result = { oneElementSelected: false }; + + if (selection.isCollapsed) { + result.node = selection.focusNode; + } else { + var rangeCount = selection.rangeCount; + if (rangeCount == 1) { + result.node = editor.getSelectedElement(""); + var range = selection.getRangeAt(0); + + // check for a weird case : when we select a piece of text inside + // a text node and apply an inline style to it, the selection starts + // at the end of the text node preceding the style and ends after the + // last char of the style. Assume the style element is selected for + // user's pleasure + if ( + !result.node && + range.startContainer.nodeType == Node.TEXT_NODE && + range.startOffset == range.startContainer.length && + range.endContainer.nodeType == Node.TEXT_NODE && + range.endOffset == range.endContainer.length && + range.endContainer.nextSibling == null && + range.startContainer.nextSibling == range.endContainer.parentNode + ) { + result.node = range.endContainer.parentNode; + } + + if (!result.node) { + // let's rely on the common ancestor of the selection + result.node = range.commonAncestorContainer; + } else { + result.oneElementSelected = true; + } + } else { + // assume table cells ! + var i, + container = null; + for (i = 0; i < rangeCount; i++) { + range = selection.getRangeAt(i); + if (!container) { + container = range.startContainer; + } else if (container != range.startContainer) { + // all table cells don't belong to same row so let's + // select the parent of all rows + result.node = container.parentNode; + break; + } + result.node = container; + } + } + } + + // make sure we have an element here + while (result.node.nodeType != Node.ELEMENT_NODE) { + result.node = result.node.parentNode; + } + + // and make sure the element is not a special editor node like + // the <br> we insert in blank lines + // and don't select anonymous content !!! (fix for bug 190279) + while ( + result.node.hasAttribute("_moz_editor_bogus_node") || + editor.isAnonymousElement(result.node) + ) { + result.node = result.node.parentNode; + } + + return result; +} + +function FillInHTMLTooltipEditor(tooltip) { + const XLinkNS = "http://www.w3.org/1999/xlink"; + var tooltipText = null; + var node; + if (IsInPreviewMode()) { + for (node = document.tooltipNode; node; node = node.parentNode) { + if (node.nodeType == Node.ELEMENT_NODE) { + tooltipText = node.getAttributeNS(XLinkNS, "title"); + if (tooltipText && /\S/.test(tooltipText)) { + tooltip.setAttribute("label", tooltipText); + return true; + } + tooltipText = node.getAttribute("title"); + if (tooltipText && /\S/.test(tooltipText)) { + tooltip.setAttribute("label", tooltipText); + return true; + } + } + } + } else { + for (node = document.tooltipNode; node; node = node.parentNode) { + if ( + ChromeUtils.getClassName(node) === "HTMLImageElement" || + ChromeUtils.getClassName(node) === "HTMLInputElement" + ) { + tooltipText = node.getAttribute("src"); + } else if (ChromeUtils.getClassName(node) === "HTMLAnchorElement") { + tooltipText = node.getAttribute("href") || node.getAttribute("name"); + } + if (tooltipText) { + tooltip.setAttribute("label", tooltipText); + return true; + } + } + } + return false; +} + +function UpdateTOC() { + window.openDialog( + "chrome://editor/content/EdInsertTOC.xhtml", + "_blank", + "chrome,close,modal,titlebar" + ); + window.content.focus(); +} + +function InitTOCMenu() { + var elt = GetCurrentEditor().document.getElementById("mozToc"); + var createMenuitem = document.getElementById("insertTOCMenuitem"); + var updateMenuitem = document.getElementById("updateTOCMenuitem"); + var removeMenuitem = document.getElementById("removeTOCMenuitem"); + if (removeMenuitem && createMenuitem && updateMenuitem) { + if (elt) { + createMenuitem.setAttribute("disabled", "true"); + updateMenuitem.removeAttribute("disabled"); + removeMenuitem.removeAttribute("disabled"); + } else { + createMenuitem.removeAttribute("disabled"); + removeMenuitem.setAttribute("disabled", "true"); + updateMenuitem.setAttribute("disabled", "true"); + } + } +} + +function RemoveTOC() { + var theDocument = GetCurrentEditor().document; + var elt = theDocument.getElementById("mozToc"); + if (elt) { + elt.remove(); + } + + let anchorNodes = theDocument.querySelectorAll('a[name^="mozTocId"]'); + for (let node of anchorNodes) { + if (node.parentNode) { + node.remove(); + } + } +} diff --git a/comm/suite/editor/base/content/editor.xhtml b/comm/suite/editor/base/content/editor.xhtml new file mode 100644 index 0000000000..033185592f --- /dev/null +++ b/comm/suite/editor/base/content/editor.xhtml @@ -0,0 +1,402 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/" type="text/css"?> + +<?xml-stylesheet href="chrome://editor/skin/editorPrimaryToolbar.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/editorFormatToolbar.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/editorModeToolbar.css" type="text/css"?> +<?xul-overlay href="chrome://editor/content/editorOverlay.xhtml"?> +<?xul-overlay href="chrome://editor/content/editingOverlay.xhtml"?> +<?xul-overlay href="chrome://editor/content/composerOverlay.xhtml"?> +<?xul-overlay href="chrome://communicator/content/contentAreaContextOverlay.xhtml"?> +<?xul-overlay href="chrome://editor/content/EditorContextMenuOverlay.xhtml"?> +<?xul-overlay href="chrome://communicator/content/charsetOverlay.xhtml"?> +<?xul-overlay href="chrome://communicator/content/utilityOverlay.xhtml"?> +<?xul-overlay href="chrome://communicator/content/tasksOverlay.xhtml"?> +<?xul-overlay href="chrome://communicator/content/sidebar/sidebarOverlay.xhtml"?> + +<!DOCTYPE window [ +<!ENTITY % editorDTD SYSTEM "chrome://editor/locale/editor.dtd" > +%editorDTD; +<!ENTITY % editorOverlayDTD SYSTEM "chrome://editor/locale/editorOverlay.dtd" > +%editorOverlayDTD; +<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > +%brandDTD; +]> + +<window id="editorWindow" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="EditorOnLoad()" + onunload="EditorShutdown()" + onclose="return Async.promiseSpinningly(EditorCanClose())" + onfocus="EditorOnFocus()" + title="&editorWindow.titlemodifier;" + titlemodifier="&editorWindow.titlemodifier;" + titlemenuseparator="&editorWindow.titlemodifiermenuseparator;" + toggletoolbar="true" + lightweightthemes="true" + lightweightthemesfooter="status-bar" + windowtype="composer:html" + macanimationtype="document" + drawtitle="true" + width="640" height="480" + screenX="10" screenY="10" + persist="screenX screenY width height sizemode"> + + <script src="chrome://editor/content/editor.js"/> + <script src="chrome://editor/content/publishprefs.js"/> + <script src="chrome://communicator/content/contentAreaClick.js"/> + <script src="chrome://global/content/printUtils.js"/> + <script src="chrome://global/content/nsDragAndDrop.js"/> + + <popupset id="contentAreaContextSet"/> + <popupset id="editorPopupSet"> + <menupopup id="structToolbarContext"/> + <menupopup id="sidebarPopup"/> + </popupset> + + <commandset id="editorCommands"> + <commandset id="commonEditorMenuItems"/> + <commandset id="composerMenuItems"/> + <commandset id="composerOnlyMenuItems" + commandupdater="true" + events="create, mode_switch" + oncommandupdate="goUpdateComposerMenuItems(this);"> + <!-- file menu --> + <command id="cmd_exportToText" + label="&exportToTextCmd.label;" + accesskey="&exportToTextCmd.accesskey;" + oncommand="goDoCommand('cmd_exportToText');"/> + <command id="cmd_preview" + oncommand="goDoCommand('cmd_preview');"/> + <command id="cmd_editSendPage" + label="&sendPageCmd.label;" + accesskey="&sendPageCmd.accesskey;" + oncommand="goDoCommand('cmd_editSendPage');"/> + <!-- format menu --> + <command id="cmd_pageProperties" + oncommand="goDoCommand('cmd_pageProperties');"/> + <!-- tools menu --> + <command id="cmd_validate" + oncommand="goDoCommand('cmd_validate');"/> + <!-- toolbars --> + <command id="cmd_NormalMode" + oncommand="goDoCommand('cmd_NormalMode');"/> + <command id="cmd_AllTagsMode" + oncommand="goDoCommand('cmd_AllTagsMode');"/> + <command id="cmd_HTMLSourceMode" + oncommand="goDoCommand('cmd_HTMLSourceMode');"/> + <command id="cmd_PreviewMode" + oncommand="goDoCommand('cmd_PreviewMode');"/> + </commandset> + <commandset id="composerEditMenuItems"/> + <commandset id="composerSaveMenuItems"/> + <commandset id="composerStyleMenuItems"> + <command id="cmd_updateStructToolbar" + oncommand="goDoCommand('cmd_updateStructToolbar');"/> + </commandset> + <commandset id="composerTableMenuItems"/> + <commandset id="composerListMenuItems"/> + <commandset id="tasksCommands"/> + <!-- view menu --> + <command id="cmd_viewEditModeToolbar" + oncommand="goToggleToolbar('EditModeToolbar','cmd_viewEditModeToolbar');" + checked="true"/> + </commandset> + + <tooltip id="aHTMLTooltip" onpopupshowing="return FillInHTMLTooltipEditor(this);"/> + + <!-- keys are appended from the overlay --> + <keyset id="editorKeys"> + <keyset id="tasksKeys"/> + <key id="showHideSidebar"/> + <!-- eat these tab events here to stop focus from moving --> + <key keycode="VK_TAB" oncommand="return true;"/> + <key keycode="VK_TAB" modifiers="shift" oncommand="return true;"/> + <key keycode="VK_TAB" modifiers="control" oncommand="return true;"/> + <key keycode="VK_TAB" modifiers="control,shift" oncommand="return true;"/> + </keyset> + + <vbox id="titlebar"/> + +<toolbox id="EditorToolbox" + class="toolbox-top" + mode="full" + defaultmode="full"> + <toolbar id="toolbar-menubar" + type="menubar" + class="chromeclass-menubar" + persist="collapsed" + grippytooltiptext="&menuBar.tooltip;" + customizable="true" + defaultset="menubar-items" + mode="icons" + iconsize="small" + defaultmode="icons" + defaulticonsize="small" + context="toolbar-context-menu"> + <toolbaritem id="menubar-items" + class="menubar-items" + align="center"> + <menubar id="main-menubar"> + <menu id="menu_File"/> + <menu id="menu_Edit"/> + + <menu id="menu_View"> + <!-- id pulls in "Show Sidebar" item from sidebarOverlay --> + <menupopup id="menu_View_Popup"> + <menu id="menu_Toolbars"> + <menupopup id="view_toolbars_popup" + onpopupshowing="onViewToolbarsPopupShowing(event);" + oncommand="onViewToolbarCommand(event);"> + <menuitem id="viewEditModeToolbar" + label="&editmodeToolbarCmd.label;" + accesskey="&editmodeToolbarCmd.accesskey;" + type="checkbox" + command="cmd_viewEditModeToolbar"/> + <menuitem id="menu_showTaskbar"/> + </menupopup> + </menu> + <menuseparator id="viewSep1"/> + <menuitem id="viewNormalMode" + type="radio" + group="mode" + checked="true" + label="&NormalMode.label;" + accesskey="&NormalMode.accesskey;" + command="cmd_NormalMode"/> + <menuitem id="viewAllTagsMode" + type="radio" + group="mode" + label="&AllTagsMode.label;" + accesskey="&AllTagsMode.accesskey;" + command="cmd_AllTagsMode"/> + <menuitem id="viewSourceMode" + type="radio" + group="mode" + label="&HTMLSourceMode.label;" + accesskey="&HTMLSourceMode.accesskey;" + command="cmd_HTMLSourceMode"/> + <menuitem id="viewPreviewMode" + type="radio" + group="mode" + label="&PreviewMode.label;" + accesskey="&PreviewMode.accesskey;" + command="cmd_PreviewMode"/> + <menuseparator id="viewSep2"/> + <menu id="charsetMenu" + onpopupshowing="EditorUpdateCharsetMenu(event.target);" + oncommand="EditorSetCharacterSet(event);"/> + </menupopup> + </menu> + + <menu id="insertMenu"/> + + <menu id="formatMenu" + label="&formatMenu.label;" + accesskey="&formatMenu.accesskey;"> + <menupopup id="formatMenuPopup"> + <menuitem id="snapToGrid" + label="&grid.label;" + accesskey="&grid.accesskey;" + oncommand="goDoCommand('cmd_grid');" + observes="cmd_renderedHTMLEnabler"/> + <menuseparator/> + <menuitem id="objectProperties"/> + <menuitem id="colorsAndBackground"/> + <!-- Don't use 'observes', must call command correctly --> + <menuitem id="pageProperties" + label="&pageProperties.label;" + accesskey="&pageProperties.accesskey;" + oncommand="goDoCommand('cmd_pageProperties');" + observes="cmd_renderedHTMLEnabler"/> + </menupopup> + </menu> + + <menu id="tableMenu"/> + + <!-- tasks menu filled from tasksOverlay --> + <menu id="tasksMenu"> + <menupopup id="taskPopup"> + <menuitem id="menu_validate" + label="&validateCmd.label;" + accesskey="&validateCmd.accesskey;" + command="cmd_validate"/> + <menuseparator id="sep_validate"/> + </menupopup> + </menu> + + <menu id="windowMenu"/> + + <!-- help menu filled from globalOverlay --> + <menu id="menu_Help"/> + </menubar> + </toolbaritem> + </toolbar> + + <!-- toolbar mostly filled out from editorOverlay --> + <!-- add class="standard" for dark blue background, icons need rework first --> + <toolbar id="EditToolbar" + class="chromeclass-toolbar toolbar-primary" + persist="collapsed" + grippytooltiptext="&compositionToolbar.tooltip;" + toolbarname="&compositionToolbarCmd.label;" + accesskey="&compositionToolbarCmd.accesskey;" + customizable="true" + defaultset="newButton,openButton,saveButton,publishButton,previewButton,print-button,separator,linkButton,imageButton,tableButton,spellingButton,spring,throbber-box" + context="toolbar-context-menu"> + <toolbarbutton id="newButton"/> + <toolbarbutton id="openButton"/> + <toolbarbutton id="saveButton"/> + <toolbarbutton id="publishButton"/> + <toolbarbutton id="previewButton" + class="toolbarbutton-1" + label="&previewToolbarCmd.label;" + removable="true" + command="cmd_preview" + tooltiptext="&previewToolbarCmd.tooltip;"/> + <toolbarbutton id="cutButton"/> + <toolbarbutton id="copyButton"/> + <toolbarbutton id="pasteButton"/> + <toolbarbutton id="print-button"/> + <toolbarbutton id="findButton"/> + <toolbarbutton id="linkButton"/> + <toolbarbutton id="namedAnchorButton"/> + <toolbarbutton id="imageButton"/> + <toolbarbutton id="formButton"/> + <toolbarbutton id="hlineButton"/> + <toolbarbutton id="tableButton"/> + <toolbarbutton id="spellingButton"/> + <toolbaritem id="throbber-box"/> + </toolbar> + + <toolbarset id="customToolbars" context="toolbar-context-menu"/> + + <toolbarpalette id="EditToolbarPalette"/> + + <toolbar id="FormatToolbar" + class="chromeclass-toolbar" + persist="collapsed" + grippytooltiptext="&formatToolbar.tooltip;" + toolbarname="&formattingToolbarCmd.label;" + accesskey="&formattingToolbarCmd.accesskey;" + customizable="true" + defaultset="paragraph-select-container,color-buttons-container,HighlightColorButton,separator,DecreaseFontSizeButton,IncreaseFontSizeButton,separator,boldButton,italicButton,underlineButton,separator,ulButton,olButton,outdentButton,indentButton,separator,align-left-button,align-center-button,align-right-button,align-justify-button,absolutePositionButton,decreaseZIndexButton,increaseZIndexButton" + mode="icons" + iconsize="small" + defaultmode="icons" + defaulticonsize="small" + context="toolbar-context-menu" + nowindowdrag="true"> + <!-- from editorOverlay --> + <toolbaritem id="paragraph-select-container"/> + <toolbaritem id="color-buttons-container" + disableoncustomize="true"/> + <toolbarbutton id="HighlightColorButton"/> + <!-- Enable if required for SeaMonkey. + <toolbarbutton id="AbsoluteFontSizeButton"/> + --> + <toolbarbutton id="DecreaseFontSizeButton"/> + <toolbarbutton id="IncreaseFontSizeButton"/> + <toolbarbutton id="boldButton"/> + <toolbarbutton id="italicButton"/> + <toolbarbutton id="underlineButton"/> + <toolbarbutton id="ulButton"/> + <toolbarbutton id="olButton"/> + <toolbarbutton id="outdentButton"/> + <toolbarbutton id="indentButton"/> + <toolbarbutton id="align-left-button"/> + <toolbarbutton id="align-center-button"/> + <toolbarbutton id="align-right-button"/> + <toolbarbutton id="align-justify-button"/> + <toolbarbutton id="absolutePositionButton"/> + <toolbarbutton id="decreaseZIndexButton"/> + <toolbarbutton id="increaseZIndexButton"/> + + <!-- TODO: Change to a menulist? --> + <!-- menu> + <button id="AlignPopupButton"/> + <menupopup id="AlignmentPopup"/> + </menu --> + + + <spacer flex="1"/> + </toolbar> +</toolbox> + +<!-- sidebar/toolbar/content/status --> +<hbox id="sidebar-parent" flex="1"> + <!-- From sidebarOverlay.xhtml --> + <vbox id="sidebar-box" class="chromeclass-extrachrome" hidden="true"/> + <splitter id="sidebar-splitter" class="chromeclass-extrachrome" hidden="true"/> + + <vbox id="appcontent" flex="1"> + <deck id="ContentWindowDeck" selectedIndex="0" flex="1"> + <vbox> + <findbar id="FindToolbar" browserid="content-frame"/> + <editor editortype="html" + type="content" + primary="true" + id="content-frame" + onclick="EditorClick(event);" + ondblclick="EditorDblClick(event);" + context="contentAreaContextMenu" + flex="1" + tooltip="aHTMLTooltip"/> + </vbox> + <vbox> + <label id="doctype-text" crop="right"/> + <editor type="content" + id="content-source" + context="contentAreaContextMenu" + flex="1"/> + </vbox> + </deck> + + <!-- Edit Mode toolbar --> + <tabbox id="EditModeToolbar" + persist="collapsed"> + <tabs id="EditModeTabs" + class="tabs-bottom" + flex="1" + onselect="this.selectedItem.doCommand();"> + <tab id="NormalModeButton" + class="tab-bottom edit-mode" + label="&NormalModeTab.label;" + tooltiptext="&NormalMode.tooltip;" + command="cmd_NormalMode"/> + <tab id="TagModeButton" + class="tab-bottom edit-mode" + label="&AllTagsModeTab.label;" + tooltiptext="&AllTagsMode.tooltip;" + command="cmd_AllTagsMode"/> + <tab id="SourceModeButton" + class="tab-bottom edit-mode" + label="&HTMLSourceModeTab.label;" + tooltiptext="&HTMLSourceMode.tooltip;" + dir="&HTMLSourceModeTab.dir;" + command="cmd_HTMLSourceMode"/> + <tab id="PreviewModeButton" + class="tab-bottom edit-mode" + label="&PreviewModeTab.label;" + tooltiptext="&PreviewMode.tooltip;" + command="cmd_PreviewMode"/> + </tabs> + </tabbox> + + </vbox> <!-- appcontent --> +</hbox><!-- sidebar-parent --> + + <!-- Some of this is from globalOverlay.xhtml --> + <hbox class="statusbar chromeclass-status" id="status-bar"> + <statusbarpanel id="component-bar"/> + <hbox id="structToolbar" class="statusbarpanel" flex="1" pack="end"> + <label id="structSpacer" value="" flex="1"/> + </hbox> + <statusbarpanel id="offline-status" class="statusbarpanel-iconic"/> + </hbox> +</window> diff --git a/comm/suite/editor/base/content/editorApplicationOverlay.js b/comm/suite/editor/base/content/editorApplicationOverlay.js new file mode 100644 index 0000000000..17d02a510b --- /dev/null +++ b/comm/suite/editor/base/content/editorApplicationOverlay.js @@ -0,0 +1,161 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implementations of nsIControllerCommand for composer commands */ + +function initEditorContextMenuItems(aEvent) +{ + var shouldShowEditPage = !gContextMenu.onImage && !gContextMenu.onLink && !gContextMenu.onTextInput && !gContextMenu.inDirList; + gContextMenu.showItem( "context-editpage", shouldShowEditPage ); + + var shouldShowEditLink = gContextMenu.onSaveableLink; + gContextMenu.showItem( "context-editlink", shouldShowEditLink ); + + // Hide the applications separator if there's no add-on apps present. + gContextMenu.showItem("context-sep-apps", gContextMenu.shouldShowSeparator("context-sep-apps")); +} + +function initEditorContextMenuListener(aEvent) +{ + var popup = document.getElementById("contentAreaContextMenu"); + if (popup) + popup.addEventListener("popupshowing", initEditorContextMenuItems); +} + +addEventListener("load", initEditorContextMenuListener, false); + +function editDocument(aDocument) +{ + if (!aDocument) + aDocument = window.content.document; + + editPage(aDocument.URL); +} + +function editPageOrFrame() +{ + var focusedWindow = document.commandDispatcher.focusedWindow; + + // if the uri is a specific frame, grab it, else use the frameset uri + // and let Composer handle error if necessary + editPage(getContentFrameURI(focusedWindow)); +} + +function getContentFrameURI(aFocusedWindow) +{ + let isContentFrame = aFocusedWindow ? + (aFocusedWindow.top == window.content) : false; + + let contentFrame = isContentFrame ? + aFocusedWindow : window.content; + + return contentFrame.location.href; +} + +// Any non-editor window wanting to create an editor with a URL +// should use this instead of "window.openDialog..." +// We must always find an existing window with requested URL +function editPage(url, aFileType) +{ + // aFileType is optional and needs to default to html. + aFileType = aFileType || "html"; + + // Always strip off "view-source:" and #anchors + url = url.replace(/^view-source:/, "").replace(/#.*/, ""); + + // if the current window is a browser window, then extract the current charset menu setting from the current + // document and use it to initialize the new composer window... + + var wintype = document.documentElement.getAttribute('windowtype'); + var charsetArg; + + if (wintype == "navigator:browser" && content.document) + charsetArg = "charset=" + content.document.characterSet; + + try { + let uri = createURI(url, null, null); + + let enumerator = Services.wm.getEnumerator("composer:" + aFileType); + let emptyWindow; + while ( enumerator.hasMoreElements() ) + { + var win = enumerator.getNext(); + if (win && !win.closed && win.IsWebComposer()) + { + if (CheckOpenWindowForURIMatch(uri, win)) + { + // We found an editor with our url + win.focus(); + return; + } + else if (!emptyWindow && win.PageIsEmptyAndUntouched()) + { + emptyWindow = win; + } + } + } + + if (emptyWindow) + { + // we have an empty window we can use + if (aFileType == "html" && emptyWindow.IsInHTMLSourceMode()) + emptyWindow.SetEditMode(emptyWindow.PreviousNonSourceDisplayMode); + emptyWindow.EditorLoadUrl(url); + emptyWindow.focus(); + emptyWindow.SetSaveAndPublishUI(url); + return; + } + + // Create new Composer / Text Editor window. + if (aFileType == "text" && ("EditorNewPlaintext" in window)) + EditorNewPlaintext(url, charsetArg); + else + NewEditorWindow(url, charsetArg); + + } catch(e) {} +} + +function createURI(urlstring) +{ + try { + return Services.io.newURI(urlstring); + } catch (e) {} + + return null; +} + +function CheckOpenWindowForURIMatch(uri, win) +{ + try { + return createURI(win.content.document.URL).equals(uri); + } catch (e) {} + + return false; +} + +function toEditor() +{ + if (!CycleWindow("composer:html")) + NewEditorWindow(); +} + +function NewEditorWindow(aUrl, aCharsetArg) +{ + window.openDialog("chrome://editor/content", + "_blank", + "chrome,all,dialog=no", + aUrl || "about:blank", + aCharsetArg); +} + +function NewEditorFromTemplate() +{ + // XXX not implemented +} + +function NewEditorFromDraft() +{ + // XXX not implemented +} diff --git a/comm/suite/editor/base/content/editorOverlay.xhtml b/comm/suite/editor/base/content/editorOverlay.xhtml new file mode 100644 index 0000000000..7a5d3900f0 --- /dev/null +++ b/comm/suite/editor/base/content/editorOverlay.xhtml @@ -0,0 +1,1504 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<!DOCTYPE overlay [ +<!ENTITY % editorOverlayDTD SYSTEM "chrome://editor/locale/editorOverlay.dtd"> +%editorOverlayDTD; +<!ENTITY % editorSmileyOverlayDTD SYSTEM + "chrome://editor/locale/editorSmileyOverlay.dtd"> +%editorSmileyOverlayDTD; +]> + +<overlay id="editorOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"> + +<script src="chrome://editor/content/editorUtilities.js"/> +<script src="chrome://editor/content/ComposerCommands.js"/> + + <keyset id="editorKeys"> + <!-- defined in globalOverlay --> + <key id="key_newNavigator"/> + <key id="key_newPrivateWindow"/> + <key id="key_newBlankPage"/> + <key id="key_save" + key="&saveCmd.key;" + command="cmd_save" + modifiers="accel"/> + <key id="key_print"/> + <key id="key_close"/> + <key id="key_undo"/> + <key id="key_redo"/> + <key id="key_cut"/> + <key id="key_copy"/> + <key id="key_paste"/> + <key id="key_delete"/> + <key id="key_delete2"/> + <key id="key_selectAll"/> + <key id="pastequotationkb" + key="&pasteAsQuotationCmd.key;" + command="cmd_pasteQuote" + modifiers="accel, shift"/> + <key id="pastenoformattingkb" + key="&pasteNoFormatting.key;" + command="cmd_pasteNoFormatting" + modifiers="accel, shift"/> + <key id="key_rewrap" + key="&editRewrapCmd.key;" + command="cmd_rewrap" + modifiers="accel"/> + <keyset id="findKeys"/> + <key id="key_checkspelling" + key="&checkSpellingCmd2.key;" + command="cmd_spelling" + modifiers="accel,shift"/> + + <key id="boldkb" + key="&styleBoldCmd.key;" + command="cmd_bold" + modifiers="accel"/> + <key id="italickb" + key="&styleItalicCmd.key;" + command="cmd_italic" + modifiers="accel"/> + <key id="underlinekb" + key="&styleUnderlineCmd.key;" + command="cmd_underline" + modifiers="accel"/> + <key id="fixedwidthkb" + key="&fontFixedWidth.key;" + command="cmd_tt" + modifiers="accel"/> + + <key id="increaseindentkb" + key="&increaseIndent.key;" + command="cmd_indent" + modifiers="accel"/> + <key id="decreaseindentkb" + key="&decreaseIndent.key;" + command="cmd_outdent" + modifiers="accel"/> + + <key id="removestyleskb" + key="&formatRemoveStyles.key;" + command="cmd_removeStyles" + modifiers="accel, shift"/> + <key id="removestyleskb2" + key=" " + command="cmd_removeStyles" + modifiers="accel"/> + <key id="removelinkskb" + key="&formatRemoveLinks.key;" + command="cmd_removeLinks" + modifiers="accel, shift"/> + <key id="removenamedanchorskb" + key="&formatRemoveNamedAnchors2.key;" + command="cmd_removeNamedAnchors" + modifiers="accel, shift"/> + <key id="decreasefontsizekb" + key="&decrementFontSize.key;" + command="cmd_decreaseFontStep" + modifiers="accel"/> + <key id="increasefontsizekb" + key="&incrementFontSize.key;" + command="cmd_increaseFontStep" + modifiers="accel"/> + <key key="&incrementFontSize.key;" + command="cmd_increaseFontStep" + modifiers="accel,shift"/> + <key key="&incrementFontSize.key2;" + command="cmd_increaseFontStep" + modifiers="accel"/> + + <key id="insertlinkkb" + key="&insertLinkCmd2.key;" + command="cmd_link" + modifiers="accel"/> + </keyset> + + <!-- commands updated when the editor gets created --> + <commandset id="commonEditorMenuItems" + commandupdater="true" + events="create" + oncommandupdate="goUpdateComposerMenuItems(this)"> + <command id="cmd_printSetup" oncommand="goDoCommand('cmd_printSetup');"/> + <command id="cmd_printpreview" oncommand="goDoCommand('cmd_printpreview');"/> + <command id="cmd_print" oncommand="goDoCommand('cmd_print');"/> + <command id="cmd_close" oncommand="goDoCommand('cmd_close');"/> + </commandset> + + <commandset id="composerMenuItems" + commandupdater="true" + events="create, mode_switch" + oncommandupdate="goUpdateComposerMenuItems(this)"> + <!-- format menu --> + <command id="cmd_listProperties" oncommand="goDoCommand('cmd_listProperties')"/> + <command id="cmd_colorProperties" oncommand="goDoCommand('cmd_colorProperties')"/> + + <command id="cmd_link" oncommand="goDoCommand('cmd_link')"/> + <command id="cmd_anchor" oncommand="goDoCommand('cmd_anchor')"/> + <command id="cmd_image" oncommand="goDoCommand('cmd_image')"/> + <command id="cmd_hline" oncommand="goDoCommand('cmd_hline')"/> + <command id="cmd_table" oncommand="goDoCommand('cmd_table')"/> + <command id="cmd_objectProperties" oncommand="goDoCommand('cmd_objectProperties')"/> + <command id="cmd_insertChars" oncommand="goDoCommand('cmd_insertChars')" label="&insertCharsCmd.label;"/> + <command id="cmd_insertHTMLWithDialog" oncommand="goDoCommand('cmd_insertHTMLWithDialog')" label="&insertHTMLCmd.label;"/> + <command id="cmd_insertMathWithDialog" oncommand="goDoCommand('cmd_insertMathWithDialog')" label="&insertMathCmd.label;"/> + <command id="cmd_insertBreak" oncommand="goDoCommand('cmd_insertBreak')"/> + <command id="cmd_insertBreakAll" oncommand="goDoCommand('cmd_insertBreakAll')"/> + + <!-- only used in context popup menu --> + <command id="cmd_editLink" oncommand="goDoCommand('cmd_editLink')"/> + + <!-- dummy command used just to disable things in non-HTML modes --> + <command id="cmd_renderedHTMLEnabler"/> + </commandset> + + <!-- edit menu commands. These get updated by code in globalOverlay.js --> + <commandset id="composerEditMenuItems" + commandupdater="true" + events="create, mode_switch" + oncommandupdate="goUpdateComposerMenuItems(this)"> + <command id="cmd_undo"/> + <command id="cmd_redo"/> + <command id="cmd_cut"/> + <command id="cmd_copy"/> + <command id="cmd_paste"/> + <command id="cmd_pasteNoFormatting" + label="&pasteNoFormatting.label;" + accesskey="&pasteNoFormatting.accesskey;" + oncommand="goDoCommand('cmd_pasteNoFormatting');"/> + <command id="cmd_delete"/> + <command id="cmd_selectAll"/> + <command id="cmd_preferences" oncommand="goDoCommand('cmd_preferences')"/> + <command id="cmd_findReplace" oncommand="goDoCommand('cmd_findReplace')"/> + <command id="cmd_find" oncommand="goDoCommand('cmd_find')"/> + <command id="cmd_findNext" oncommand="goDoCommand('cmd_findNext');"/> + <command id="cmd_findPrev" oncommand="goDoCommand('cmd_findPrev');"/> + <command id="cmd_spelling" oncommand="goDoCommand('cmd_spelling')"/> + <command id="cmd_pasteQuote" + label="&pasteAsQuotationCmd.label;" + accesskey="&pasteAsQuotationCmd.accesskey;" + oncommand="goDoCommand('cmd_pasteQuote');"/> + <command id="cmd_rewrap" oncommand="goDoCommand('cmd_rewrap');"/> + </commandset> + + <!-- style related commands that update on creation, and on selection change --> + <commandset id="composerStyleMenuItems" + commandupdater="true" + events="create, style, mode_switch" + oncommandupdate="goUpdateComposerMenuItems(this)"> + <command id="cmd_bold" state="false" oncommand="doStyleUICommand('cmd_bold')"/> + <command id="cmd_italic" state="false" oncommand="doStyleUICommand('cmd_italic')"/> + <command id="cmd_underline" state="false" oncommand="doStyleUICommand('cmd_underline')"/> + <command id="cmd_tt" state="false" oncommand="doStyleUICommand('cmd_tt')"/> + <command id="cmd_smiley"/> + <command id="cmd_strikethrough" state="false" oncommand="doStyleUICommand('cmd_strikethrough');"/> + <command id="cmd_superscript" state="false" oncommand="doStyleUICommand('cmd_superscript');"/> + <command id="cmd_subscript" state="false" oncommand="doStyleUICommand('cmd_subscript');"/> + <command id="cmd_nobreak" state="false" oncommand="doStyleUICommand('cmd_nobreak');"/> + <command id="cmd_em" state="false" oncommand="doStyleUICommand('cmd_em')"/> + <command id="cmd_strong" state="false" oncommand="doStyleUICommand('cmd_strong')"/> + <command id="cmd_cite" state="false" oncommand="doStyleUICommand('cmd_cite')"/> + <command id="cmd_abbr" state="false" oncommand="doStyleUICommand('cmd_abbr')"/> + <command id="cmd_acronym" state="false" oncommand="doStyleUICommand('cmd_acronym')"/> + <command id="cmd_code" state="false" oncommand="doStyleUICommand('cmd_code')"/> + <command id="cmd_samp" state="false" oncommand="doStyleUICommand('cmd_samp')"/> + <command id="cmd_var" state="false" oncommand="doStyleUICommand('cmd_var')"/> + <command id="cmd_ul" state="false" oncommand="doStyleUICommand('cmd_ul')"/> + <command id="cmd_ol" state="false" oncommand="doStyleUICommand('cmd_ol')"/> + <command id="cmd_indent" oncommand="goDoCommand('cmd_indent')"/> + <command id="cmd_outdent" oncommand="goDoCommand('cmd_outdent')"/> + + <!-- the state attribute gets filled with the paragraph format before the command is executed --> + <command id="cmd_paragraphState" state="" oncommand="doStatefulCommand('cmd_paragraphState', event.target.value)"/> + <command id="cmd_fontFace" state="" oncommand="doStatefulCommand('cmd_fontFace', event.target.value)"/> + + <!-- No "oncommand", use EditorSelectColor() to bring up color dialog --> + <command id="cmd_fontColor" state=""/> + <command id="cmd_backgroundColor" state=""/> + <command id="cmd_highlight" state="transparent" oncommand="EditorSelectColor('Highlight', event);"/> + <command id="cmd_fontSize" oncommand="goDoCommand('cmd_fontSize')"/> + <command id="cmd_align" state=""/> + <command id="cmd_absPos" state="" oncommand="goDoCommand('cmd_absPos')"/> + <command id="cmd_increaseZIndex" state="" oncommand="goDoCommand('cmd_increaseZIndex')"/> + <command id="cmd_decreaseZIndex" state="" oncommand="goDoCommand('cmd_decreaseZIndex')"/> + <command id="cmd_advancedProperties" oncommand="goDoCommand('cmd_advancedProperties')"/> + <command id="cmd_increaseFontStep" oncommand="goDoCommand('cmd_increaseFontStep')"/> + <command id="cmd_decreaseFontStep" oncommand="goDoCommand('cmd_decreaseFontStep')"/> + <command id="cmd_removeStyles" oncommand="goDoCommand('cmd_removeStyles')"/> + <command id="cmd_removeLinks" oncommand="goDoCommand('cmd_removeLinks')"/> + <command id="cmd_removeNamedAnchors" oncommand="goDoCommand('cmd_removeNamedAnchors')"/> + </commandset> + + <!-- commands updated only when the menu gets created --> + <commandset id="composerListMenuItems" + commandupdater="true" + events="create, mode_switch" + oncommandupdate="goUpdateComposerMenuItems(this)"> + <!-- List menu --> + <command id="cmd_dt" oncommand="doStyleUICommand('cmd_dt')"/> + <command id="cmd_dd" oncommand="doStyleUICommand('cmd_dd')"/> + <command id="cmd_removeList" oncommand="goDoCommand('cmd_removeList')"/> + <!-- cmd_ul and cmd_ol are shared with toolbar and are in composerStyleMenuItems commandset --> + </commandset> + + <commandset id="composerTableMenuItems" + commandupdater="true" + events="create, mode_switch" + oncommandupdate="goUpdateTableMenuItems(this)"> + <!-- Table menu --> + <command id="cmd_SelectTable" oncommand="goDoCommand('cmd_SelectTable')"/> + <command id="cmd_SelectRow" oncommand="goDoCommand('cmd_SelectRow')"/> + <command id="cmd_SelectColumn" oncommand="goDoCommand('cmd_SelectColumn')"/> + <command id="cmd_SelectCell" oncommand="goDoCommand('cmd_SelectCell')"/> + <command id="cmd_SelectAllCells" oncommand="goDoCommand('cmd_SelectAllCells')"/> + <command id="cmd_InsertTable" oncommand="goDoCommand('cmd_InsertTable')"/> + <command id="cmd_InsertRowAbove" oncommand="goDoCommand('cmd_InsertRowAbove')"/> + <command id="cmd_InsertRowBelow" oncommand="goDoCommand('cmd_InsertRowBelow')"/> + <command id="cmd_InsertColumnBefore" oncommand="goDoCommand('cmd_InsertColumnBefore')"/> + <command id="cmd_InsertColumnAfter" oncommand="goDoCommand('cmd_InsertColumnAfter')"/> + <command id="cmd_InsertCellBefore" oncommand="goDoCommand('cmd_InsertCellBefore')"/> + <command id="cmd_InsertCellAfter" oncommand="goDoCommand('cmd_InsertCellAfter')"/> + <command id="cmd_DeleteTable" oncommand="goDoCommand('cmd_DeleteTable')"/> + <command id="cmd_DeleteRow" oncommand="goDoCommand('cmd_DeleteRow')"/> + <command id="cmd_DeleteColumn" oncommand="goDoCommand('cmd_DeleteColumn')"/> + <command id="cmd_DeleteCell" oncommand="goDoCommand('cmd_DeleteCell')"/> + <command id="cmd_DeleteCellContents" oncommand="goDoCommand('cmd_DeleteCellContents')"/> + <command id="cmd_NormalizeTable" oncommand="goDoCommand('cmd_NormalizeTable')"/> + <command id="cmd_JoinTableCells" oncommand="goDoCommand('cmd_JoinTableCells')"/> + <command id="cmd_SplitTableCell" oncommand="goDoCommand('cmd_SplitTableCell')"/> + <command id="cmd_ConvertToTable" oncommand="goDoCommand('cmd_ConvertToTable')"/> + <command id="cmd_TableOrCellColor" oncommand="goDoCommand('cmd_TableOrCellColor')"/> + <command id="cmd_editTable" oncommand="goDoCommand('cmd_editTable')"/> + </commandset> + + <commandset id="editorCommands"> + <commandset id="globalEditMenuItems"/> + <commandset id="selectEditMenuItems"/> + <commandset id="undoEditMenuItems"/> + <commandset id="clipboardEditMenuItems"/> + <command id="toggleSidebar"/> + <!-- file menu --> + <command id="cmd_newNavigator"/> + <command id="cmd_newPrivateWindow"/> + <command id="cmd_newEditor"/> + <command id="cmd_newEditorTemplate"/> + <command id="cmd_newEditorDraft"/> + </commandset> + + <popupset id="editorPopupSet"> + <menupopup id="popupNotificationMenu"/> + <menupopup id="toolbar-context-menu"/> + <panel id="customizeToolbarSheetPopup"/> + </popupset> + + <menu id="menu_Edit"> + <menupopup id="menu_EditPopup"> + <!-- from utilityOverlay.xhtml --> + <menuitem id="menu_undo"/> + <menuitem id="menu_redo"/> + <menuseparator id="sep_cut"/> + <menuitem id="menu_cut"/> + <menuitem id="menu_copy"/> + <menuitem id="menu_paste"/> + <menuitem id="menu_pasteNoFormatting" + key="pastenoformattingkb" + command="cmd_pasteNoFormatting"/> + <menuitem id="menu_pasteQuote" + key="pastequotationkb" + command="cmd_pasteQuote"/> + <menuitem id="menu_rewrap" + label="&editRewrapCmd.label;" + accesskey="&editRewrapCmd.accesskey;" + key="key_rewrap" + command="cmd_rewrap"/> + <menuitem id="menu_delete"/> + <menuseparator id="sep_selectAll"/> + <menuitem id="menu_selectAll"/> + <menuseparator id="sep_find"/> + <menuitem id="menu_find" + label="&findBarCmd.label;"/> + <menuitem id="menu_findReplace" + label="&findReplaceCmd.label;"/> + <menuitem id="menu_findNext"/> + <menuitem id="menu_findPrev"/> + <menuseparator id="sep_checkspelling"/> + <menuitem id="menu_checkspelling" + label="&checkSpellingCmd2.label;" + accesskey="&checkSpellingCmd2.accesskey;" + key="key_checkspelling" + command="cmd_spelling"/> + <menuitem id="menu_inlineSpellCheck" + type="checkbox" + label="&enableInlineSpellChecker.label;" + accesskey="&enableInlineSpellChecker.accesskey;"/> + <menuseparator id="sep_preferences"/> + <menuitem id="menu_preferences" + command="cmd_preferences"/> + </menupopup> + </menu> + + <!-- Insert menu --> + <menu id="insertMenu" + label="&insertMenu.label;" + accesskey="&insertMenu.accesskey;"> + <menupopup id="insertMenuPopup"> + <menuitem id="insertImage" + label="&insertImageCmd.label;" + accesskey="&insertImageCmd.accesskey;" + command="cmd_image"/> + <menuitem id="insertTable" + label="&insertTableCmd.label;" + accesskey="&insertTableCmd.accesskey;" + command="cmd_InsertTable"/> + <menuitem id="insertLink" + label="&insertLinkCmd2.label;" + accesskey="&insertLinkCmd2.accesskey;" + command="cmd_link" + key="insertlinkkb"/> + <menuitem id="insertAnchor" + label="&insertAnchorCmd.label;" + accesskey="&insertAnchorCmd.accesskey;" + command="cmd_anchor"/> + <menuitem id="insertHline" + label="&insertHLineCmd.label;" + accesskey="&insertHLineCmd.accesskey;" + command="cmd_hline"/> + <menuitem id="insertHTMLSource" + accesskey="&insertHTMLCmd.accesskey;" + command="cmd_insertHTMLWithDialog"/> + <menuitem id="insertMath" + accesskey="&insertMathCmd.accesskey;" + command="cmd_insertMathWithDialog"/> + <menuitem id="insertChars" + accesskey="&insertCharsCmd.accesskey;" + command="cmd_insertChars"/> + <menu id="insertTOC" label="&tocMenu.label;" accesskey="&tocMenu.accesskey;"> + <menupopup id="insertTOCPopup" onpopupshowing="InitTOCMenu()"> + <menuitem id="insertTOCMenuitem" + label="&insertTOC.label;" + accesskey="&insertTOC.accesskey;" + oncommand="UpdateTOC()"/> + <menuitem id="updateTOCMenuitem" + label="&updateTOC.label;" + accesskey="&updateTOC.accesskey;" + oncommand="UpdateTOC()"/> + <menuitem id="removeTOCMenuitem" + label="&removeTOC.label;" + accesskey="&removeTOC.accesskey;" + oncommand="RemoveTOC()"/> + </menupopup> + </menu> + <menu id="insertSmiley" + label="&insertSmiley.label;" + accesskey="&insertSmiley.accesskey;"> + <menupopup id="smilyMenuPopup"> + <menuitem class="smiley insert-smile menuitem-iconic" + label="&smiley1Cmd.label;" + accesskey="&smiley1Cmd.accesskey;" + oncommand="doStatefulCommand('cmd_smiley', ':-)');"/> + <menuitem class="smiley insert-frown menuitem-iconic" + label="&smiley2Cmd.label;" + accesskey="&smiley2Cmd.accesskey;" + oncommand="doStatefulCommand('cmd_smiley', ':-(');"/> + <menuitem class="smiley insert-wink menuitem-iconic" + label="&smiley3Cmd.label;" + accesskey="&smiley3Cmd.accesskey;" + oncommand="doStatefulCommand('cmd_smiley', ';-)');"/> + <menuitem class="smiley insert-tongue menuitem-iconic" + label="&smiley4Cmd.label;" + accesskey="&smiley4Cmd.accesskey;" + oncommand="doStatefulCommand('cmd_smiley', ':-P');"/> + <menuitem class="smiley insert-laughing menuitem-iconic" + label="&smiley5Cmd.label;" + accesskey="&smiley5Cmd.accesskey;" + oncommand="doStatefulCommand('cmd_smiley', ':-D');"/> + <menuitem class="smiley insert-embarrassed menuitem-iconic" + label="&smiley6Cmd.label;" + accesskey="&smiley6Cmd.accesskey;" + oncommand="doStatefulCommand('cmd_smiley', ':-[');"/> + <menuitem class="smiley insert-undecided menuitem-iconic" + label="&smiley7Cmd.label;" + accesskey="&smiley7Cmd.accesskey;" + oncommand="doStatefulCommand('cmd_smiley', ':-\\');"/> + <menuitem class="smiley insert-surprise menuitem-iconic" + label="&smiley8Cmd.label;" + accesskey="&smiley8Cmd.accesskey;" + oncommand="doStatefulCommand('cmd_smiley', '=-O');"/> + <menuitem class="smiley insert-kiss menuitem-iconic" + label="&smiley9Cmd.label;" + accesskey="&smiley9Cmd.accesskey;" + oncommand="doStatefulCommand('cmd_smiley', ':-*');"/> + <menuitem class="smiley insert-yell menuitem-iconic" + label="&smiley10Cmd.label;" + accesskey="&smiley10Cmd.accesskey;" + oncommand="doStatefulCommand('cmd_smiley', '>:o');"/> + <menuitem class="smiley insert-cool menuitem-iconic" + label="&smiley11Cmd.label;" + accesskey="&smiley11Cmd.accesskey;" + oncommand="doStatefulCommand('cmd_smiley', '8-)');"/> + <menuitem class="smiley insert-money menuitem-iconic" + label="&smiley12Cmd.label;" + accesskey="&smiley12Cmd.accesskey;" + oncommand="doStatefulCommand('cmd_smiley', ':-$');"/> + <menuitem class="smiley insert-foot menuitem-iconic" + label="&smiley13Cmd.label;" + accesskey="&smiley13Cmd.accesskey;" + oncommand="doStatefulCommand('cmd_smiley', ':-!');"/> + <menuitem class="smiley insert-innocent menuitem-iconic" + label="&smiley14Cmd.label;" + accesskey="&smiley14Cmd.accesskey;" + oncommand="doStatefulCommand('cmd_smiley', 'O:-)');"/> + <menuitem class="smiley insert-cry menuitem-iconic" + label="&smiley15Cmd.label;" + accesskey="&smiley15Cmd.accesskey;" + oncommand="doStatefulCommand('cmd_smiley', ':\'(');"/> + <menuitem class="smiley insert-sealed menuitem-iconic" + label="&smiley16Cmd.label;" + accesskey="&smiley16Cmd.accesskey;" + oncommand="doStatefulCommand('cmd_smiley', ':-X');"/> + </menupopup> + </menu> + <menuseparator id="insertMenuSeparator"/> + <menuitem id="insertBreakAll" + accesskey="&insertBreakAllCmd.accesskey;" + command="cmd_insertBreakAll" + label="&insertBreakAllCmd.label;"/> + </menupopup> + </menu> + + <!-- Format Menu --> + <menupopup id="formatMenuPopup" onpopupshowing="EditorInitFormatMenu()"> + <!-- Font face submenu --> + <menu id="fontFaceMenu" + label="&fontfaceMenu.label;" + accesskey="&fontfaceMenu.accesskey;" + position="1"> + <menupopup id="fontFaceMenuPopup" + oncommand="if (event.target.localName == 'menuitem') + doStatefulCommand('cmd_fontFace', event.target.getAttribute('value'));" + onpopupshowing="initFontFaceMenu(this);"> + <menuitem id="menu_fontFaceVarWidth" + label="&fontVarWidth.label;" + accesskey="&fontVarWidth.accesskey;" + value="" + type="radio" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_fontFaceFixedWidth" + label="&fontFixedWidth.label;" + accesskey="&fontFixedWidth.accesskey;" + value="tt" + type="radio" + observes="cmd_renderedHTMLEnabler"/> + <menuseparator id="fontFaceMenuAfterGenericFontsSeparator"/> + <menuitem id="menu_fontFaceHelvetica" + label="&fontHelvetica.label;" + accesskey="&fontHelvetica.accesskey;" + value="Helvetica, Arial, sans-serif" + value_parsed="helvetica,arial,sans-serif" + type="radio" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_fontFaceTimes" + label="&fontTimes.label;" + accesskey="&fontTimes.accesskey;" + value="Times New Roman, Times, serif" + value_parsed="times new roman,times,serif" + type="radio" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_fontFaceCourier" + label="&fontCourier.label;" + accesskey="&fontCourier.accesskey;" + value="Courier New, Courier, monospace" + value_parsed="courier new,courier,monospace" + type="radio" + observes="cmd_renderedHTMLEnabler"/> + <menuseparator id="fontFaceMenuAfterDefaultFontsSeparator" + class="fontFaceMenuAfterDefaultFonts"/> + <menuseparator id="fontFaceMenuAfterUsedFontsSeparator" + class="fontFaceMenuAfterUsedFonts" + collapsed="true"/> + <!-- Local font face items added here by initLocalFontFaceMenu() --> + </menupopup> + </menu> + + <!-- Font size submenu --> + <menu id="fontSizeMenu" label="&fontSizeMenu.label;" + accesskey="&fontSizeMenu.accesskey;" + position="2"> + <menupopup id="fontSizeMenuPopup" onpopupshowing="initFontSizeMenu(this, true)"> + <menuitem id="menu_decreaseFontSize" + label="&decreaseFontSize.label;" + accesskey="&decreaseFontSize.accesskey;" + command="cmd_decreaseFontStep" + type="radio" name="1" autocheck="false" + key="decreasefontsizekb"/> + <menuitem id="menu_increaseFontSize" + label="&increaseFontSize.label;" + accesskey="&increaseFontSize.accesskey;" + command="cmd_increaseFontStep" + type="radio" name="1" autocheck="false" + key="increasefontsizekb"/> + <menuseparator id="fontSizeMenuAfterIncreaseFontSizeSeparator"/> + <menuitem id="menu_x-small" + label="&size-tinyCmd.label;" + accesskey="&size-tinyCmd.accesskey;" + oncommand="EditorSetFontSize('x-small')" + type="radio" name="1" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_size-small" + label="&size-smallCmd.label;" + accesskey="&size-smallCmd.accesskey;" + oncommand="EditorSetFontSize('small')" + type="radio" name="1" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_size-medium" + label="&size-mediumCmd.label;" + accesskey="&size-mediumCmd.accesskey;" + oncommand="EditorSetFontSize('medium')" + type="radio" name="1" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_size-large" + label="&size-largeCmd.label;" + accesskey="&size-largeCmd.accesskey;" + oncommand="EditorSetFontSize('large')" + type="radio" name="1" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_size-x-large" + label="&size-extraLargeCmd.label;" + accesskey="&size-extraLargeCmd.accesskey;" + oncommand="EditorSetFontSize('x-large')" + type="radio" name="1" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_size-xx-large" + label="&size-hugeCmd.label;" + accesskey="&size-hugeCmd.accesskey;" + oncommand="EditorSetFontSize('xx-large')" + type="radio" name="1" + observes="cmd_renderedHTMLEnabler"/> + <!-- Enable if required for SeaMonkey. + <menuitem id="fontSizeMenu_smallBigInfo" + type="checkbox" name="2" disabled="true" hidden="true"/> + --> + </menupopup> + </menu> + + <!-- Font style submenu --> + <menu id="fontStyleMenu" label="&fontStyleMenu.label;" + accesskey="&fontStyleMenu.accesskey;" + position="3"> + <menupopup id="fontStyleMenuPopup" onpopupshowing="initFontStyleMenu(this)"> + <menuitem id="menu_styleBold" + label="&styleBoldCmd.label;" + accesskey="&styleBoldCmd.accesskey;" + observes="cmd_bold" + type="checkbox" + key="boldkb"/> + <menuitem id="menu_styleItalic" + label="&styleItalicCmd.label;" + accesskey="&styleItalicCmd.accesskey;" + observes="cmd_italic" + type="checkbox" + key="italickb"/> + <menuitem id="menu_styleUnderline" + label="&styleUnderlineCmd.label;" + accesskey="&styleUnderlineCmd.accesskey;" + observes="cmd_underline" + type="checkbox" + key="underlinekb"/> + <menuitem id="menu_styleStrikeThru" + label="&styleStrikeThruCmd.label;" + accesskey="&styleStrikeThruCmd.accesskey;" + observes="cmd_strikethrough" + type="checkbox"/> + <menuitem id="menu_styleSuperscript" + label="&styleSuperscriptCmd.label;" + accesskey="&styleSuperscriptCmd.accesskey;" + observes="cmd_superscript" + type="checkbox"/> + <menuitem id="menu_styleSubscript" + label="&styleSubscriptCmd.label;" + accesskey="&styleSubscriptCmd.accesskey;" + observes="cmd_subscript" + type="checkbox"/> + <menuitem id="menu_fontFixedWidth" + label="&fontFixedWidth.label;" + accesskey="&fontFixedWidth.accesskey;" + observes="cmd_tt" + type="checkbox" + key="fixedwidthkb"/> + <menuitem id="menu_styleNonbreaking" + label="&styleNonbreakingCmd.label;" + accesskey="&styleNonbreakingCmd.accesskey;" + observes="cmd_nobreak" + type="checkbox"/> + <menuseparator id="fontStyleMenuAfterNonbreakingSeparator"/> + <menuitem id="menu_styleEm" + label="&styleEm.label;" + accesskey="&styleEm.accesskey;" + observes="cmd_em" + type="checkbox"/> + <menuitem id="menu_styleStrong" + label="&styleStrong.label;" + accesskey="&styleStrong.accesskey;" + observes="cmd_strong" + type="checkbox"/> + <menuitem id="menu_styleCite" + label="&styleCite.label;" + accesskey="&styleCite.accesskey;" + observes="cmd_cite" + type="checkbox"/> + <menuitem id="menu_styleAbbr" + label="&styleAbbr.label;" + accesskey="&styleAbbr.accesskey;" + observes="cmd_abbr" + type="checkbox"/> + <menuitem id="menu_styleAcronym" + label="&styleAcronym.label;" + accesskey="&styleAcronym.accesskey;" + observes="cmd_acronym" + type="checkbox"/> + <menuitem id="menu_styleCode" + label="&styleCode.label;" + accesskey="&styleCode.accesskey;" + observes="cmd_code" + type="checkbox"/> + <menuitem id="menu_styleSamp" + label="&styleSamp.label;" + accesskey="&styleSamp.accesskey;" + observes="cmd_samp" + type="checkbox"/> + <menuitem id="menu_styleVar" + label="&styleVar.label;" + accesskey="&styleVar.accesskey;" + observes="cmd_var" + type="checkbox"/> + </menupopup> + </menu> + + <!-- Note: "cmd_fontColor" only monitors color state, it doesn't execute the command + (We should use "cmd_fontColorState" and "cmd_backgroundColorState" ?) --> + <menuitem id="fontColor" + label="&formatFontColor.label;" + accesskey="&formatFontColor.accesskey;" + observes="cmd_fontColor" + oncommand="EditorSelectColor('Text', null);" + position="4"/> + <menuseparator id="removeSep" position="5"/> + + <!-- label and accesskey set at runtime from strings --> + <menuitem id="removeStylesMenuitem" + key="removestyleskb" + observes="cmd_removeStyles" + position="6"/> + <menuitem id="removeLinksMenuitem" + key="removelinkskb" + observes="cmd_removeLinks" + position="7"/> + <menuitem id="removeNamedAnchorsMenuitem" + label="&formatRemoveNamedAnchors.label;" + key="removenamedanchorskb" + accesskey="&formatRemoveNamedAnchors.accesskey;" + observes="cmd_removeNamedAnchors" + position="8"/> + <menuseparator id="tabSep" position="9"/> + + <!-- Note: the 'Init' menu methods for Paragraph, List, and Align + assume that the id = 'menu_'+tagName (the 'value' label), + except for the first ('none') item + --> + <!-- Paragraph Style submenu --> + <menu id="paragraphMenu" label="¶graphMenu.label;" + accesskey="¶graphMenu.accesskey;" + position="10" onpopupshowing="InitParagraphMenu()"> + <menupopup id="paragraphMenuPopup" + oncommand="doStatefulCommand('cmd_paragraphState', event.target.getAttribute('value'))"> + <menuitem id="menu_bodyText" + type="radio" + name="1" + label="&bodyTextCmd.label;" + accesskey="&bodyTextCmd.accesskey;" + value="" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_p" + type="radio" + name="1" + label="¶graphParagraphCmd.label;" + accesskey="¶graphParagraphCmd.accesskey;" + value="p" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_h1" + type="radio" + name="1" + label="&heading1Cmd.label;" + accesskey="&heading1Cmd.accesskey;" + value="h1" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_h2" + type="radio" + name="1" + label="&heading2Cmd.label;" + accesskey="&heading2Cmd.accesskey;" + value="h2" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_h3" + type="radio" + name="1" label="&heading3Cmd.label;" + accesskey="&heading3Cmd.accesskey;" + value="h3" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_h4" + type="radio" + name="1" label="&heading4Cmd.label;" + accesskey="&heading4Cmd.accesskey;" + value="h4" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_h5" + type="radio" + name="1" label="&heading5Cmd.label;" + accesskey="&heading5Cmd.accesskey;" + value="h5" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_h6" + type="radio" + name="1" + label="&heading6Cmd.label;" + accesskey="&heading6Cmd.accesskey;" + value="h6" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_address" + type="radio" + name="1" + label="¶graphAddressCmd.label;" + accesskey="¶graphAddressCmd.accesskey;" + value="address" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_pre" + type="radio" + name="1" + label="¶graphPreformatCmd.label;" + accesskey="¶graphPreformatCmd.accesskey;" + value="pre" + observes="cmd_renderedHTMLEnabler"/> + </menupopup> + </menu> + + <!-- List Style submenu --> + <menu id="listMenu" label="&formatlistMenu.label;" + accesskey="&formatlistMenu.accesskey;" + position="11" onpopupshowing="InitListMenu()"> + <menupopup> + <menuitem id="menu_noList" + type="radio" + name="1" + label="&noneCmd.label;" + accesskey="&noneCmd.accesskey;" + command="cmd_removeList"/> + <menuitem id="menu_ul" + type="radio" + name="1" + label="&listBulletCmd.label;" + accesskey="&listBulletCmd.accesskey;" + command="cmd_ul"/> + <menuitem id="menu_ol" + type="radio" + name="1" + label="&listNumberedCmd.label;" + accesskey="&listNumberedCmd.accesskey;" + command="cmd_ol"/> + <menuitem id="menu_dt" + type="radio" + name="1" + label="&listTermCmd.label;" + accesskey="&listTermCmd.accesskey;" + command="cmd_dt"/> + <menuitem id="menu_dd" + type="radio" + name="1" + label="&listDefinitionCmd.label;" + accesskey="&listDefinitionCmd.accesskey;" + command="cmd_dd"/> + <menuseparator/> + <menuitem id="listProps" + label="&listPropsCmd.label;" + accesskey="&listPropsCmd.accesskey;" + command="cmd_listProperties"/> + </menupopup> + </menu> + <menuseparator id="identingSep" position="12"/> + + <menuitem id="increaseIndent" + label="&increaseIndent.label;" + accesskey="&increaseIndent.accesskey;" + key="increaseindentkb" + command="cmd_indent" + position="13"/> + <menuitem id="decreaseIndent" + label="&decreaseIndent.label;" + accesskey="&decreaseIndent.accesskey;" + key="decreaseindentkb" + command="cmd_outdent" + position="14"/> + + <menu id="alignMenu" label="&alignMenu.label;" accesskey="&alignMenu.accesskey;" + onpopupshowing="InitAlignMenu()" + position="15"> + <!-- Align submenu --> + <menupopup id="alignMenuPopup" + oncommand="doStatefulCommand('cmd_align', event.target.getAttribute('value'))"> + <menuitem id="menu_left" + label="&alignLeft.label;" + accesskey="&alignLeft.accesskey;" + type="radio" + name="1" + value="left" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_center" + label="&alignCenter.label;" + accesskey="&alignCenter.accesskey;" + type="radio" + name="1" + value="center" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_right" + label="&alignRight.label;" + accesskey="&alignRight.accesskey;" + type="radio" + name="1" + value="right" + observes="cmd_renderedHTMLEnabler"/> + <menuitem id="menu_justify" + label="&alignJustify.label;" + accesskey="&alignJustify.accesskey;" + type="radio" + name="1" + value="justify" + observes="cmd_renderedHTMLEnabler"/> + </menupopup> + </menu> + <menuseparator id="tableSep" position="16"/> + <!-- Merge Table Menu and separator in Messenger Composer here --> + <!-- Merge property items here --> + </menupopup> + + <!-- Next 2 are items to append at the bottom of the formatMenuPopup --> + <!-- label and accesskey filled in during menu creation --> + <menuitem id="objectProperties" command="cmd_objectProperties"/> + <!-- Don't use 'observes', must call command correctly --> + <menuitem id="colorsAndBackground" + label="&colorsAndBackground.label;" + accesskey="&colorsAndBackground.accesskey;" + oncommand="goDoCommand('cmd_colorProperties')" + observes="cmd_renderedHTMLEnabler"/> + + <menu id="tableMenu" label="&tableMenu.label;" accesskey="&tableMenu.accesskey;"> + <menupopup id="tableMenuPopup" onpopupshowing="EditorInitTableMenu()"> + <!-- From EditorCommandOverlay.xhtml (shared with context popup) --> + <menu id="tableInsertMenu" label="&tableInsertMenu.label;" accesskey="&tableInsertMenu.accesskey;"> + <menupopup id="tableInsertMenuPopup"> + <menuitem id="menu_insertTable" + label="&insertTableCmd.label;" + accesskey="&insertTableCmd.accesskey;" + command="cmd_InsertTable"/> + <menuseparator id="tableMenuAfterInsertTableSeparator"/> + <menuitem id="menu_tableRowAbove" + label="&tableRowAbove.label;" + accesskey="&tableRowAbove.accesskey;" + command="cmd_InsertRowAbove"/> + <menuitem id="menu_tableRowBelow" + label="&tableRowBelow.label;" + accesskey="&tableRowBelow.accesskey;" + command="cmd_InsertRowBelow"/> + <menuseparator id="tableMenuAfterTableRowSeparator"/> + <menuitem id="menu_tableColumnBefore" + label="&tableColumnBefore.label;" + accesskey="&tableColumnBefore.accesskey;" + command="cmd_InsertColumnBefore"/> + <menuitem id="menu_tableColumnAfter" + label="&tableColumnAfter.label;" + accesskey="&tableColumnAfter.accesskey;" + command="cmd_InsertColumnAfter"/> + <menuseparator id="tableMenuAfterInsertColumnSeparator"/> + <menuitem id="menu_tableCellBefore" + label="&tableCellBefore.label;" + accesskey="&tableCellBefore.accesskey;" + command="cmd_InsertCellBefore"/> + <menuitem id="menu_tableCellAfter" + label="&tableCellAfter.label;" + accesskey="&tableCellAfter.accesskey;" + command="cmd_InsertCellAfter"/> + </menupopup> + </menu> + <menu id="tableSelectMenu" label="&tableSelectMenu.label;" accesskey="&tableSelectMenu.accesskey;"> + <menupopup id="tableSelectPopup"> + <menuitem id="menu_SelectTable" + label="&tableTable.label;" + accesskey="&tableTable.accesskey;" + command="cmd_SelectTable"/> + <menuitem id="menu_SelectRow" + label="&tableRow.label;" + accesskey="&tableRow.accesskey;" + command="cmd_SelectRow"/> + <menuitem id="menu_SelectColumn" + label="&tableColumn.label;" + accesskey="&tableColumn.accesskey;" + command="cmd_SelectColumn"/> + <menuitem id="menu_SelectCell" + label="&tableCell.label;" + accesskey="&tableCell.accesskey;" + command="cmd_SelectCell"/> + <menuitem id="menu_SelectAllCells" + label="&tableAllCells.label;" + accesskey="&tableAllCells.accesskey;" + command="cmd_SelectAllCells"/> + </menupopup> + </menu> + <menu id="tableDeleteMenu" label="&tableDeleteMenu.label;" accesskey="&tableDeleteMenu.accesskey;"> + <menupopup id="tableDeletePopup"> + <menuitem id="menu_DeleteTable" + label="&tableTable.label;" + accesskey="&tableTable.accesskey;" + command="cmd_DeleteTable"/> + <menuitem id="menu_DeleteRow" + label="&tableRows.label;" + accesskey="&tableRow.accesskey;" + command="cmd_DeleteRow"/> + <menuitem id="menu_DeleteColumn" + label="&tableColumns.label;" + accesskey="&tableColumn.accesskey;" + command="cmd_DeleteColumn"/> + <menuitem id="menu_DeleteCell" + label="&tableCells.label;" + accesskey="&tableCell.accesskey;" + command="cmd_DeleteCell"/> + <menuitem id="menu_DeleteCellContents" + label="&tableCellContents.label;" + accesskey="&tableCellContents.accesskey;" + command="cmd_DeleteCellContents"/> + </menupopup> + </menu> + <menuseparator id="tableDeleteMenuAfterDeleteCellSeparator"/> + <!-- menu label is set in InitTableMenu --> + <menuitem id="menu_JoinTableCells" + label="&tableJoinCells.label;" + accesskey="&tableJoinCells.accesskey;" + command="cmd_JoinTableCells"/> + <menuitem id="menu_SlitTableCell" + label="&tableSplitCell.label;" + accesskey="&tableSplitCell.accesskey;" + command="cmd_SplitTableCell"/> + <menuitem id="menu_ConvertToTable" + label="&convertToTable.label;" + accesskey="&convertToTable.accesskey;" + command="cmd_ConvertToTable"/> + <menuseparator id="tableDeleteMenuAfterConvertToTableSeparator"/> + <menuitem id="menu_TableOrCellColor" + label="&tableOrCellColor.label;" + accesskey="&tableOrCellColor.accesskey;" + command="cmd_TableOrCellColor"/> + <menuitem id="menu_tableProperties" + label="&tableProperties.label;" + accesskey="&tableProperties.accesskey;" + command="cmd_editTable"/> + </menupopup> + </menu> + + <toolbarbutton id="AlignPopupButton" + class="formatting-button" + label="&AlignPopupButton.label;" + removable="true" + tooltiptext="&AlignPopupButton.tooltip;" + type="menu" + observes="cmd_align"> + <menupopup id="AlignPopup"> + <menuitem id="AlignLeftItem" class="menuitem-iconic" label="&alignLeft.label;" + oncommand="doStatefulCommand('cmd_align', 'left')" + tooltiptext="&alignLeftButton.tooltip;" /> + <menuitem id="AlignCenterItem" class="menuitem-iconic" label="&alignCenter.label;" + oncommand="doStatefulCommand('cmd_align', 'center')" + tooltiptext="&alignCenterButton.tooltip;" /> + <menuitem id="AlignRightItem" class="menuitem-iconic" label="&alignRight.label;" + oncommand="doStatefulCommand('cmd_align', 'right')" + tooltiptext="&alignRightButton.tooltip;" /> + <menuitem id="AlignJustifyItem" class="menuitem-iconic" label="&alignJustify.label;" + oncommand="doStatefulCommand('cmd_align', 'justify')" + tooltiptext="&alignJustifyButton.tooltip;"/> + </menupopup> + </toolbarbutton> + + <toolbarbutton id="InsertPopupButton" + class="formatting-button" + label="&InsertPopupButton.label;" + removable="true" + tooltiptext="&InsertPopupButton.tooltip;" + type="menu" + observes="cmd_renderedHTMLEnabler"> + <menupopup id="InsertPopup"> + <menuitem id="InsertLinkItem" + class="menuitem-iconic" + command="cmd_link" + label="&linkToolbarCmd.label;" + tooltiptext="&linkToolbarCmd.tooltip;"/> + <menuitem id="InsertAnchorItem" + class="menuitem-iconic" + command="cmd_anchor" + label="&anchorToolbarCmd.label;" + tooltiptext="&anchorToolbarCmd.tooltip;"/> + <menuitem id="InsertImageItem" + class="menuitem-iconic" + command="cmd_image" + label="&imageToolbarCmd.label;" + tooltiptext="&imageToolbarCmd.tooltip;"/> + <menuitem id="InsertHRuleItem" + class="menuitem-iconic" + command="cmd_hline" + label="&hruleToolbarCmd.label;" + tooltiptext="&hruleToolbarCmd.tooltip;"/> + <menuitem id="InsertTableItem" + class="menuitem-iconic" + command="cmd_table" + label="&tableToolbarCmd.label;" + tooltiptext="&tableToolbarCmd.tooltip;"/> + </menupopup> + </toolbarbutton> + + <!-- Editor toolbar items --> + <!-- note that we override the submenu item label "Blank Window" with "New" used for the menu --> + <toolbarbutton id="cutButton" class="toolbarbutton-1" + removable="true" + command="cmd_cut" + tooltiptext="&cutToolbarCmd.tooltip;"/> + <toolbarbutton id="copyButton" class="toolbarbutton-1" + removable="true" + command="cmd_copy" + tooltiptext="©ToolbarCmd.tooltip;"/> + <toolbarbutton id="pasteButton" class="toolbarbutton-1" + removable="true" + command="cmd_paste" + tooltiptext="&pasteToolbarCmd.tooltip;"/> + + <toolbarbutton id="findButton" + class="toolbarbutton-1" + label="&findToolbarCmd.label;" + removable="true" + command="cmd_find" + tooltiptext="&findToolbarCmd.tooltip;"/> + <toolbarbutton id="spellingButton" + class="toolbarbutton-1" + label="&spellToolbarCmd.label;" + removable="true" + command="cmd_spelling" + tooltiptext="&spellToolbarCmd.tooltip;"/> + <toolbarbutton id="imageButton" + class="toolbarbutton-1" + removable="true" + label="&imageToolbarCmd.label;" + command="cmd_image" + tooltiptext="&imageToolbarCmd.tooltip;"/> + <toolbarbutton id="hlineButton" + class="toolbarbutton-1" + removable="true" + label="&hruleToolbarCmd.label;" + command="cmd_hline" + tooltiptext="&hruleToolbarCmd.tooltip;"/> + <toolbarbutton id="tableButton" + class="toolbarbutton-1" + removable="true" + label="&tableToolbarCmd.label;" + command="cmd_table" + tooltiptext="&tableToolbarCmd.tooltip;"/> + <toolbarbutton id="linkButton" + class="toolbarbutton-1" + removable="true" + label="&linkToolbarCmd.label;" + command="cmd_link" + tooltiptext="&linkToolbarCmd.tooltip;"/> + <toolbarbutton id="namedAnchorButton" + class="toolbarbutton-1" + removable="true" + label="&anchorToolbarCmd.label;" + command="cmd_anchor" + tooltiptext="&anchorToolbarCmd.tooltip;"/> + + <!-- Formatting toolbar items. "value" are HTML tagnames, don't translate --> +<toolbaritem id="paragraph-select-container" + class="formatting-button" + title="&ParagraphSelect.title;" + align="center" + removable="true" + tooltiptext="&ParagraphSelect.tooltip;" + observes="cmd_renderedHTMLEnabler"> + <menulist id="ParagraphSelect" + class="toolbar-focustarget" + crop="right"> + <observes element="paragraph-select-container" attribute="disabled"/> + <observes element="cmd_paragraphState" attribute="state" onbroadcast="onParagraphFormatChange(this.parentNode, 'cmd_paragraphState')"/> + <menupopup id="ParagraphPopup" oncommand="doStatefulCommand('cmd_paragraphState', event.target.value)"> + <menuitem id="toolbarmenu_bodyText" label="&bodyTextCmd.label;" value=""/> + <menuitem id="toolbarmenu_paragraph" label="¶graphParagraphCmd.label;" value="p"/> + <menuitem id="toolbarmenu_h1" label="&heading1Cmd.label;" value="h1"/> + <menuitem id="toolbarmenu_h2" label="&heading2Cmd.label;" value="h2"/> + <menuitem id="toolbarmenu_h3" label="&heading3Cmd.label;" value="h3"/> + <menuitem id="toolbarmenu_h4" label="&heading4Cmd.label;" value="h4"/> + <menuitem id="toolbarmenu_h5" label="&heading5Cmd.label;" value="h5"/> + <menuitem id="toolbarmenu_h6" label="&heading6Cmd.label;" value="h6"/> + <menuitem id="toolbarmenu_address" label="¶graphAddressCmd.label;" value="address"/> + <menuitem id="toolbarmenu_pre" label="¶graphPreformatCmd.label;" value="pre"/> + </menupopup> + </menulist> +</toolbaritem> + + <!-- "value" are HTML tagnames, don't translate --> +<toolbaritem id="font-face-select-container" + class="formatting-button" + title="&FontFaceSelect.title;" + align="center" + removable="true" + tooltiptext="&FontFaceSelect.tooltip;" + observes="cmd_renderedHTMLEnabler"> + <menulist id="FontFaceSelect" + class="toolbar-focustarget" + crop="center" + sizetopopup="pref"> + <observes element="font-face-select-container" attribute="disabled"/> + <observes element="cmd_fontFace" attribute="state" onbroadcast="onFontFaceChange(this.parentNode, 'cmd_fontFace')"/> + <menupopup id="FontFacePopup" oncommand="doStatefulCommand('cmd_fontFace', event.target.value)"> + <menuitem id="toolbarmenu_fontVarWidth" label="&fontVarWidth.label;" value=""/> + <menuitem id="toolbarmenu_fontFixedWidth" label="&fontFixedWidth.label;" value="tt"/> + <menuseparator id="toolbarmenuAfterGenericFontsSeparator"/> + <menuitem id="toolbarmenu_fontHelvetica" label="&fontHelvetica.label;" + value="Helvetica, Arial, sans-serif" + value_parsed="helvetica,arial,sans-serif"/> + <menuitem id="toolbarmenu_fontTimes" label="&fontTimes.label;" + value="Times New Roman, Times, serif" + value_parsed="times new roman,times,serif"/> + <menuitem id="toolbarmenu_fontCourier" label="&fontCourier.label;" + value="Courier New, Courier, monospace" + value_parsed="courier new,courier,monospace"/> + <menuseparator id="toolbarmenuAfterDefaultFontsSeparator" + class="fontFaceMenuAfterDefaultFonts"/> + <menuseparator id="toolbarmenuAfterUsedFontsSeparator" + class="fontFaceMenuAfterUsedFonts" + hidden="true"/> + <!-- Local font face items added here by initLocalFontFaceMenu() --> + </menupopup> + </menulist> +</toolbaritem> + +<toolbaritem id="font-size-select-container" + class="formatting-button" + title="&FontSizeSelect.title;" + align="center" + removable="true" + tooltiptext="&FontSizeSelect.tooltip;"> + <menulist id="FontSizeSelect" + class="toolbar-focustarget" + crop="right" + oncommand="EditorSelectFontSize();"> + <observes element="font-size-select-container" attribute="disabled"/> + <observes element="cmd_fontSize" attribute="state" onbroadcast="onFontSizeChange(this.parentNode, 'cmd_fontSize')"/> + <menupopup id="FontSizePopup"> + <menuitem id="toobarmenu_fontSize_x-small" label="&size-tinyCmd.label;"/> + <menuitem id="toobarmenu_fontSize_small" label="&size-smallCmd.label;"/> + <menuitem id="toobarmenu_fontSize_medium" label="&size-mediumCmd.label;"/> + <menuitem id="toobarmenu_fontSize_large" label="&size-largeCmd.label;"/> + <menuitem id="toobarmenu_fontSize_x-large" label="&size-extraLargeCmd.label;"/> + <menuitem id="toobarmenu_fontSize_xx-large" label="&size-hugeCmd.label;"/> + </menupopup> + </menulist> +</toolbaritem> + +<toolbaritem id="color-buttons-container" + align="center" + title="&colorButtons.title;" + removable="true" + class="formatting-button"> + <stack id="ColorButtons" align="center"> + <observes element="cmd_fontColor" attribute="state" onbroadcast="onFontColorChange()"/> + <observes element="cmd_backgroundColor" attribute="state" onbroadcast="onBackgroundColorChange()"/> + <box class="color-button" id="BackgroundColorButton" + onclick="EditorSelectColor('', event);" + tooltiptext="&BackgroundColorButton.tooltip;"/> + <box class="color-button" id="TextColorButton" + onclick="EditorSelectColor('Text', event);" + tooltiptext="&TextColorButton.tooltip;"/> + </stack> +</toolbaritem> + <toolbarbutton id="HighlightColorButton" + class="formatting-button" + label="&HighlightColorButton.label;" + removable="true" + tooltiptext="&HighlightColorButton.tooltip;" + command="cmd_highlight"> + <observes element="cmd_highlight" attribute="state" onbroadcast="onHighlightColorChange()"/> + <observes element="cmd_highlight" attribute="collapsed"/> + </toolbarbutton> + + <toolbarbutton id="AbsoluteFontSizeButton" + class="formatting-button" + label="&absoluteFontSize.label;" + removable="true" + tooltiptext="&absoluteFontSizeToolbarCmd.tooltip;" + type="menu" + observes="cmd_renderedHTMLEnabler"> + <menupopup id="AbsoluteFontSizeButtonPopup" + onpopupshowing="initFontSizeMenu(this, false);"> + <menuitem id="toobarmenu_fontSize_x-small" + label="&size-tinyCmd.label;" + type="radio" name="1" + oncommand="EditorSetFontSize('x-small')"/> + <menuitem id="toobarmenu_fontSize_small" + label="&size-smallCmd.label;" + type="radio" name="1" + oncommand="EditorSetFontSize('small')"/> + <menuitem id="toobarmenu_fontSize_medium" + label="&size-mediumCmd.label;" + type="radio" name="1" + oncommand="EditorSetFontSize('medium')"/> + <menuitem id="toobarmenu_fontSize_large" + label="&size-largeCmd.label;" + type="radio" name="1" + oncommand="EditorSetFontSize('large')"/> + <menuitem id="toobarmenu_fontSize_x-large" + label="&size-extraLargeCmd.label;" + type="radio" name="1" + oncommand="EditorSetFontSize('x-large')"/> + <menuitem id="toobarmenu_fontSize_xx-large" + label="&size-hugeCmd.label;" + type="radio" name="1" + oncommand="EditorSetFontSize('xx-large')"/> + <!-- Enable if required for SeaMonkey. + <menuitem id="toobarmenu_fontSize_smallBigInfo" + type="checkbox" name="2" disabled="true" hidden="true"/> + --> + </menupopup> + </toolbarbutton> + <toolbarbutton id="DecreaseFontSizeButton" + class="formatting-button" + label="&smaller.label;" + removable="true" + tooltiptext="&decreaseFontSizeToolbarCmd.tooltip;" + command="cmd_decreaseFontStep"/> + <toolbarbutton id="IncreaseFontSizeButton" + class="formatting-button" + label="&larger.label;" + removable="true" + tooltiptext="&increaseFontSizeToolbarCmd.tooltip;" + command="cmd_increaseFontStep"/> + <toolbarbutton id="boldButton" + class="formatting-button" + label="&bold.label;" + removable="true" + tooltiptext="&boldToolbarCmd.tooltip;" + type="checkbox" + autoCheck="false" + command="cmd_bold"> + <observes element="cmd_bold" type="checkbox" attribute="state" onbroadcast="onButtonUpdate(this.parentNode, 'cmd_bold')"/> + </toolbarbutton> + <toolbarbutton id="italicButton" + class="formatting-button" + label="&italic.label;" + removable="true" + tooltiptext="&italicToolbarCmd.tooltip;" + type="checkbox" + autoCheck="false" + command="cmd_italic"> + <observes element="cmd_italic" attribute="state" onbroadcast="onButtonUpdate(this.parentNode, 'cmd_italic')"/> + </toolbarbutton> + <toolbarbutton id="underlineButton" + class="formatting-button" + label="&underline.label;" + removable="true" + tooltiptext="&underlineToolbarCmd.tooltip;" + type="checkbox" + autoCheck="false" + command="cmd_underline"> + <observes element="cmd_underline" attribute="state" onbroadcast="onButtonUpdate(this.parentNode, 'cmd_underline')"/> + </toolbarbutton> + + <toolbarbutton id="ulButton" + class="formatting-button" + label="&bullets.label;" + removable="true" + tooltiptext="&bulletListToolbarCmd.tooltip;" + type="radio" + group="lists" + autoCheck="false" + command="cmd_ul"> + <observes element="cmd_ul" attribute="state" onbroadcast="onButtonUpdate(this.parentNode, 'cmd_ul')"/> + </toolbarbutton> + + <toolbarbutton id="olButton" + class="formatting-button" + label="&numbers.label;" + removable="true" + tooltiptext="&numberListToolbarCmd.tooltip;" + type="radio" + group="lists" + autoCheck="false" + command="cmd_ol"> + <observes element="cmd_ol" attribute="state" onbroadcast="onButtonUpdate(this.parentNode, 'cmd_ol')"/> + </toolbarbutton> + + <toolbarbutton id="outdentButton" + class="formatting-button" + label="&outdent.label;" + removable="true" + tooltiptext="&outdentToolbarCmd.tooltip;" + command="cmd_outdent"/> + <toolbarbutton id="indentButton" + class="formatting-button" + label="&indent.label;" + removable="true" + tooltiptext="&indentToolbarCmd.tooltip;" + command="cmd_indent"/> + + <!-- alignment buttons --> + <toolbarbutton id="align-left-button" + class="formatting-button" + label="&alignLeftButton.label;" + removable="true" + tooltiptext="&alignLeftButton.tooltip;" + type="radio" + group="align" + autoCheck="false" + oncommand="doStatefulCommand('cmd_align', 'left')"> + <observes element="cmd_align" attribute="state" + onbroadcast="onStateButtonUpdate(this.parentNode, 'cmd_align', 'left')" /> + </toolbarbutton> + <toolbarbutton id="align-center-button" + class="formatting-button" + label="&alignCenterButton.label;" + removable="true" + tooltiptext="&alignCenterButton.tooltip;" + type="radio" + group="align" + autoCheck="false" + oncommand="doStatefulCommand('cmd_align', 'center')"> + <observes element="cmd_align" attribute="state" + onbroadcast="onStateButtonUpdate(this.parentNode, 'cmd_align', 'center')"/> + </toolbarbutton> + <toolbarbutton id="align-right-button" + class="formatting-button" + label="&alignRightButton.label;" + removable="true" + tooltiptext="&alignRightButton.tooltip;" + type="radio" + group="align" + autoCheck="false" + oncommand="doStatefulCommand('cmd_align', 'right')"> + <observes element="cmd_align" attribute="state" + onbroadcast="onStateButtonUpdate(this.parentNode, 'cmd_align', 'right')"/> + </toolbarbutton> + <toolbarbutton id="align-justify-button" + class="formatting-button" + label="&alignJustifyButton.label;" + removable="true" + tooltiptext="&alignJustifyButton.tooltip;" + type="radio" + group="align" + autoCheck="false" + oncommand="doStatefulCommand('cmd_align', 'justify')"> + <observes element="cmd_align" attribute="state" + onbroadcast="onStateButtonUpdate(this.parentNode, 'cmd_align', 'justify')"/> + </toolbarbutton> + + <toolbarbutton id="absolutePositionButton" + class="formatting-button" + label="&absolutePosition.label;" + removable="true" + tooltiptext="&layer.tooltip;" + type="checkbox" + command="cmd_absPos"> + <observes element="cmd_absPos" attribute="state" onbroadcast="onStateButtonUpdate(this.parentNode, 'cmd_absPos', 'absolute')"/> + </toolbarbutton> + <toolbarbutton id="decreaseZIndexButton" + class="formatting-button" + label="&decreaseZIndex.label;" + removable="true" + tooltiptext="&layerSendToBack.tooltip;" + command="cmd_decreaseZIndex"/> + <toolbarbutton id="increaseZIndexButton" + class="formatting-button" + label="&increaseZIndex.label;" + removable="true" + tooltiptext="&layerBringToFront.tooltip;" + command="cmd_increaseZIndex"/> + + <!-- smiley menu --> + <toolbarbutton id="smileButtonMenu" + class="formatting-button" + label="&SmileButton.label;" + removable="true" + tooltiptext="&SmileButton.tooltip;" + type="menu" + observes="cmd_smiley"> + <menupopup id="smilyPopup"> + <menuitem class="smiley insert-smile menuitem-iconic" + label="&smiley1Cmd.label;" + tooltiptext="&smiley1Cmd.tooltip;" + oncommand="doStatefulCommand('cmd_smiley', ':-)');"/> + <menuitem class="smiley insert-frown menuitem-iconic" + label="&smiley2Cmd.label;" + tooltiptext="&smiley2Cmd.tooltip;" + oncommand="doStatefulCommand('cmd_smiley', ':-(');"/> + <menuitem class="smiley insert-wink menuitem-iconic" + label="&smiley3Cmd.label;" + tooltiptext="&smiley3Cmd.tooltip;" + oncommand="doStatefulCommand('cmd_smiley', ';-)');"/> + <menuitem class="smiley insert-tongue menuitem-iconic" + label="&smiley4Cmd.label;" + tooltiptext="&smiley4Cmd.tooltip;" + oncommand="doStatefulCommand('cmd_smiley', ':-P');"/> + <menuitem class="smiley insert-laughing menuitem-iconic" + label="&smiley5Cmd.label;" + tooltiptext="&smiley5Cmd.tooltip;" + oncommand="doStatefulCommand('cmd_smiley', ':-D');"/> + <menuitem class="smiley insert-embarrassed menuitem-iconic" + label="&smiley6Cmd.label;" + tooltiptext="&smiley6Cmd.tooltip;" + oncommand="doStatefulCommand('cmd_smiley', ':-[');"/> + <menuitem class="smiley insert-undecided menuitem-iconic" + label="&smiley7Cmd.label;" + tooltiptext="&smiley7Cmd.tooltip;" + oncommand="doStatefulCommand('cmd_smiley', ':-\\');"/> + <menuitem class="smiley insert-surprise menuitem-iconic" + label="&smiley8Cmd.label;" + tooltiptext="&smiley8Cmd.tooltip;" + oncommand="doStatefulCommand('cmd_smiley', '=-O');"/> + <menuitem class="smiley insert-kiss menuitem-iconic" + label="&smiley9Cmd.label;" + tooltiptext="&smiley9Cmd.tooltip;" + oncommand="doStatefulCommand('cmd_smiley', ':-*');"/> + <menuitem class="smiley insert-yell menuitem-iconic" + label="&smiley10Cmd.label;" + tooltiptext="&smiley10Cmd.tooltip;" + oncommand="doStatefulCommand('cmd_smiley', '>:o');"/> + <menuitem class="smiley insert-cool menuitem-iconic" + label="&smiley11Cmd.label;" + tooltiptext="&smiley11Cmd.tooltip;" + oncommand="doStatefulCommand('cmd_smiley', '8-)');"/> + <menuitem class="smiley insert-money menuitem-iconic" + label="&smiley12Cmd.label;" + tooltiptext="&smiley12Cmd.tooltip;" + oncommand="doStatefulCommand('cmd_smiley', ':-$');"/> + <menuitem class="smiley insert-foot menuitem-iconic" + label="&smiley13Cmd.label;" + tooltiptext="&smiley13Cmd.tooltip;" + oncommand="doStatefulCommand('cmd_smiley', ':-!');"/> + <menuitem class="smiley insert-innocent menuitem-iconic" + label="&smiley14Cmd.label;" + tooltiptext="&smiley14Cmd.tooltip;" + oncommand="doStatefulCommand('cmd_smiley', 'O:-)');"/> + <menuitem class="smiley insert-cry menuitem-iconic" + label="&smiley15Cmd.label;" + tooltiptext="&smiley15Cmd.tooltip;" + oncommand="doStatefulCommand('cmd_smiley', ':\'(');"/> + <menuitem class="smiley insert-sealed menuitem-iconic" + label="&smiley16Cmd.label;" + tooltiptext="&smiley16Cmd.tooltip;" + oncommand="doStatefulCommand('cmd_smiley', ':-X');"/> + </menupopup> + </toolbarbutton> +</overlay> diff --git a/comm/suite/editor/base/content/editorTasksOverlay.xhtml b/comm/suite/editor/base/content/editorTasksOverlay.xhtml new file mode 100644 index 0000000000..d3fcb2d4b2 --- /dev/null +++ b/comm/suite/editor/base/content/editorTasksOverlay.xhtml @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<!DOCTYPE overlay SYSTEM "chrome://communicator/locale/tasksOverlay.dtd"> + +<overlay id="editorTasksOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"> + + <script src="chrome://editor/content/editorApplicationOverlay.js"/> + + <keyset id="tasksKeys"> + <key id="key_editor" key="&editorCmd.commandkey;" command="Tasks:Editor" modifiers="accel"/> + </keyset> + + <commandset id="tasksCommands"> + <command id="Tasks:Editor" oncommand="toEditor();"/> + </commandset> + + <hbox id="component-bar" class="statusbarpanel"> + <toolbarbutton class="taskbutton" id="mini-comp" command="Tasks:Editor" + tooltiptext="&taskComposer.tooltip;" insertafter="mini-nav"/> + </hbox> + + <menupopup id="windowPopup"> + <menuitem label="&editorCmd.label;" accesskey="&editorCmd.accesskey;" key="key_editor" command="Tasks:Editor" id="tasksMenuEditor" insertafter="IMMenuItem,tasksMenuNavigator" class="menuitem-iconic icon-composer16 menu-iconic"/> + </menupopup> + +</overlay> + diff --git a/comm/suite/editor/base/content/editorUtilities.js b/comm/suite/editor/base/content/editorUtilities.js new file mode 100644 index 0000000000..ea06446e45 --- /dev/null +++ b/comm/suite/editor/base/content/editorUtilities.js @@ -0,0 +1,1014 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from editor.js */ + +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var { AppConstants } = ChromeUtils.import( + "resource://gre/modules/AppConstants.jsm" +); + +// Each editor window must include this file +// Variables shared by all dialogs: + +// Object to attach commonly-used widgets (all dialogs should use this) +var gDialog = {}; + +var kOutputEncodeBasicEntities = + Ci.nsIDocumentEncoder.OutputEncodeBasicEntities; +var kOutputEncodeHTMLEntities = Ci.nsIDocumentEncoder.OutputEncodeHTMLEntities; +var kOutputEncodeLatin1Entities = + Ci.nsIDocumentEncoder.OutputEncodeLatin1Entities; +var kOutputEncodeW3CEntities = Ci.nsIDocumentEncoder.OutputEncodeW3CEntities; +var kOutputFormatted = Ci.nsIDocumentEncoder.OutputFormatted; +var kOutputLFLineBreak = Ci.nsIDocumentEncoder.OutputLFLineBreak; +var kOutputSelectionOnly = Ci.nsIDocumentEncoder.OutputSelectionOnly; +var kOutputWrap = Ci.nsIDocumentEncoder.OutputWrap; + +var gStringBundle; +var gFilePickerDirectory; + +/** *********** Message dialogs ***************/ + +// Optional: Caller may supply text to substitute for "Ok" and/or "Cancel" +function ConfirmWithTitle(title, message, okButtonText, cancelButtonText) { + let okFlag = okButtonText + ? Services.prompt.BUTTON_TITLE_IS_STRING + : Services.prompt.BUTTON_TITLE_OK; + let cancelFlag = cancelButtonText + ? Services.prompt.BUTTON_TITLE_IS_STRING + : Services.prompt.BUTTON_TITLE_CANCEL; + + return ( + Services.prompt.confirmEx( + window, + title, + message, + okFlag * Services.prompt.BUTTON_POS_0 + + cancelFlag * Services.prompt.BUTTON_POS_1, + okButtonText, + cancelButtonText, + null, + null, + { value: 0 } + ) == 0 + ); +} + +/** *********** String Utilities ***************/ + +function GetString(name) { + if (!gStringBundle) { + try { + gStringBundle = Services.strings.createBundle( + "chrome://editor/locale/editor.properties" + ); + } catch (ex) {} + } + if (gStringBundle) { + try { + return gStringBundle.GetStringFromName(name); + } catch (e) {} + } + return null; +} + +function GetFormattedString(aName, aVal) { + if (!gStringBundle) { + try { + gStringBundle = Services.strings.createBundle( + "chrome://editor/locale/editor.properties" + ); + } catch (ex) {} + } + if (gStringBundle) { + try { + return gStringBundle.formatStringFromName(aName, [aVal]); + } catch (e) {} + } + return null; +} + +function TrimStringLeft(string) { + if (!string) { + return ""; + } + return string.trimLeft(); +} + +function TrimStringRight(string) { + if (!string) { + return ""; + } + return string.trimRight(); +} + +// Remove whitespace from both ends of a string +function TrimString(string) { + if (!string) { + return ""; + } + return string.trim(); +} + +function TruncateStringAtWordEnd(string, maxLength, addEllipses) { + // Return empty if string is null, undefined, or the empty string + if (!string) { + return ""; + } + + // We assume they probably don't want whitespace at the beginning + string = string.trimLeft(); + if (string.length <= maxLength) { + return string; + } + + // We need to truncate the string to maxLength or fewer chars + if (addEllipses) { + maxLength -= 3; + } + string = string.replace(RegExp("(.{0," + maxLength + "})\\s.*"), "$1"); + + if (string.length > maxLength) { + string = string.slice(0, maxLength); + } + + if (addEllipses) { + string += "..."; + } + return string; +} + +// Replace all whitespace characters with supplied character +// E.g.: Use charReplace = " ", to "unwrap" the string by removing line-end chars +// Use charReplace = "_" when you don't want spaces (like in a URL) +function ReplaceWhitespace(string, charReplace) { + return string.trim().replace(/\s+/g, charReplace); +} + +// Replace whitespace with "_" and allow only HTML CDATA +// characters: "a"-"z","A"-"Z","0"-"9", "_", ":", "-", ".", +// and characters above ASCII 127 +function ConvertToCDATAString(string) { + return string + .replace(/\s+/g, "_") + .replace(/[^a-zA-Z0-9_\.\-\:\u0080-\uFFFF]+/g, ""); +} + +function GetSelectionAsText() { + try { + return GetCurrentEditor().outputToString( + "text/plain", + kOutputSelectionOnly + ); + } catch (e) {} + + return ""; +} + +/** *********** Get Current Editor and associated interfaces or info ***************/ +const nsIHTMLEditor = Ci.nsIHTMLEditor; +const nsITableEditor = Ci.nsITableEditor; +const nsIEditorStyleSheets = Ci.nsIEditorStyleSheets; +const nsIEditingSession = Ci.nsIEditingSession; + +function GetCurrentEditor() { + // Get the active editor from the <editor> tag + // XXX This will probably change if we support > 1 editor in main Composer window + // (e.g. a plaintext editor for HTMLSource) + + // For dialogs: Search up parent chain to find top window with editor + var editor; + try { + var editorElement = GetCurrentEditorElement(); + editor = editorElement.getEditor(editorElement.contentWindow); + + // Do QIs now so editor users won't have to figure out which interface to use + // Using "instanceof" does the QI for us. + editor instanceof Ci.nsIHTMLEditor; + } catch (e) { + dump(e) + "\n"; + } + + return editor; +} + +function GetCurrentTableEditor() { + var editor = GetCurrentEditor(); + return editor && editor instanceof nsITableEditor ? editor : null; +} + +function GetCurrentEditorElement() { + var tmpWindow = window; + + do { + // Get the <editor> element(s) + let editorItem = tmpWindow.document.querySelector("editor"); + + // This will change if we support > 1 editor element + if (editorItem) { + return editorItem; + } + + tmpWindow = tmpWindow.opener; + } while (tmpWindow); + + return null; +} + +function GetCurrentCommandManager() { + try { + return GetCurrentEditorElement().commandManager; + } catch (e) { + dump(e) + "\n"; + } + + return null; +} + +function GetCurrentEditorType() { + try { + return GetCurrentEditorElement().editortype; + } catch (e) { + dump(e) + "\n"; + } + + return ""; +} + +function IsHTMLEditor() { + // We don't have an editorElement, just return false + if (!GetCurrentEditorElement()) { + return false; + } + + var editortype = GetCurrentEditorType(); + switch (editortype) { + case "html": + case "htmlmail": + return true; + + case "text": + case "textmail": + return false; + + default: + dump("INVALID EDITOR TYPE: " + editortype + "\n"); + break; + } + return false; +} + +function PageIsEmptyAndUntouched() { + return IsDocumentEmpty() && !IsDocumentModified() && !IsHTMLSourceChanged(); +} + +function IsInHTMLSourceMode() { + return gEditorDisplayMode == kDisplayModeSource; +} + +function IsInPreviewMode() { + return gEditorDisplayMode == kDisplayModePreview; +} + +// are we editing HTML (i.e. neither in HTML source mode, nor editing a text file) +function IsEditingRenderedHTML() { + return IsHTMLEditor() && !IsInHTMLSourceMode(); +} + +function IsWebComposer() { + return document.documentElement.id == "editorWindow"; +} + +function IsDocumentEditable() { + try { + return GetCurrentEditor().isDocumentEditable; + } catch (e) {} + return false; +} + +function IsDocumentEmpty() { + try { + return GetCurrentEditor().documentIsEmpty; + } catch (e) {} + return false; +} + +function IsDocumentModified() { + try { + return GetCurrentEditor().documentModified; + } catch (e) {} + return false; +} + +function IsHTMLSourceChanged() { + // gSourceTextEditor will not be defined if we're just a text editor. + return gSourceTextEditor ? gSourceTextEditor.documentModified : false; +} + +function newCommandParams() { + try { + return Cu.createCommandParams(); + } catch (e) { + dump("error thrown in newCommandParams: " + e + "\n"); + } + return null; +} + +/** *********** General editing command utilities ***************/ + +function GetDocumentTitle() { + try { + return GetCurrentEditorElement().contentDocument.title; + } catch (e) {} + + return ""; +} + +function SetDocumentTitle(title) { + try { + GetCurrentEditorElement().contentDocument.title = title; + + // Update window title (doesn't work if called from a dialog) + if ("UpdateWindowTitle" in window) { + window.UpdateWindowTitle(); + } + } catch (e) {} +} + +function EditorGetTextProperty( + property, + attribute, + value, + firstHas, + anyHas, + allHas +) { + try { + return GetCurrentEditor().getInlinePropertyWithAttrValue( + property, + attribute, + value, + firstHas, + anyHas, + allHas + ); + } catch (e) {} +} + +function EditorSetTextProperty(property, attribute, value) { + try { + GetCurrentEditor().setInlineProperty(property, attribute, value); + if ("gContentWindow" in window) { + window.gContentWindow.focus(); + } + } catch (e) {} +} + +function EditorRemoveTextProperty(property, attribute) { + try { + GetCurrentEditor().removeInlineProperty(property, attribute); + if ("gContentWindow" in window) { + window.gContentWindow.focus(); + } + } catch (e) {} +} + +/** *********** Element enbabling/disabling ***************/ + +// this function takes an elementID and a flag +// if the element can be found by ID, then it is either enabled (by removing "disabled" attr) +// or disabled (setAttribute) as specified in the "doEnable" parameter +function SetElementEnabledById(elementID, doEnable) { + SetElementEnabled(document.getElementById(elementID), doEnable); +} + +function SetElementEnabled(element, doEnable) { + if (element) { + if (doEnable) { + element.removeAttribute("disabled"); + } else { + element.setAttribute("disabled", "true"); + } + } else { + dump("Element not found in SetElementEnabled\n"); + } +} + +/** *********** Services / Prefs ***************/ + +function GetFileProtocolHandler() { + let handler = Services.io.getProtocolHandler("file"); + return handler.QueryInterface(Ci.nsIFileProtocolHandler); +} + +function SetStringPref(aPrefName, aPrefValue) { + try { + Services.prefs.setStringPref(aPrefName, aPrefValue); + } catch (e) {} +} + +// Set initial directory for a filepicker from URLs saved in prefs +function SetFilePickerDirectory(filePicker, fileType) { + if (filePicker) { + try { + // Save current directory so we can reset it in SaveFilePickerDirectory + gFilePickerDirectory = filePicker.displayDirectory; + + let location = Services.prefs.getComplexValue( + "editor.lastFileLocation." + fileType, + Ci.nsIFile + ); + if (location) { + filePicker.displayDirectory = location; + } + } catch (e) {} + } +} + +// Save the directory of the selected file to prefs +function SaveFilePickerDirectory(filePicker, fileType) { + if (filePicker && filePicker.file) { + try { + var fileDir; + if (filePicker.file.parent) { + fileDir = filePicker.file.parent.QueryInterface(Ci.nsIFile); + } + + Services.prefs.setComplexValue( + "editor.lastFileLocation." + fileType, + Ci.nsIFile, + fileDir + ); + + Services.prefs.savePrefFile(null); + } catch (e) {} + } + + // Restore the directory used before SetFilePickerDirectory was called; + // This reduces interference with Browser and other module directory defaults + if (gFilePickerDirectory) { + filePicker.displayDirectory = gFilePickerDirectory; + } + + gFilePickerDirectory = null; +} + +function GetDefaultBrowserColors() { + var colors = { + TextColor: 0, + BackgroundColor: 0, + LinkColor: 0, + ActiveLinkColor: 0, + VisitedLinkColor: 0, + }; + var useSysColors = Services.prefs.getBoolPref( + "browser.display.use_system_colors", + false + ); + + if (!useSysColors) { + colors.TextColor = Services.prefs.getCharPref( + "browser.display.foreground_color", + 0 + ); + colors.BackgroundColor = Services.prefs.getCharPref( + "browser.display.background_color", + 0 + ); + } + // Use OS colors for text and background if explicitly asked or pref is not set + if (!colors.TextColor) { + colors.TextColor = "windowtext"; + } + + if (!colors.BackgroundColor) { + colors.BackgroundColor = "window"; + } + + colors.LinkColor = Services.prefs.getCharPref("browser.anchor_color"); + colors.ActiveLinkColor = Services.prefs.getCharPref("browser.active_color"); + colors.VisitedLinkColor = Services.prefs.getCharPref("browser.visited_color"); + + return colors; +} + +/** *********** URL handling ***************/ + +function TextIsURI(selectedText) { + return ( + selectedText && + /^http:\/\/|^https:\/\/|^file:\/\/|^ftp:\/\/|^about:|^mailto:|^news:|^snews:|^telnet:|^ldap:|^ldaps:|^gopher:|^finger:|^javascript:/i.test( + selectedText + ) + ); +} + +function IsUrlAboutBlank(urlString) { + return urlString == "about:blank"; +} + +function MakeRelativeUrl(url) { + let inputUrl = url.trim(); + if (!inputUrl) { + return inputUrl; + } + + // Get the filespec relative to current document's location + // NOTE: Can't do this if file isn't saved yet! + var docUrl = GetDocumentBaseUrl(); + var docScheme = GetScheme(docUrl); + + // Can't relativize if no doc scheme (page hasn't been saved) + if (!docScheme) { + return inputUrl; + } + + var urlScheme = GetScheme(inputUrl); + + // Do nothing if not the same scheme or url is already relativized + if (docScheme != urlScheme) { + return inputUrl; + } + + // Host must be the same + var docHost = GetHost(docUrl); + var urlHost = GetHost(inputUrl); + if (docHost != urlHost) { + return inputUrl; + } + + // Get just the file path part of the urls + // XXX Should we use GetCurrentEditor().documentCharacterSet for 2nd param ? + let docPath = Services.io.newURI( + docUrl, + GetCurrentEditor().documentCharacterSet + ).pathQueryRef; + let urlPath = Services.io.newURI( + inputUrl, + GetCurrentEditor().documentCharacterSet + ).pathQueryRef; + + // We only return "urlPath", so we can convert the entire docPath for + // case-insensitive comparisons. + var doCaseInsensitive = docScheme == "file" && AppConstants.platform == "win"; + if (doCaseInsensitive) { + docPath = docPath.toLowerCase(); + } + + // Get document filename before we start chopping up the docPath + var docFilename = GetFilename(docPath); + + // Both url and doc paths now begin with "/" + // Look for shared dirs starting after that + urlPath = urlPath.slice(1); + docPath = docPath.slice(1); + + var firstDirTest = true; + var nextDocSlash = 0; + var done = false; + + // Remove all matching subdirs common to both doc and input urls + do { + nextDocSlash = docPath.indexOf("/"); + var nextUrlSlash = urlPath.indexOf("/"); + + if (nextUrlSlash == -1) { + // We're done matching and all dirs in url + // what's left is the filename + done = true; + + // Remove filename for named anchors in the same file + if (nextDocSlash == -1 && docFilename) { + var anchorIndex = urlPath.indexOf("#"); + if (anchorIndex > 0) { + var urlFilename = doCaseInsensitive ? urlPath.toLowerCase() : urlPath; + + if (urlFilename.startsWith(docFilename)) { + urlPath = urlPath.slice(anchorIndex); + } + } + } + } else if (nextDocSlash >= 0) { + // Test for matching subdir + var docDir = docPath.slice(0, nextDocSlash); + var urlDir = urlPath.slice(0, nextUrlSlash); + if (doCaseInsensitive) { + urlDir = urlDir.toLowerCase(); + } + + if (urlDir == docDir) { + // Remove matching dir+"/" from each path + // and continue to next dir. + docPath = docPath.slice(nextDocSlash + 1); + urlPath = urlPath.slice(nextUrlSlash + 1); + } else { + // No match, we're done. + done = true; + + // Be sure we are on the same local drive or volume + // (the first "dir" in the path) because we can't + // relativize to different drives/volumes. + // UNIX doesn't have volumes, so we must not do this else + // the first directory will be misinterpreted as a volume name. + if ( + firstDirTest && + docScheme == "file" && + AppConstants.platform != "unix" + ) { + return inputUrl; + } + } + } else { + // No more doc dirs left, we're done + done = true; + } + + firstDirTest = false; + } while (!done); + + // Add "../" for each dir left in docPath + while (nextDocSlash > 0) { + urlPath = "../" + urlPath; + nextDocSlash = docPath.indexOf("/", nextDocSlash + 1); + } + return urlPath; +} + +function MakeAbsoluteUrl(url) { + let resultUrl = TrimString(url); + if (!resultUrl) { + return resultUrl; + } + + // Check if URL is already absolute, i.e., it has a scheme + let urlScheme = GetScheme(resultUrl); + + if (urlScheme) { + return resultUrl; + } + + let docUrl = GetDocumentBaseUrl(); + let docScheme = GetScheme(docUrl); + + // Can't relativize if no doc scheme (page hasn't been saved) + if (!docScheme) { + return resultUrl; + } + + // Make a URI object to use its "resolve" method + let absoluteUrl = resultUrl; + let docUri = Services.io.newURI( + docUrl, + GetCurrentEditor().documentCharacterSet + ); + + try { + absoluteUrl = docUri.resolve(resultUrl); + // This is deprecated and buggy! + // If used, we must make it a path for the parent directory (remove filename) + // absoluteUrl = IOService.resolveRelativePath(resultUrl, docUrl); + } catch (e) {} + + return absoluteUrl; +} + +// Get the HREF of the page's <base> tag or the document location +// returns empty string if no base href and document hasn't been saved yet +function GetDocumentBaseUrl() { + try { + var docUrl; + + // if document supplies a <base> tag, use that URL instead + let base = GetCurrentEditor().document.querySelector("base"); + if (base) { + docUrl = base.getAttribute("href"); + } + if (!docUrl) { + docUrl = GetDocumentUrl(); + } + + if (!IsUrlAboutBlank(docUrl)) { + return docUrl; + } + } catch (e) {} + return ""; +} + +function GetDocumentUrl() { + try { + return GetCurrentEditor().document.URL; + } catch (e) {} + return ""; +} + +// Extract the scheme (e.g., 'file', 'http') from a URL string +function GetScheme(urlspec) { + var resultUrl = TrimString(urlspec); + // Unsaved document URL has no acceptable scheme yet + if (!resultUrl || IsUrlAboutBlank(resultUrl)) { + return ""; + } + + var scheme = ""; + try { + // This fails if there's no scheme + scheme = Services.io.extractScheme(resultUrl); + } catch (e) {} + + return scheme ? scheme.toLowerCase() : ""; +} + +function GetHost(urlspec) { + if (!urlspec) { + return ""; + } + + var host = ""; + try { + host = Services.io.newURI(urlspec).host; + } catch (e) {} + + return host; +} + +function GetUsername(urlspec) { + if (!urlspec) { + return ""; + } + + var username = ""; + try { + username = Services.io.newURI(urlspec).username; + } catch (e) {} + + return username; +} + +function GetFilename(urlspec) { + if (!urlspec || IsUrlAboutBlank(urlspec)) { + return ""; + } + + var filename; + + try { + let uri = Services.io.newURI(urlspec); + if (uri) { + let url = uri.QueryInterface(Ci.nsIURL); + if (url) { + filename = url.fileName; + } + } + } catch (e) {} + + return filename ? filename : ""; +} + +// Return the url without username and password +// Optional output objects return extracted username and password strings +// This uses just string routines via nsIIOServices +function StripUsernamePassword(urlspec, usernameObj, passwordObj) { + urlspec = TrimString(urlspec); + if (!urlspec || IsUrlAboutBlank(urlspec)) { + return urlspec; + } + + if (usernameObj) { + usernameObj.value = ""; + } + if (passwordObj) { + passwordObj.value = ""; + } + + // "@" must exist else we will never detect username or password + var atIndex = urlspec.indexOf("@"); + if (atIndex > 0) { + try { + let uri = Services.io.newURI(urlspec); + let username = uri.username; + let password = uri.password; + + if (usernameObj && username) { + usernameObj.value = username; + } + if (passwordObj && password) { + passwordObj.value = password; + } + if (username) { + let usernameStart = urlspec.indexOf(username); + if (usernameStart != -1) { + return urlspec.slice(0, usernameStart) + urlspec.slice(atIndex + 1); + } + } + } catch (e) {} + } + return urlspec; +} + +function StripPassword(urlspec, passwordObj) { + urlspec = TrimString(urlspec); + if (!urlspec || IsUrlAboutBlank(urlspec)) { + return urlspec; + } + + if (passwordObj) { + passwordObj.value = ""; + } + + // "@" must exist else we will never detect password + var atIndex = urlspec.indexOf("@"); + if (atIndex > 0) { + try { + let password = Services.io.newURI(urlspec).password; + + if (passwordObj && password) { + passwordObj.value = password; + } + if (password) { + // Find last ":" before "@" + let colon = urlspec.lastIndexOf(":", atIndex); + if (colon != -1) { + // Include the "@" + return urlspec.slice(0, colon) + urlspec.slice(atIndex); + } + } + } catch (e) {} + } + return urlspec; +} + +// Version to use when you have an nsIURI object +function StripUsernamePasswordFromURI(uri) { + var urlspec = ""; + if (uri) { + try { + urlspec = uri.spec; + var userPass = uri.userPass; + if (userPass) { + let start = urlspec.indexOf(userPass); + urlspec = + urlspec.slice(0, start) + urlspec.slice(start + userPass.length + 1); + } + } catch (e) {} + } + return urlspec; +} + +function InsertUsernameIntoUrl(urlspec, username) { + if (!urlspec || !username) { + return urlspec; + } + + try { + let URI = Services.io.newURI( + urlspec, + GetCurrentEditor().documentCharacterSet + ); + URI.username = username; + return URI.spec; + } catch (e) {} + + return urlspec; +} + +function ConvertRGBColorIntoHEXColor(color) { + if (/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.test(color)) { + var r = Number(RegExp.$1).toString(16); + if (r.length == 1) { + r = "0" + r; + } + var g = Number(RegExp.$2).toString(16); + if (g.length == 1) { + g = "0" + g; + } + var b = Number(RegExp.$3).toString(16); + if (b.length == 1) { + b = "0" + b; + } + return "#" + r + g + b; + } + + return color; +} + +/** *********** CSS ***************/ + +function GetHTMLOrCSSStyleValue(element, attrName, cssPropertyName) { + var value; + if (Services.prefs.getBoolPref("editor.use_css") && IsHTMLEditor()) { + value = element.style.getPropertyValue(cssPropertyName); + } + + if (!value) { + value = element.getAttribute(attrName); + } + + if (!value) { + return ""; + } + + return value; +} + +/** *********** Miscellaneous ***************/ +// Clone simple JS objects +function Clone(obj) { + var clone = {}; + for (var i in obj) { + if (typeof obj[i] == "object") { + clone[i] = Clone(obj[i]); + } else { + clone[i] = obj[i]; + } + } + return clone; +} + +/** + * Utility functions to handle shortended data: URLs in EdColorProps.js and EdImageOverlay.js. + */ + +/** + * Is the passed in image URI a shortened data URI? + * @return {bool} + */ +function isImageDataShortened(aImageData) { + return /^data:/i.test(aImageData) && aImageData.includes("…"); +} + +/** + * Event handler for Copy or Cut + * @param aEvent the event + */ +function onCopyOrCutShortened(aEvent) { + // Put the original data URI onto the clipboard in case the value + // is a shortened data URI. + let field = aEvent.target; + let startPos = field.selectionStart; + if (startPos == undefined) { + return; + } + let endPos = field.selectionEnd; + let selection = field.value.substring(startPos, endPos).trim(); + + // Test that a) the user selected the whole value, + // b) the value is a data URI, + // c) it contains the ellipsis we added. Otherwise it could be + // a new value that the user pasted in. + if (selection == field.value.trim() && isImageDataShortened(selection)) { + aEvent.clipboardData.setData("text/plain", field.fullDataURI); + if (aEvent.type == "cut") { + // We have to cut the selection manually. Since we tested that + // everything was selected, we can just reset the field. + field.value = ""; + } + aEvent.preventDefault(); + } +} + +/** + * Set up element showing an image URI with a shortened version. + * and add event handler for Copy or Cut. + * + * @param aImageData the data: URL of the image to be shortened. + * Note: Original stored in 'aDialogField.fullDataURI'. + * @param aDialogField The field of the dialog to contain the data. + * @return {bool} URL was shortened? + */ +function shortenImageData(aImageData, aDialogField) { + let shortened = false; + aDialogField.value = aImageData.replace(/^(data:.+;base64,)(.*)/i, function( + match, + nonDataPart, + dataPart + ) { + if (dataPart.length <= 35) { + return match; + } + + shortened = true; + aDialogField.addEventListener("copy", onCopyOrCutShortened); + aDialogField.addEventListener("cut", onCopyOrCutShortened); + aDialogField.fullDataURI = aImageData; + aDialogField.removeAttribute("tooltiptext"); + aDialogField.setAttribute("tooltip", "shortenedDataURI"); + return ( + nonDataPart + + dataPart.substring(0, 5) + + "…" + + dataPart.substring(dataPart.length - 30) + ); + }); + return shortened; +} + +/** + * Return full data URIs for a shortened element. + * + * @param aDialogField The field of the dialog containing the data. + */ +function restoredImageData(aDialogField) { + return aDialogField.fullDataURI; +} diff --git a/comm/suite/editor/base/content/images/bringtofront-disabled.png b/comm/suite/editor/base/content/images/bringtofront-disabled.png Binary files differnew file mode 100644 index 0000000000..ee8bfb0185 --- /dev/null +++ b/comm/suite/editor/base/content/images/bringtofront-disabled.png diff --git a/comm/suite/editor/base/content/images/bringtofront.png b/comm/suite/editor/base/content/images/bringtofront.png Binary files differnew file mode 100644 index 0000000000..ab22be7e66 --- /dev/null +++ b/comm/suite/editor/base/content/images/bringtofront.png diff --git a/comm/suite/editor/base/content/images/sendtoback-disabled.png b/comm/suite/editor/base/content/images/sendtoback-disabled.png Binary files differnew file mode 100644 index 0000000000..fe1e0502b2 --- /dev/null +++ b/comm/suite/editor/base/content/images/sendtoback-disabled.png diff --git a/comm/suite/editor/base/content/images/sendtoback.png b/comm/suite/editor/base/content/images/sendtoback.png Binary files differnew file mode 100644 index 0000000000..5aa02b7f0b --- /dev/null +++ b/comm/suite/editor/base/content/images/sendtoback.png diff --git a/comm/suite/editor/base/content/images/tag-a.png b/comm/suite/editor/base/content/images/tag-a.png Binary files differnew file mode 100644 index 0000000000..e66eb1db47 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-a.png diff --git a/comm/suite/editor/base/content/images/tag-abr.png b/comm/suite/editor/base/content/images/tag-abr.png Binary files differnew file mode 100644 index 0000000000..a04af50b35 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-abr.png diff --git a/comm/suite/editor/base/content/images/tag-acr.png b/comm/suite/editor/base/content/images/tag-acr.png Binary files differnew file mode 100644 index 0000000000..75cc3a5a9a --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-acr.png diff --git a/comm/suite/editor/base/content/images/tag-adr.png b/comm/suite/editor/base/content/images/tag-adr.png Binary files differnew file mode 100644 index 0000000000..63b95a3e02 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-adr.png diff --git a/comm/suite/editor/base/content/images/tag-anchor.png b/comm/suite/editor/base/content/images/tag-anchor.png Binary files differnew file mode 100644 index 0000000000..5b116c668c --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-anchor.png diff --git a/comm/suite/editor/base/content/images/tag-app.png b/comm/suite/editor/base/content/images/tag-app.png Binary files differnew file mode 100644 index 0000000000..ad0c0cac30 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-app.png diff --git a/comm/suite/editor/base/content/images/tag-ara.png b/comm/suite/editor/base/content/images/tag-ara.png Binary files differnew file mode 100644 index 0000000000..6c8354fa45 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-ara.png diff --git a/comm/suite/editor/base/content/images/tag-b.png b/comm/suite/editor/base/content/images/tag-b.png Binary files differnew file mode 100644 index 0000000000..0a40231180 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-b.png diff --git a/comm/suite/editor/base/content/images/tag-bas.png b/comm/suite/editor/base/content/images/tag-bas.png Binary files differnew file mode 100644 index 0000000000..d86b376ed1 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-bas.png diff --git a/comm/suite/editor/base/content/images/tag-bdo.png b/comm/suite/editor/base/content/images/tag-bdo.png Binary files differnew file mode 100644 index 0000000000..13a0db68cd --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-bdo.png diff --git a/comm/suite/editor/base/content/images/tag-big.png b/comm/suite/editor/base/content/images/tag-big.png Binary files differnew file mode 100644 index 0000000000..1bf075320c --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-big.png diff --git a/comm/suite/editor/base/content/images/tag-blq.png b/comm/suite/editor/base/content/images/tag-blq.png Binary files differnew file mode 100644 index 0000000000..7faa4c2846 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-blq.png diff --git a/comm/suite/editor/base/content/images/tag-body.png b/comm/suite/editor/base/content/images/tag-body.png Binary files differnew file mode 100644 index 0000000000..df47443823 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-body.png diff --git a/comm/suite/editor/base/content/images/tag-br.png b/comm/suite/editor/base/content/images/tag-br.png Binary files differnew file mode 100644 index 0000000000..8e93c47db3 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-br.png diff --git a/comm/suite/editor/base/content/images/tag-bsf.png b/comm/suite/editor/base/content/images/tag-bsf.png Binary files differnew file mode 100644 index 0000000000..8b2b078619 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-bsf.png diff --git a/comm/suite/editor/base/content/images/tag-btn.png b/comm/suite/editor/base/content/images/tag-btn.png Binary files differnew file mode 100644 index 0000000000..2996ff9a74 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-btn.png diff --git a/comm/suite/editor/base/content/images/tag-cit.png b/comm/suite/editor/base/content/images/tag-cit.png Binary files differnew file mode 100644 index 0000000000..37624fe222 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-cit.png diff --git a/comm/suite/editor/base/content/images/tag-clg.png b/comm/suite/editor/base/content/images/tag-clg.png Binary files differnew file mode 100644 index 0000000000..1c912ef1be --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-clg.png diff --git a/comm/suite/editor/base/content/images/tag-cod.png b/comm/suite/editor/base/content/images/tag-cod.png Binary files differnew file mode 100644 index 0000000000..5b7831f386 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-cod.png diff --git a/comm/suite/editor/base/content/images/tag-col.png b/comm/suite/editor/base/content/images/tag-col.png Binary files differnew file mode 100644 index 0000000000..834b57bb7b --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-col.png diff --git a/comm/suite/editor/base/content/images/tag-cpt.png b/comm/suite/editor/base/content/images/tag-cpt.png Binary files differnew file mode 100644 index 0000000000..4bcba8bf33 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-cpt.png diff --git a/comm/suite/editor/base/content/images/tag-ctr.png b/comm/suite/editor/base/content/images/tag-ctr.png Binary files differnew file mode 100644 index 0000000000..3e6aee0663 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-ctr.png diff --git a/comm/suite/editor/base/content/images/tag-dd.png b/comm/suite/editor/base/content/images/tag-dd.png Binary files differnew file mode 100644 index 0000000000..0b192b50ac --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-dd.png diff --git a/comm/suite/editor/base/content/images/tag-del.png b/comm/suite/editor/base/content/images/tag-del.png Binary files differnew file mode 100644 index 0000000000..0dd897c7be --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-del.png diff --git a/comm/suite/editor/base/content/images/tag-dfn.png b/comm/suite/editor/base/content/images/tag-dfn.png Binary files differnew file mode 100644 index 0000000000..ea820aeecc --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-dfn.png diff --git a/comm/suite/editor/base/content/images/tag-dir.png b/comm/suite/editor/base/content/images/tag-dir.png Binary files differnew file mode 100644 index 0000000000..3f20e2dd70 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-dir.png diff --git a/comm/suite/editor/base/content/images/tag-div.png b/comm/suite/editor/base/content/images/tag-div.png Binary files differnew file mode 100644 index 0000000000..8478e20f03 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-div.png diff --git a/comm/suite/editor/base/content/images/tag-dl.png b/comm/suite/editor/base/content/images/tag-dl.png Binary files differnew file mode 100644 index 0000000000..576b6f3968 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-dl.png diff --git a/comm/suite/editor/base/content/images/tag-dt.png b/comm/suite/editor/base/content/images/tag-dt.png Binary files differnew file mode 100644 index 0000000000..4c9121ebd5 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-dt.png diff --git a/comm/suite/editor/base/content/images/tag-em.png b/comm/suite/editor/base/content/images/tag-em.png Binary files differnew file mode 100644 index 0000000000..1a5f24551e --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-em.png diff --git a/comm/suite/editor/base/content/images/tag-fld.png b/comm/suite/editor/base/content/images/tag-fld.png Binary files differnew file mode 100644 index 0000000000..c299e5cad1 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-fld.png diff --git a/comm/suite/editor/base/content/images/tag-fnt.png b/comm/suite/editor/base/content/images/tag-fnt.png Binary files differnew file mode 100644 index 0000000000..eb8dba7c9e --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-fnt.png diff --git a/comm/suite/editor/base/content/images/tag-for.png b/comm/suite/editor/base/content/images/tag-for.png Binary files differnew file mode 100644 index 0000000000..bb38c428d0 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-for.png diff --git a/comm/suite/editor/base/content/images/tag-frm.png b/comm/suite/editor/base/content/images/tag-frm.png Binary files differnew file mode 100644 index 0000000000..5bd4689246 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-frm.png diff --git a/comm/suite/editor/base/content/images/tag-fst.png b/comm/suite/editor/base/content/images/tag-fst.png Binary files differnew file mode 100644 index 0000000000..269d5505f4 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-fst.png diff --git a/comm/suite/editor/base/content/images/tag-h1.png b/comm/suite/editor/base/content/images/tag-h1.png Binary files differnew file mode 100644 index 0000000000..2edff90dfe --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-h1.png diff --git a/comm/suite/editor/base/content/images/tag-h2.png b/comm/suite/editor/base/content/images/tag-h2.png Binary files differnew file mode 100644 index 0000000000..a55fb07cd4 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-h2.png diff --git a/comm/suite/editor/base/content/images/tag-h3.png b/comm/suite/editor/base/content/images/tag-h3.png Binary files differnew file mode 100644 index 0000000000..c8aa875994 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-h3.png diff --git a/comm/suite/editor/base/content/images/tag-h4.png b/comm/suite/editor/base/content/images/tag-h4.png Binary files differnew file mode 100644 index 0000000000..dd73041ff3 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-h4.png diff --git a/comm/suite/editor/base/content/images/tag-h5.png b/comm/suite/editor/base/content/images/tag-h5.png Binary files differnew file mode 100644 index 0000000000..1f3e94d5e3 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-h5.png diff --git a/comm/suite/editor/base/content/images/tag-h6.png b/comm/suite/editor/base/content/images/tag-h6.png Binary files differnew file mode 100644 index 0000000000..c2153ea2cc --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-h6.png diff --git a/comm/suite/editor/base/content/images/tag-hed.png b/comm/suite/editor/base/content/images/tag-hed.png Binary files differnew file mode 100644 index 0000000000..c1b87f447c --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-hed.png diff --git a/comm/suite/editor/base/content/images/tag-hr.png b/comm/suite/editor/base/content/images/tag-hr.png Binary files differnew file mode 100644 index 0000000000..b9d6a35a58 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-hr.png diff --git a/comm/suite/editor/base/content/images/tag-html.png b/comm/suite/editor/base/content/images/tag-html.png Binary files differnew file mode 100644 index 0000000000..0d1c9b361c --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-html.png diff --git a/comm/suite/editor/base/content/images/tag-i.png b/comm/suite/editor/base/content/images/tag-i.png Binary files differnew file mode 100644 index 0000000000..e75db74169 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-i.png diff --git a/comm/suite/editor/base/content/images/tag-ifr.png b/comm/suite/editor/base/content/images/tag-ifr.png Binary files differnew file mode 100644 index 0000000000..f212680ea4 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-ifr.png diff --git a/comm/suite/editor/base/content/images/tag-img.png b/comm/suite/editor/base/content/images/tag-img.png Binary files differnew file mode 100644 index 0000000000..f0b458e356 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-img.png diff --git a/comm/suite/editor/base/content/images/tag-inp.png b/comm/suite/editor/base/content/images/tag-inp.png Binary files differnew file mode 100644 index 0000000000..d9e81ea407 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-inp.png diff --git a/comm/suite/editor/base/content/images/tag-ins.png b/comm/suite/editor/base/content/images/tag-ins.png Binary files differnew file mode 100644 index 0000000000..a477f94b88 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-ins.png diff --git a/comm/suite/editor/base/content/images/tag-isx.png b/comm/suite/editor/base/content/images/tag-isx.png Binary files differnew file mode 100644 index 0000000000..4f53e9bf1d --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-isx.png diff --git a/comm/suite/editor/base/content/images/tag-kbd.png b/comm/suite/editor/base/content/images/tag-kbd.png Binary files differnew file mode 100644 index 0000000000..4945dfbd74 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-kbd.png diff --git a/comm/suite/editor/base/content/images/tag-lbl.png b/comm/suite/editor/base/content/images/tag-lbl.png Binary files differnew file mode 100644 index 0000000000..b1533723f1 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-lbl.png diff --git a/comm/suite/editor/base/content/images/tag-lgn.png b/comm/suite/editor/base/content/images/tag-lgn.png Binary files differnew file mode 100644 index 0000000000..c9d3149a9f --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-lgn.png diff --git a/comm/suite/editor/base/content/images/tag-li.png b/comm/suite/editor/base/content/images/tag-li.png Binary files differnew file mode 100644 index 0000000000..1d63b29e7c --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-li.png diff --git a/comm/suite/editor/base/content/images/tag-lnk.png b/comm/suite/editor/base/content/images/tag-lnk.png Binary files differnew file mode 100644 index 0000000000..58194ca38f --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-lnk.png diff --git a/comm/suite/editor/base/content/images/tag-lst.png b/comm/suite/editor/base/content/images/tag-lst.png Binary files differnew file mode 100644 index 0000000000..f79929c047 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-lst.png diff --git a/comm/suite/editor/base/content/images/tag-map.png b/comm/suite/editor/base/content/images/tag-map.png Binary files differnew file mode 100644 index 0000000000..9fc0dfe028 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-map.png diff --git a/comm/suite/editor/base/content/images/tag-men.png b/comm/suite/editor/base/content/images/tag-men.png Binary files differnew file mode 100644 index 0000000000..ccde7feec1 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-men.png diff --git a/comm/suite/editor/base/content/images/tag-met.png b/comm/suite/editor/base/content/images/tag-met.png Binary files differnew file mode 100644 index 0000000000..b6d86a7946 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-met.png diff --git a/comm/suite/editor/base/content/images/tag-nbr.png b/comm/suite/editor/base/content/images/tag-nbr.png Binary files differnew file mode 100644 index 0000000000..80ee8fd90c --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-nbr.png diff --git a/comm/suite/editor/base/content/images/tag-nfr.png b/comm/suite/editor/base/content/images/tag-nfr.png Binary files differnew file mode 100644 index 0000000000..885c530bf8 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-nfr.png diff --git a/comm/suite/editor/base/content/images/tag-nsc.png b/comm/suite/editor/base/content/images/tag-nsc.png Binary files differnew file mode 100644 index 0000000000..fdcde6f81e --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-nsc.png diff --git a/comm/suite/editor/base/content/images/tag-obj.png b/comm/suite/editor/base/content/images/tag-obj.png Binary files differnew file mode 100644 index 0000000000..05f80f0c87 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-obj.png diff --git a/comm/suite/editor/base/content/images/tag-ol.png b/comm/suite/editor/base/content/images/tag-ol.png Binary files differnew file mode 100644 index 0000000000..22456f8d2d --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-ol.png diff --git a/comm/suite/editor/base/content/images/tag-opg.png b/comm/suite/editor/base/content/images/tag-opg.png Binary files differnew file mode 100644 index 0000000000..9bcb0948fa --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-opg.png diff --git a/comm/suite/editor/base/content/images/tag-opt.png b/comm/suite/editor/base/content/images/tag-opt.png Binary files differnew file mode 100644 index 0000000000..46ec67560e --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-opt.png diff --git a/comm/suite/editor/base/content/images/tag-p.png b/comm/suite/editor/base/content/images/tag-p.png Binary files differnew file mode 100644 index 0000000000..0f49a89eec --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-p.png diff --git a/comm/suite/editor/base/content/images/tag-pln.png b/comm/suite/editor/base/content/images/tag-pln.png Binary files differnew file mode 100644 index 0000000000..e6d49b442c --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-pln.png diff --git a/comm/suite/editor/base/content/images/tag-pre.png b/comm/suite/editor/base/content/images/tag-pre.png Binary files differnew file mode 100644 index 0000000000..84423c484e --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-pre.png diff --git a/comm/suite/editor/base/content/images/tag-prm.png b/comm/suite/editor/base/content/images/tag-prm.png Binary files differnew file mode 100644 index 0000000000..e65d57ec1e --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-prm.png diff --git a/comm/suite/editor/base/content/images/tag-q.png b/comm/suite/editor/base/content/images/tag-q.png Binary files differnew file mode 100644 index 0000000000..a34e65d542 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-q.png diff --git a/comm/suite/editor/base/content/images/tag-s.png b/comm/suite/editor/base/content/images/tag-s.png Binary files differnew file mode 100644 index 0000000000..37564252ee --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-s.png diff --git a/comm/suite/editor/base/content/images/tag-scr.png b/comm/suite/editor/base/content/images/tag-scr.png Binary files differnew file mode 100644 index 0000000000..c8df1cefe1 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-scr.png diff --git a/comm/suite/editor/base/content/images/tag-slc.png b/comm/suite/editor/base/content/images/tag-slc.png Binary files differnew file mode 100644 index 0000000000..837b0eab89 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-slc.png diff --git a/comm/suite/editor/base/content/images/tag-sml.png b/comm/suite/editor/base/content/images/tag-sml.png Binary files differnew file mode 100644 index 0000000000..4df1639861 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-sml.png diff --git a/comm/suite/editor/base/content/images/tag-smp.png b/comm/suite/editor/base/content/images/tag-smp.png Binary files differnew file mode 100644 index 0000000000..e95e85d75f --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-smp.png diff --git a/comm/suite/editor/base/content/images/tag-spn.png b/comm/suite/editor/base/content/images/tag-spn.png Binary files differnew file mode 100644 index 0000000000..d1066e5248 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-spn.png diff --git a/comm/suite/editor/base/content/images/tag-stk.png b/comm/suite/editor/base/content/images/tag-stk.png Binary files differnew file mode 100644 index 0000000000..5700f9ed6e --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-stk.png diff --git a/comm/suite/editor/base/content/images/tag-stl.png b/comm/suite/editor/base/content/images/tag-stl.png Binary files differnew file mode 100644 index 0000000000..22fead4662 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-stl.png diff --git a/comm/suite/editor/base/content/images/tag-stn.png b/comm/suite/editor/base/content/images/tag-stn.png Binary files differnew file mode 100644 index 0000000000..9155590bfd --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-stn.png diff --git a/comm/suite/editor/base/content/images/tag-sub.png b/comm/suite/editor/base/content/images/tag-sub.png Binary files differnew file mode 100644 index 0000000000..f1ea4abbab --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-sub.png diff --git a/comm/suite/editor/base/content/images/tag-sup.png b/comm/suite/editor/base/content/images/tag-sup.png Binary files differnew file mode 100644 index 0000000000..a814d9f815 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-sup.png diff --git a/comm/suite/editor/base/content/images/tag-tbd.png b/comm/suite/editor/base/content/images/tag-tbd.png Binary files differnew file mode 100644 index 0000000000..e46c1931b3 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-tbd.png diff --git a/comm/suite/editor/base/content/images/tag-tbl.png b/comm/suite/editor/base/content/images/tag-tbl.png Binary files differnew file mode 100644 index 0000000000..cb553528f0 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-tbl.png diff --git a/comm/suite/editor/base/content/images/tag-td.png b/comm/suite/editor/base/content/images/tag-td.png Binary files differnew file mode 100644 index 0000000000..beebc393a4 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-td.png diff --git a/comm/suite/editor/base/content/images/tag-tft.png b/comm/suite/editor/base/content/images/tag-tft.png Binary files differnew file mode 100644 index 0000000000..cb0db0fe21 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-tft.png diff --git a/comm/suite/editor/base/content/images/tag-th.png b/comm/suite/editor/base/content/images/tag-th.png Binary files differnew file mode 100644 index 0000000000..dac140f41e --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-th.png diff --git a/comm/suite/editor/base/content/images/tag-thd.png b/comm/suite/editor/base/content/images/tag-thd.png Binary files differnew file mode 100644 index 0000000000..7b7325c2af --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-thd.png diff --git a/comm/suite/editor/base/content/images/tag-tr.png b/comm/suite/editor/base/content/images/tag-tr.png Binary files differnew file mode 100644 index 0000000000..5ab2fc0e85 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-tr.png diff --git a/comm/suite/editor/base/content/images/tag-tt.png b/comm/suite/editor/base/content/images/tag-tt.png Binary files differnew file mode 100644 index 0000000000..61108f6366 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-tt.png diff --git a/comm/suite/editor/base/content/images/tag-ttl.png b/comm/suite/editor/base/content/images/tag-ttl.png Binary files differnew file mode 100644 index 0000000000..2cbdbe3943 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-ttl.png diff --git a/comm/suite/editor/base/content/images/tag-txt.png b/comm/suite/editor/base/content/images/tag-txt.png Binary files differnew file mode 100644 index 0000000000..2ec48df034 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-txt.png diff --git a/comm/suite/editor/base/content/images/tag-u.png b/comm/suite/editor/base/content/images/tag-u.png Binary files differnew file mode 100644 index 0000000000..c435789a59 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-u.png diff --git a/comm/suite/editor/base/content/images/tag-ul.png b/comm/suite/editor/base/content/images/tag-ul.png Binary files differnew file mode 100644 index 0000000000..5bdee5d496 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-ul.png diff --git a/comm/suite/editor/base/content/images/tag-userdefined.png b/comm/suite/editor/base/content/images/tag-userdefined.png Binary files differnew file mode 100644 index 0000000000..1b36f9f259 --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-userdefined.png diff --git a/comm/suite/editor/base/content/images/tag-var.png b/comm/suite/editor/base/content/images/tag-var.png Binary files differnew file mode 100644 index 0000000000..aa8200597b --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-var.png diff --git a/comm/suite/editor/base/content/images/tag-xmp.png b/comm/suite/editor/base/content/images/tag-xmp.png Binary files differnew file mode 100644 index 0000000000..3c66fa0d9e --- /dev/null +++ b/comm/suite/editor/base/content/images/tag-xmp.png diff --git a/comm/suite/editor/base/content/publishprefs.js b/comm/suite/editor/base/content/publishprefs.js new file mode 100644 index 0000000000..4de3b4f282 --- /dev/null +++ b/comm/suite/editor/base/content/publishprefs.js @@ -0,0 +1,867 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/****************** Get publishing data methods *******************/ + +// Build an array of all publish site data obtained from prefs +function GetPublishSiteData() +{ + var publishBranch = GetPublishPrefsBranch(); + if (!publishBranch) + return null; + + // Array of site names - sorted, but don't put default name first + var siteNameList = GetSiteNameList(true, false); + if (!siteNameList) + return null; + + // Array of all site data + var siteArray = []; + + // We rewrite siteName prefs to eliminate names if data is bad + // and to be sure order is the same as sorted name list + try { + publishBranch.deleteBranch("site_name."); + } catch (e) {} + + // Get publish data using siteName as the key + var index = 0; + for (var i = 0; i < siteNameList.length; i++) + { + // Associated data uses site name as key + var publishData = GetPublishData_internal(publishBranch, siteNameList[i]); + if (publishData) + { + siteArray[index] = publishData; + SetPublishStringPref(publishBranch, "site_name."+index, siteNameList[i]); + index++; + } + else + { + try { + // Remove bad site prefs now + publishBranch.deleteBranch("site_data." + siteNameList[i] + "."); + } catch (e) {} + } + } + + SavePrefFile(); + + if (index == 0) // No Valid pref records found! + return null; + + + return siteArray; +} + +function GetDefaultPublishSiteName() +{ + var publishBranch = GetPublishPrefsBranch(); + var name = ""; + if (publishBranch) + name = GetPublishStringPref(publishBranch, "default_site"); + + return name; +} + +// Return object with all info needed to publish +// from database of sites previously published to. +function CreatePublishDataFromUrl(docUrl) +{ + if (!docUrl || IsUrlAboutBlank(docUrl) || GetScheme(docUrl) == "file") + return null; + + var pubSiteData = GetPublishSiteData(); + if (pubSiteData) + { + var dirObj = {}; + var index = FindSiteIndexAndDocDir(pubSiteData, docUrl, dirObj); + var publishData; + if (index != -1) + { + publishData = pubSiteData[index]; + publishData.docDir = FormatDirForPublishing(dirObj.value) + + //XXX Problem: OtherDir: How do we decide when to use the dir in + // publishSiteData (default DocDir) or docDir from current filepath? + publishData.otherDir = FormatDirForPublishing(pubSiteData[index].otherDir); + + publishData.filename = GetFilename(docUrl); + publishData.notInSiteData = false; + return publishData; + } + } + + // Document wasn't found in publish site database + // Create data just from URL + + // Extract username and password from docUrl + var userObj = {}; + var passObj = {}; + var pubUrl = StripUsernamePassword(docUrl, userObj, passObj); + + // Strip off filename + var lastSlash = pubUrl.lastIndexOf("\/"); + //XXX Look for "?", "=", and "&" ? + pubUrl = pubUrl.slice(0, lastSlash+1); + + var siteName = CreateSiteNameFromUrl(pubUrl, pubSiteData); + + publishData = { + siteName : siteName, + previousSiteName : siteName, + filename : GetFilename(docUrl), + username : userObj.value, + password : passObj.value, + savePassword : false, + publishUrl : pubUrl, + browseUrl : pubUrl, + docDir : "", + otherDir : "", + publishOtherFiles : true, + dirList : [""], + saveDirs : false, + notInSiteData : true + } + + return publishData; +} + +function CreateSiteNameFromUrl(url, publishSiteData) +{ + var host = GetHost(url); + var schemePostfix = " (" + GetScheme(url) + ")"; + var siteName = host + schemePostfix; + + if (publishSiteData) + { + // Look for duplicates. Append "-1" etc until unique name found + var i = 1; + var exists = false; + do { + exists = PublishSiteNameExists(siteName, publishSiteData, -1) + if (exists) + siteName = host + "-" + i + schemePostfix; + i++; + } + while (exists); + } + return siteName; +} + +// Similar to above, but in param is a site profile name +// Note that this is more efficient than getting from a URL, +// since we don't have to get all the sitedata but can key off of sitename. +// Caller must supply the current docUrl or just a filename +// If doc URL is supplied, we find the publish subdirectory if publishUrl is part of docUrl +function GetPublishDataFromSiteName(siteName, docUrlOrFilename) +{ + var publishBranch = GetPublishPrefsBranch(); + if (!publishBranch) + return null; + + var siteNameList = GetSiteNameList(false, false); + if (!siteNameList) + return null; + for (var i = 0; i < siteNameList.length; i++) + { + if (siteNameList[i] == siteName) + { + var publishData = GetPublishData_internal(publishBranch, siteName); + if (GetScheme(docUrlOrFilename)) + FillInMatchingPublishData(publishData, docUrlOrFilename); + else + publishData.filename = docUrlOrFilename; + + return publishData; + } + } + return null; +} + +function GetDefaultPublishData() +{ + var publishBranch = GetPublishPrefsBranch(); + if (!publishBranch) + return null; + + var siteName = GetPublishStringPref(publishBranch, "default_site"); + if (!siteName) + return null; + + return GetPublishData_internal(publishBranch, siteName); +} + +function GetPublishData_internal(publishBranch, siteName) +{ + if (!publishBranch || !siteName) + return null; + + var prefPrefix = "site_data." + siteName + "."; + + // We must have a publish url, else we ignore this site + // (siteData and siteNames for sites with incomplete data + // will get deleted by SavePublishSiteDataToPrefs) + var publishUrl = GetPublishStringPref(publishBranch, prefPrefix+"url"); + if (!publishUrl) + return null; + + var savePassword = false; + var publishOtherFiles = true; + try { + savePassword = publishBranch.getBoolPref(prefPrefix+"save_password"); + publishOtherFiles = publishBranch.getBoolPref(prefPrefix+"publish_other_files"); + } catch (e) {} + + var publishData = { + siteName : siteName, + previousSiteName : siteName, + filename : "", + username : GetPublishStringPref(publishBranch, prefPrefix+"username"), + savePassword : savePassword, + publishUrl : publishUrl, + browseUrl : GetPublishStringPref(publishBranch, prefPrefix+"browse_url"), + docDir : FormatDirForPublishing(GetPublishStringPref(publishBranch, prefPrefix+"doc_dir")), + otherDir : FormatDirForPublishing(GetPublishStringPref(publishBranch, prefPrefix+"other_dir")), + publishOtherFiles : publishOtherFiles, + saveDirs : false + } + + // Get password from PasswordManager + publishData.password = GetSavedPassword(publishData); + + // If password was found, user must have checked "Save password" + // checkbox in prompt outside of publishing, so override the pref we stored + if (publishData.password) + { + if (!savePassword) + { + try { + publishPrefsBranch.setBoolPref(prefPrefix+"save_password", true); + } catch (e) {} + } + publishData.savePassword = true; + } + + // Build history list of directories + // Always supply the root dir + publishData.dirList = [""]; + + // Get the rest from prefs + var dirPrefs; + try { + dirPrefs = publishBranch.getChildList(prefPrefix+"dir."); + } catch (e) {} + + if (dirPrefs && dirPrefs.length > 0) + { + if (dirPrefs.length > 1) + dirPrefs.sort(); + + for (var j = 0; j < dirPrefs.length; j++) + { + var dirName = GetPublishStringPref(publishBranch, dirPrefs[j]); + if (dirName) + publishData.dirList[j+1] = dirName; + } + } + + return publishData; +} + +/****************** Save publishing data methods *********************/ + +// Save the siteArray containing all current publish site data +function SavePublishSiteDataToPrefs(siteArray, defaultName) +{ + var publishBranch = GetPublishPrefsBranch(); + if (!publishBranch) + return false; + + try { + if (siteArray) + { + var defaultFound = false; + + // Clear existing names and data -- rebuild all site prefs + publishBranch.deleteBranch("site_name."); + publishBranch.deleteBranch("site_data."); + + for (var i = 0; i < siteArray.length; i++) + { + SavePublishData_Internal(publishBranch, siteArray[i], i); + if (!defaultFound) + defaultFound = defaultName == siteArray[i].siteName; + } + // Assure that we have a default name + if (siteArray.length && !defaultFound) + defaultName = siteArray[0].siteName; + } + + // Save default site name + SetPublishStringPref(publishBranch, "default_site", defaultName); + + // Force saving to file so next page edited finds these values + SavePrefFile(); + } + catch (ex) { return false; } + + return true; +} + +// Update prefs if publish site already exists +// or add prefs for a new site +function SavePublishDataToPrefs(publishData) +{ + if (!publishData || !publishData.publishUrl) + return false; + + var publishBranch = GetPublishPrefsBranch(); + if (!publishBranch) + return false; + + // Create name from URL if no site name is provided + if (!publishData.siteName) + publishData.siteName = CreateSiteNameFromUrl(publishData.publishUrl, publishData); + + var siteNamePrefs; + try { + siteNamePrefs = publishBranch.getChildList("site_name."); + } catch (e) {} + + if (!siteNamePrefs || siteNamePrefs.length == 0) + { + // We currently have no site prefs, so create them + var siteData = [publishData]; + return SavePublishSiteDataToPrefs(siteData, publishData.siteName); + } + + // Use "previous" name if available in case it was changed + var previousSiteName = ("previousSiteName" in publishData && publishData.previousSiteName) ? + publishData.previousSiteName : publishData.siteName; + + // Find site number of existing site or fall through at next available one + // (Number is arbitrary; needed to construct unique "site_name.x" pref string) + for (var i = 0; i < siteNamePrefs.length; i++) + { + var siteName = GetPublishStringPref(publishBranch, "site_name."+i); + + if (siteName == previousSiteName) + { + // Delete prefs for an existing site + try { + publishBranch.deleteBranch("site_data." + siteName + "."); + } catch (e) {} + break; + } + } + + // We've taken care of finding old duplicate, so be sure 'previous name' is current + publishData.previousSiteName = publishData.siteName; + + var ret = SavePublishData_Internal(publishBranch, publishData, i); + if (ret) + { + // Check if siteName was the default and we need to update that + var defaultSiteName = GetPublishStringPref(publishBranch, "default_site"); + if (previousSiteName == defaultSiteName + && publishData.siteName != defaultSiteName) + SetPublishStringPref(publishBranch, "default_site", publishData.siteName); + + SavePrefFile(); + + // Clear signal to save these data + if ("notInSiteData" in publishData && publishData.notInSiteData) + publishData.notInSiteData = false; + } + return ret; +} + +// Save data at a particular site number +function SavePublishData_Internal(publishPrefsBranch, publishData, siteIndex) +{ + if (!publishPrefsBranch || !publishData) + return false; + + SetPublishStringPref(publishPrefsBranch, "site_name."+siteIndex, publishData.siteName); + + FixupUsernamePasswordInPublishData(publishData); + + var prefPrefix = "site_data." + publishData.siteName + "." + + SetPublishStringPref(publishPrefsBranch, prefPrefix+"url", publishData.publishUrl); + SetPublishStringPref(publishPrefsBranch, prefPrefix+"browse_url", publishData.browseUrl); + SetPublishStringPref(publishPrefsBranch, prefPrefix+"username", publishData.username); + + try { + publishPrefsBranch.setBoolPref(prefPrefix+"save_password", publishData.savePassword); + publishPrefsBranch.setBoolPref(prefPrefix+"publish_other_files", publishData.publishOtherFiles); + } catch (e) {} + + // Save password using PasswordManager + // (If publishData.savePassword = false, this clears existing password) + SavePassword(publishData); + + SetPublishStringPref(publishPrefsBranch, prefPrefix+"doc_dir", + FormatDirForPublishing(publishData.docDir)); + + if (publishData.publishOtherFiles && publishData.otherDir) + SetPublishStringPref(publishPrefsBranch, prefPrefix+"other_dir", + FormatDirForPublishing(publishData.otherDir)); + + if ("saveDirs" in publishData && publishData.saveDirs) + { + if (publishData.docDir) + AppendNewDirToList(publishData, publishData.docDir); + + if (publishData.publishOtherFiles && publishData.otherDir + && publishData.otherDir != publishData.docDir) + AppendNewDirToList(publishData, publishData.otherDir); + } + + // Save array of subdirectories with site + if (publishData.dirList.length) + { + publishData.dirList.sort(); + var dirIndex = 0; + for (var j = 0; j < publishData.dirList.length; j++) + { + var dir = publishData.dirList[j]; + + // Don't store the root dir + if (dir && dir != "/") + { + SetPublishStringPref(publishPrefsBranch, prefPrefix + "dir." + dirIndex, dir); + dirIndex++; + } + } + } + + return true; +} + +function AppendNewDirToList(publishData, newDir) +{ + newDir = FormatDirForPublishing(newDir); + if (!publishData || !newDir) + return; + + if (!publishData.dirList) + { + publishData.dirList = [newDir]; + return; + } + + // Check if already in the list + for (var i = 0; i < publishData.dirList.length; i++) + { + // Don't add if already in the list + if (newDir == publishData.dirList[i]) + return; + } + // Add to end of list + publishData.dirList[publishData.dirList.length] = newDir; +} + +function RemovePublishSubdirectoryFromPrefs(publishData, removeDir) +{ + removeDir = FormatDirForPublishing(removeDir); + if (!publishData || !publishData.siteName || !removeDir) + return false; + + var publishBranch = GetPublishPrefsBranch(); + if (!publishBranch) + return false; + + var prefPrefix = "site_data." + publishData.siteName + "."; + + // Remove dir from the default dir prefs + if (publishData.docDir == removeDir) + { + publishData.docDir = ""; + SetPublishStringPref(publishBranch, prefPrefix+"doc_dir", ""); + } + + if (publishData.otherDir == removeDir) + { + publishData.otherDir = ""; + SetPublishStringPref(publishBranch, prefPrefix+"other_dir", ""); + } + + prefPrefix += "dir."; + + // Delete entire subdir list + try { + publishBranch.deleteBranch(prefPrefix); + } catch (e) {} + + // Rebuild prefs, skipping over site to remove + if (publishData.dirList.length) + { + var dirIndex = 0; + var docDirInList = false; + var otherDirInList = false; + for (var i = 0; i < publishData.dirList.length; i++) + { + var dir = publishData.dirList[i]; + if (dir == removeDir) + { + // Remove item from the dirList array + publishData.dirList.splice(i, 1); + --i; + } + else if (dir && dir != "/") // skip empty or root dir + { + // Save to prefs + SetPublishStringPref(publishBranch, prefPrefix + dirIndex, dir); + dirIndex++; + } + } + } + SavePrefFile(); + return true; +} + +function SetDefaultSiteName(name) +{ + if (name) + { + var publishBranch = GetPublishPrefsBranch(); + if (publishBranch) + SetPublishStringPref(publishBranch, "default_site", name); + + SavePrefFile(); + } +} + +function SavePrefFile() +{ + try { + Services.prefs.savePrefFile(null); + } + catch (e) {} +} + +/***************** Helper / utility methods ********************/ + +function GetPublishPrefsBranch() +{ + return Services.prefs.getBranch("editor.publish."); +} + +function GetSiteNameList(doSort, defaultFirst) +{ + var publishBranch = GetPublishPrefsBranch(); + if (!publishBranch) + return null; + + var siteNamePrefs; + try { + siteNamePrefs = publishBranch.getChildList("site_name."); + } catch (e) {} + + if (!siteNamePrefs || siteNamePrefs.length == 0) + return null; + + // Array of site names + var siteNameList = []; + var index = 0; + var defaultName = ""; + if (defaultFirst) + { + defaultName = GetPublishStringPref(publishBranch, "default_site"); + // This always sorts to top -- replace with real string below + siteNameList[0] = ""; + index++; + } + + for (var i = 0; i < siteNamePrefs.length; i++) + { + var siteName = GetPublishStringPref(publishBranch, siteNamePrefs[i]); + // Skip if siteName pref is empty or is default name + if (siteName && siteName != defaultName) + { + siteNameList[index] = siteName; + index++; + } + } + + if (siteNameList.length && doSort) + siteNameList.sort(); + + if (defaultName) + { + siteNameList[0] = defaultName; + index++; + } + + return siteNameList.length? siteNameList : null; +} + +function PublishSiteNameExists(name, publishSiteData, skipSiteIndex) +{ + if (!name) + return false; + + if (!publishSiteData) + { + publishSiteData = GetPublishSiteData(); + skipSiteIndex = -1; + } + + if (!publishSiteData) + return false; + + // Array of site names - sorted, but don't put default name first + for (var i = 0; i < publishSiteData.length; i++) + { + if (i != skipSiteIndex && name == publishSiteData[i].siteName) + return true; + } + return false; +} + +// Find index of a site record in supplied publish site database +// docUrl: Document URL with or without filename +// (Must end in "/" if no filename) +// dirObj.value = the directory of the document URL +// relative to the base publishing URL, using "" if none +// +// XXX: Currently finds the site with the longest-matching url; +// should we look for the shortest instead? Or match just the host portion? +function FindSiteIndexAndDocDir(publishSiteData, docUrl, dirObj) +{ + if (dirObj) + dirObj.value = ""; + + if (!publishSiteData || !docUrl || GetScheme(docUrl) == "file") + return -1; + + var siteIndex = -1; + var siteUrlLen = 0; + + for (var i = 0; i < publishSiteData.length; i++) + { + // Site publish or browse url needs to be contained in document URL, + // but that may also have a directory after the site base URL + // So we must examine all records to find the site URL that best + // matches the document URL: the longest-matching substring (XXX is this right?) + var lenObj = {value:0}; + var tempData = Clone(publishSiteData[i]); + + // Check if this site matches docUrl (returns length of match if found) + var len = FillInMatchingPublishData(tempData, docUrl); + + if (len > siteUrlLen) + { + siteIndex = i; + siteUrlLen = len; + if (dirObj) + dirObj.value = tempData.docDir; + + // Continue to find the site with longest-matching publishUrl + } + } + return siteIndex; +} + +// Look for a matching publish url within the document url +// (We need to look at both "publishUrl" and "browseUrl" in case we are editing +// an http: document but using ftp: to publish.) +// If match is found: +// Fill in the filename and subdirectory based on the docUrl and +// return the length of the docUrl with username+password stripped out +function FillInMatchingPublishData(publishData, docUrl) +{ + if (!publishData || !docUrl) + return 0; + + // Separate docUrl into the base url and filename + var lastSlash = docUrl.lastIndexOf("\/"); + var baseUrl = docUrl.slice(0, lastSlash+1); + var filename = docUrl.slice(lastSlash+1); + + // Strip username+password from docUrl because these + // are stored separately in publishData, never embedded in the publishUrl + // If both docUrl and publishData contain usernames, + // we must match that as well as the url + var username = {value:""}; + baseUrl = StripUsernamePassword(baseUrl, username); + username = username.value; + + var matchedLength = 0; + let pubUrlFound = publishData.publishUrl && baseUrl.startsWith(publishData.publishUrl); + let browseUrlFound = publishData.browseUrl && baseUrl.startsWith(publishData.browseUrl); + + if ((pubUrlFound || browseUrlFound) + && (!username || !publishData.username || username == publishData.username)) + { + // We found a match + matchedLength = pubUrlFound ? publishData.publishUrl.length + : publishData.browseUrl.length; + + if (matchedLength > 0) + { + publishData.filename = filename; + + // Subdirectory within the site is what's left in baseUrl after the matched portion + publishData.docDir = FormatDirForPublishing(baseUrl.slice(matchedLength)); + } + } + return matchedLength; +} + +// Prefs that don't exist will through an exception, +// so just return an empty string +function GetPublishStringPref(prefBranch, name) +{ + if (prefBranch && name) + { + try { + return prefBranch.getStringPref(name); + } catch (e) {} + } + return ""; +} + +function SetPublishStringPref(prefBranch, name, value) +{ + if (prefBranch && name) + { + try { + prefBranch.setStringPref(name, value); + } catch (e) {} + } +} + +// Assure that a publishing URL ends in "/", "=", "&" or "?" +// Username and password should always be extracted as separate fields +// and are not allowed to remain embedded in publishing URL +function FormatUrlForPublishing(url) +{ + url = TrimString(StripUsernamePassword(url)); + if (url) + { + var lastChar = url.charAt(url.length-1); + if (lastChar != "/" && lastChar != "=" && lastChar != "&" && lastChar != "?") + return (url + "/"); + } + return url; +} + +// Username and password present in publish url are +// extracted into the separate "username" and "password" fields +// of the publishData object +// Returns true if we did change the publishData +function FixupUsernamePasswordInPublishData(publishData) +{ + var ret = false; + if (publishData && publishData.publishUrl) + { + var userObj = {value:""}; + var passObj = {value:""}; + publishData.publishUrl = FormatUrlForPublishing(StripUsernamePassword(publishData.publishUrl, userObj, passObj)); + if (userObj.value) + { + publishData.username = userObj.value; + ret = true; + } + if (passObj.value) + { + publishData.password = passObj.value; + ret = true; + } + // While we're at it, be sure browse URL is proper format + publishData.browseUrl = FormatUrlForPublishing(publishData.browseUrl); + } + return ret; +} + +// Assure that a publishing directory ends with "/" and does not begin with "/" +// Input dir is assumed to be a subdirectory string, not a full URL or pathname +function FormatDirForPublishing(dir) +{ + dir = TrimString(dir); + + // The "//" case is an expected "typo" filter + // that simplifies code below! + if (!dir || dir == "/" || dir == "//") + return ""; + + // Remove leading "/" + if (dir.startsWith("/")) + dir = dir.slice(1); + + // Append "/" at the end if necessary + var dirLen = dir.length; + var lastChar = dir.charAt(dirLen-1); + if (dirLen > 1 && ["/", "=", "&", "?"].indexOf(lastChar) == -1) + dir += "/"; + + return dir; +} + +function GetSavedPassword(publishData) +{ + if (!publishData || !publishData.publishUrl) + return ""; + + let url = GetUrlForPasswordManager(publishData); + let logins = Services.logins.findLogins(url, null, url); + + for (let i = 0; i < logins.length; i++) { + if (logins[i].username == publishData.username) + return logins[i].password; + } + + return ""; +} + +function SavePassword(publishData) +{ + if (!publishData || !publishData.publishUrl || !publishData.username) + return false; + + let url = GetUrlForPasswordManager(publishData); + + // Remove existing entry by finding all logins that match. + let logins = Services.logins.findLogins(url, null, url); + + for (let i = 0; i < logins.length; i++) { + if (logins[i].username == publishData.username) { + Services.logins.removeLogin(logins[i]); + break; + } + } + + // If SavePassword is true, add new password. + if (publishData.savePassword) + { + let authInfo = Cc["@mozilla.org/login-manager/loginInfo;1"] + .createInstance(Ci.nsILoginInfo); + authInfo.init(url, null, url, publishData.username, publishData.password, + "", ""); + Services.logins.addLogin(authInfo); + } + + return true; +} + +function GetUrlForPasswordManager(publishData) +{ + if (!publishData || !publishData.publishUrl) + return false; + + let url = Services.io.newURI(publishData.publishUrl); + + if (url.scheme == "ftp" && publishData.username) + // Include username in the URL so we can handle multiple users per server + // in the password manager + url = url.scheme + "://" + publishData.username + "@" + url.hostPort; + else + url = url.scheme + "://" + url.hostPort; + + return url; +} diff --git a/comm/suite/editor/base/jar.mn b/comm/suite/editor/base/jar.mn new file mode 100644 index 0000000000..35ed2406b2 --- /dev/null +++ b/comm/suite/editor/base/jar.mn @@ -0,0 +1,124 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +comm.jar: +% content editor %content/editor/ +% overlay chrome://communicator/content/tasksOverlay.xhtml chrome://editor/content/editorTasksOverlay.xhtml + content/editor/ComposerCommands.js (content/ComposerCommands.js) + content/editor/composerOverlay.xhtml (content/composerOverlay.xhtml) + content/editor/editingOverlay.js (content/editingOverlay.js) + content/editor/editingOverlay.xhtml (content/editingOverlay.xhtml) + content/editor/editor.js (content/editor.js) + content/editor/editor.xhtml (content/editor.xhtml) + content/editor/EditorAllTags.css (content/EditorAllTags.css) + content/editor/editorApplicationOverlay.js (content/editorApplicationOverlay.js) + content/editor/EditorContent.css (content/EditorContent.css) + content/editor/EditorContextMenu.js (content/EditorContextMenu.js) + content/editor/EditorContextMenuOverlay.xhtml (content/EditorContextMenuOverlay.xhtml) + content/editor/editorOverlay.xhtml (content/editorOverlay.xhtml) + content/editor/editorTasksOverlay.xhtml (content/editorTasksOverlay.xhtml) + content/editor/editorUtilities.js (content/editorUtilities.js) + content/editor/publishprefs.js (content/publishprefs.js) + content/editor/StructBarContextMenu.js (content/StructBarContextMenu.js) + content/editor/images/bringtofront.png (content/images/bringtofront.png) + content/editor/images/bringtofront-disabled.png (content/images/bringtofront-disabled.png) + content/editor/images/sendtoback.png (content/images/sendtoback.png) + content/editor/images/sendtoback-disabled.png (content/images/sendtoback-disabled.png) + content/editor/images/tag-a.png (content/images/tag-a.png) + content/editor/images/tag-abr.png (content/images/tag-abr.png) + content/editor/images/tag-acr.png (content/images/tag-acr.png) + content/editor/images/tag-adr.png (content/images/tag-adr.png) + content/editor/images/tag-anchor.png (content/images/tag-anchor.png) + content/editor/images/tag-app.png (content/images/tag-app.png) + content/editor/images/tag-ara.png (content/images/tag-ara.png) + content/editor/images/tag-b.png (content/images/tag-b.png) + content/editor/images/tag-bas.png (content/images/tag-bas.png) + content/editor/images/tag-bdo.png (content/images/tag-bdo.png) + content/editor/images/tag-big.png (content/images/tag-big.png) + content/editor/images/tag-blq.png (content/images/tag-blq.png) + content/editor/images/tag-body.png (content/images/tag-body.png) + content/editor/images/tag-br.png (content/images/tag-br.png) + content/editor/images/tag-bsf.png (content/images/tag-bsf.png) + content/editor/images/tag-btn.png (content/images/tag-btn.png) + content/editor/images/tag-cit.png (content/images/tag-cit.png) + content/editor/images/tag-clg.png (content/images/tag-clg.png) + content/editor/images/tag-cod.png (content/images/tag-cod.png) + content/editor/images/tag-col.png (content/images/tag-col.png) + content/editor/images/tag-cpt.png (content/images/tag-cpt.png) + content/editor/images/tag-ctr.png (content/images/tag-ctr.png) + content/editor/images/tag-dd.png (content/images/tag-dd.png) + content/editor/images/tag-del.png (content/images/tag-del.png) + content/editor/images/tag-dfn.png (content/images/tag-dfn.png) + content/editor/images/tag-dir.png (content/images/tag-dir.png) + content/editor/images/tag-div.png (content/images/tag-div.png) + content/editor/images/tag-dl.png (content/images/tag-dl.png) + content/editor/images/tag-dt.png (content/images/tag-dt.png) + content/editor/images/tag-em.png (content/images/tag-em.png) + content/editor/images/tag-fld.png (content/images/tag-fld.png) + content/editor/images/tag-fnt.png (content/images/tag-fnt.png) + content/editor/images/tag-for.png (content/images/tag-for.png) + content/editor/images/tag-frm.png (content/images/tag-frm.png) + content/editor/images/tag-fst.png (content/images/tag-fst.png) + content/editor/images/tag-h1.png (content/images/tag-h1.png) + content/editor/images/tag-h2.png (content/images/tag-h2.png) + content/editor/images/tag-h3.png (content/images/tag-h3.png) + content/editor/images/tag-h4.png (content/images/tag-h4.png) + content/editor/images/tag-h5.png (content/images/tag-h5.png) + content/editor/images/tag-h6.png (content/images/tag-h6.png) + content/editor/images/tag-hed.png (content/images/tag-hed.png) + content/editor/images/tag-hr.png (content/images/tag-hr.png) + content/editor/images/tag-html.png (content/images/tag-html.png) + content/editor/images/tag-i.png (content/images/tag-i.png) + content/editor/images/tag-ifr.png (content/images/tag-ifr.png) + content/editor/images/tag-img.png (content/images/tag-img.png) + content/editor/images/tag-inp.png (content/images/tag-inp.png) + content/editor/images/tag-ins.png (content/images/tag-ins.png) + content/editor/images/tag-isx.png (content/images/tag-isx.png) + content/editor/images/tag-kbd.png (content/images/tag-kbd.png) + content/editor/images/tag-lbl.png (content/images/tag-lbl.png) + content/editor/images/tag-lgn.png (content/images/tag-lgn.png) + content/editor/images/tag-li.png (content/images/tag-li.png) + content/editor/images/tag-lnk.png (content/images/tag-lnk.png) + content/editor/images/tag-lst.png (content/images/tag-lst.png) + content/editor/images/tag-map.png (content/images/tag-map.png) + content/editor/images/tag-men.png (content/images/tag-men.png) + content/editor/images/tag-met.png (content/images/tag-met.png) + content/editor/images/tag-nbr.png (content/images/tag-nbr.png) + content/editor/images/tag-nfr.png (content/images/tag-nfr.png) + content/editor/images/tag-nsc.png (content/images/tag-nsc.png) + content/editor/images/tag-obj.png (content/images/tag-obj.png) + content/editor/images/tag-ol.png (content/images/tag-ol.png) + content/editor/images/tag-opg.png (content/images/tag-opg.png) + content/editor/images/tag-opt.png (content/images/tag-opt.png) + content/editor/images/tag-p.png (content/images/tag-p.png) + content/editor/images/tag-pln.png (content/images/tag-pln.png) + content/editor/images/tag-pre.png (content/images/tag-pre.png) + content/editor/images/tag-prm.png (content/images/tag-prm.png) + content/editor/images/tag-q.png (content/images/tag-q.png) + content/editor/images/tag-s.png (content/images/tag-s.png) + content/editor/images/tag-scr.png (content/images/tag-scr.png) + content/editor/images/tag-slc.png (content/images/tag-slc.png) + content/editor/images/tag-sml.png (content/images/tag-sml.png) + content/editor/images/tag-smp.png (content/images/tag-smp.png) + content/editor/images/tag-spn.png (content/images/tag-spn.png) + content/editor/images/tag-stk.png (content/images/tag-stk.png) + content/editor/images/tag-stl.png (content/images/tag-stl.png) + content/editor/images/tag-stn.png (content/images/tag-stn.png) + content/editor/images/tag-sub.png (content/images/tag-sub.png) + content/editor/images/tag-sup.png (content/images/tag-sup.png) + content/editor/images/tag-tbd.png (content/images/tag-tbd.png) + content/editor/images/tag-tbl.png (content/images/tag-tbl.png) + content/editor/images/tag-td.png (content/images/tag-td.png) + content/editor/images/tag-tft.png (content/images/tag-tft.png) + content/editor/images/tag-th.png (content/images/tag-th.png) + content/editor/images/tag-thd.png (content/images/tag-thd.png) + content/editor/images/tag-tr.png (content/images/tag-tr.png) + content/editor/images/tag-tt.png (content/images/tag-tt.png) + content/editor/images/tag-ttl.png (content/images/tag-ttl.png) + content/editor/images/tag-txt.png (content/images/tag-txt.png) + content/editor/images/tag-u.png (content/images/tag-u.png) + content/editor/images/tag-ul.png (content/images/tag-ul.png) + content/editor/images/tag-userdefined.png (content/images/tag-userdefined.png) + content/editor/images/tag-var.png (content/images/tag-var.png) + content/editor/images/tag-xmp.png (content/images/tag-xmp.png) diff --git a/comm/suite/editor/base/moz.build b/comm/suite/editor/base/moz.build new file mode 100644 index 0000000000..de5cd1bf81 --- /dev/null +++ b/comm/suite/editor/base/moz.build @@ -0,0 +1,6 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +JAR_MANIFESTS += ["jar.mn"] diff --git a/comm/suite/editor/components/dialogs/content/EdAEAttributes.js b/comm/suite/editor/components/dialogs/content/EdAEAttributes.js new file mode 100644 index 0000000000..52b7e30fac --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdAEAttributes.js @@ -0,0 +1,973 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// HTML Attributes object for "Name" menulist +var gHTMLAttr = {}; + +// JS Events Attributes object for "Name" menulist +var gJSAttr = {}; + +// Core HTML attribute values // +// This is appended to Name menulist when "_core" is attribute name +var gCoreHTMLAttr = ["^id", "class", "title"]; + +// Core event attribute values // +// This is appended to all JS menulists +// except those elements having "noJSEvents" +// as a value in their gJSAttr array. +var gCoreJSEvents = [ + "onclick", + "ondblclick", + "onmousedown", + "onmouseup", + "onmouseover", + "onmousemove", + "onmouseout", + "-", + "onkeypress", + "onkeydown", + "onkeyup", +]; + +// Following are commonly-used strings + +// Also accept: sRGB: #RRGGBB // +var gHTMLColors = [ + "Aqua", + "Black", + "Blue", + "Fuchsia", + "Gray", + "Green", + "Lime", + "Maroon", + "Navy", + "Olive", + "Purple", + "Red", + "Silver", + "Teal", + "White", + "Yellow", +]; + +var gHAlign = ["left", "center", "right"]; + +var gHAlignJustify = ["left", "center", "right", "justify"]; + +var gHAlignTableContent = ["left", "center", "right", "justify", "char"]; + +var gVAlignTable = ["top", "middle", "bottom", "baseline"]; + +var gTarget = ["_blank", "_self", "_parent", "_top"]; + +// ================ HTML Attributes ================ // +/* For each element, there is an array of attributes, + whose name is the element name, + used to fill the "Attribute Name" menulist. + For each of those attributes, if they have a specific + set of values, those are listed in an array named: + "elementName_attName". + + In each values string, the following characters + are signal to do input filtering: + "#" Allow only integer values + "%" Allow integer values or a number ending in "%" + "+" Allow integer values and allow "+" or "-" as first character + "!" Allow only one character + "^" The first character can be only be A-Z, a-z, hyphen, underscore, colon or period + "$" is an attribute required by HTML DTD +*/ + +/* + Most elements have the "dir" attribute, + so we use this value array + for all elements instead of specifying + separately for each element +*/ +gHTMLAttr.all_dir = ["ltr", "rtl"]; + +gHTMLAttr.a = [ + "charset", + "type", + "name", + "href", + "^hreflang", + "target", + "rel", + "rev", + "!accesskey", + "shape", // with imagemap // + "coords", // with imagemap // + "#tabindex", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.a_target = gTarget; + +gHTMLAttr.a_rel = [ + "alternate", + "stylesheet", + "start", + "next", + "prev", + "contents", + "index", + "glossary", + "copyright", + "chapter", + "section", + "subsection", + "appendix", + "help", + "bookmark", +]; + +gHTMLAttr.a_rev = [ + "alternate", + "stylesheet", + "start", + "next", + "prev", + "contents", + "index", + "glossary", + "copyright", + "chapter", + "section", + "subsection", + "appendix", + "help", + "bookmark", +]; + +gHTMLAttr.a_shape = ["rect", "circle", "poly", "default"]; + +gHTMLAttr.abbr = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.acronym = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.address = ["_core", "-", "^lang", "dir"]; + +// this is deprecated // +gHTMLAttr.applet = [ + "codebase", + "archive", + "code", + "object", + "alt", + "name", + "%$width", + "%$height", + "align", + "#hspace", + "#vspace", + "-", + "_core", +]; + +gHTMLAttr.applet_align = ["top", "middle", "bottom", "left", "right"]; + +gHTMLAttr.area = [ + "shape", + "coords", + "href", + "nohref", + "target", + "$alt", + "#tabindex", + "!accesskey", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.area_target = gTarget; + +gHTMLAttr.area_shape = ["rect", "circle", "poly", "default"]; + +gHTMLAttr.area_nohref = ["nohref"]; + +gHTMLAttr.b = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.base = ["href", "target"]; + +gHTMLAttr.base_target = gTarget; + +// this is deprecated // +gHTMLAttr.basefont = ["^id", "$size", "color", "face"]; + +gHTMLAttr.basefont_color = gHTMLColors; + +gHTMLAttr.bdo = ["_core", "-", "^lang", "$dir"]; + +gHTMLAttr.bdo_dir = ["ltr", "rtl"]; + +gHTMLAttr.big = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.blockquote = ["cite", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.body = [ + "background", + "bgcolor", + "text", + "link", + "vlink", + "alink", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.body_bgcolor = gHTMLColors; + +gHTMLAttr.body_text = gHTMLColors; + +gHTMLAttr.body_link = gHTMLColors; + +gHTMLAttr.body_vlink = gHTMLColors; + +gHTMLAttr.body_alink = gHTMLColors; + +gHTMLAttr.br = ["clear", "-", "_core"]; + +gHTMLAttr.br_clear = ["none", "left", "all", "right"]; + +gHTMLAttr.button = [ + "name", + "value", + "$type", + "disabled", + "#tabindex", + "!accesskey", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.button_type = ["submit", "button", "reset"]; + +gHTMLAttr.button_disabled = ["disabled"]; + +gHTMLAttr.caption = ["align", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.caption_align = ["top", "bottom", "left", "right"]; + +// this is deprecated // +gHTMLAttr.center = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.cite = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.code = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.col = [ + "#$span", + "%width", + "align", + "!char", + "#charoff", + "valign", + "char", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.col_span = [ + "1", // default +]; + +gHTMLAttr.col_align = gHAlignTableContent; + +gHTMLAttr.col_valign = ["top", "middle", "bottom", "baseline"]; + +gHTMLAttr.colgroup = [ + "#$span", + "%width", + "align", + "!char", + "#charoff", + "valign", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.colgroup_span = [ + "1", // default +]; + +gHTMLAttr.colgroup_align = gHAlignTableContent; + +gHTMLAttr.colgroup_valign = ["top", "middle", "bottom", "baseline"]; + +gHTMLAttr.dd = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.del = ["cite", "datetime", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.dfn = ["_core", "-", "^lang", "dir"]; + +// this is deprecated // +gHTMLAttr.dir = ["compact", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.dir_compact = ["compact"]; + +gHTMLAttr.div = ["align", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.div_align = gHAlignJustify; + +gHTMLAttr.dl = ["compact", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.dl_compact = ["compact"]; + +gHTMLAttr.dt = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.em = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.fieldset = ["_core", "-", "^lang", "dir"]; + +// this is deprecated // +gHTMLAttr.font = ["+size", "color", "face", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.font_color = gHTMLColors; + +gHTMLAttr.form = [ + "$action", + "$method", + "enctype", + "accept", + "name", + "accept-charset", + "target", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.form_method = ["get", "post"]; + +gHTMLAttr.form_enctype = ["application/x-www-form-urlencoded"]; + +gHTMLAttr.form_target = gTarget; + +gHTMLAttr.frame = [ + "longdesc", + "name", + "src", + "#frameborder", + "#marginwidth", + "#marginheight", + "noresize", + "$scrolling", +]; + +gHTMLAttr.frame_frameborder = ["1", "0"]; + +gHTMLAttr.frame_noresize = ["noresize"]; + +gHTMLAttr.frame_scrolling = ["auto", "yes", "no"]; + +gHTMLAttr.frameset = ["rows", "cols", "-", "_core"]; + +gHTMLAttr.h1 = ["align", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.h1_align = gHAlignJustify; + +gHTMLAttr.h2 = ["align", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.h2_align = gHAlignJustify; + +gHTMLAttr.h3 = ["align", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.h3_align = gHAlignJustify; + +gHTMLAttr.h4 = ["align", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.h4_align = gHAlignJustify; + +gHTMLAttr.h5 = ["align", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.h5_align = gHAlignJustify; + +gHTMLAttr.h6 = ["align", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.h6_align = gHAlignJustify; + +gHTMLAttr.head = ["profile", "-", "^lang", "dir"]; + +gHTMLAttr.hr = [ + "align", + "noshade", + "#size", + "%width", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.hr_align = gHAlign; + +gHTMLAttr.hr_noshade = ["noshade"]; + +gHTMLAttr.html = ["version", "-", "^lang", "dir"]; + +gHTMLAttr.i = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.iframe = [ + "longdesc", + "name", + "src", + "$frameborder", + "marginwidth", + "marginheight", + "$scrolling", + "align", + "%height", + "%width", + "-", + "_core", +]; + +gHTMLAttr.iframe_frameborder = ["1", "0"]; + +gHTMLAttr.iframe_scrolling = ["auto", "yes", "no"]; + +gHTMLAttr.iframe_align = ["top", "middle", "bottom", "left", "right"]; + +gHTMLAttr.img = [ + "$src", + "$alt", + "longdesc", + "name", + "%height", + "%width", + "usemap", + "ismap", + "align", + "#border", + "#hspace", + "#vspace", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.img_ismap = ["ismap"]; + +gHTMLAttr.img_align = ["top", "middle", "bottom", "left", "right"]; + +gHTMLAttr.input = [ + "$type", + "name", + "value", + "checked", + "disabled", + "readonly", + "#size", + "#maxlength", + "src", + "alt", + "usemap", + "ismap", + "#tabindex", + "!accesskey", + "accept", + "align", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.input_type = [ + "text", + "password", + "checkbox", + "radio", + "submit", + "reset", + "file", + "hidden", + "image", + "button", +]; + +gHTMLAttr.input_checked = ["checked"]; + +gHTMLAttr.input_disabled = ["disabled"]; + +gHTMLAttr.input_readonly = ["readonly"]; + +gHTMLAttr.input_ismap = ["ismap"]; + +gHTMLAttr.input_align = ["top", "middle", "bottom", "left", "right"]; + +gHTMLAttr.ins = ["cite", "datetime", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.isindex = ["prompt", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.kbd = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.label = ["for", "!accesskey", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.legend = ["!accesskey", "align", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.legend_align = ["top", "bottom", "left", "right"]; + +gHTMLAttr.li = ["type", "#value", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.li_type = ["disc", "square", "circle", "-", "1", "a", "A", "i", "I"]; + +gHTMLAttr.link = [ + "charset", + "href", + "^hreflang", + "type", + "rel", + "rev", + "media", + "target", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.link_target = gTarget; + +gHTMLAttr.link_rel = [ + "alternate", + "stylesheet", + "start", + "next", + "prev", + "contents", + "index", + "glossary", + "copyright", + "chapter", + "section", + "subsection", + "appendix", + "help", + "bookmark", +]; + +gHTMLAttr.link_rev = [ + "alternate", + "stylesheet", + "start", + "next", + "prev", + "contents", + "index", + "glossary", + "copyright", + "chapter", + "section", + "subsection", + "appendix", + "help", + "bookmark", +]; + +gHTMLAttr.map = ["$name", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.menu = ["compact", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.menu_compact = ["compact"]; + +gHTMLAttr.meta = [ + "http-equiv", + "name", + "$content", + "scheme", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.noframes = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.noscript = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.object = [ + "declare", + "classid", + "codebase", + "data", + "type", + "codetype", + "archive", + "standby", + "%height", + "%width", + "usemap", + "name", + "#tabindex", + "align", + "#border", + "#hspace", + "#vspace", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.object_declare = ["declare"]; + +gHTMLAttr.object_align = ["top", "middle", "bottom", "left", "right"]; + +gHTMLAttr.ol = ["type", "compact", "#start", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.ol_type = ["1", "a", "A", "i", "I"]; + +gHTMLAttr.ol_compact = ["compact"]; + +gHTMLAttr.optgroup = ["disabled", "$label", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.optgroup_disabled = ["disabled"]; + +gHTMLAttr.option = [ + "selected", + "disabled", + "label", + "value", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.option_selected = ["selected"]; + +gHTMLAttr.option_disabled = ["disabled"]; + +gHTMLAttr.p = ["align", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.p_align = gHAlignJustify; + +gHTMLAttr.param = ["^id", "$name", "value", "$valuetype", "type"]; + +gHTMLAttr.param_valuetype = ["data", "ref", "object"]; + +gHTMLAttr.pre = ["%width", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.q = ["cite", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.s = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.samp = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.script = ["charset", "$type", "language", "src", "defer"]; + +gHTMLAttr.script_defer = ["defer"]; + +gHTMLAttr.select = [ + "name", + "#size", + "multiple", + "disabled", + "#tabindex", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.select_multiple = ["multiple"]; + +gHTMLAttr.select_disabled = ["disabled"]; + +gHTMLAttr.small = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.span = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.strike = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.strong = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.style = ["$type", "media", "title", "-", "^lang", "dir"]; + +gHTMLAttr.sub = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.sup = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.table = [ + "summary", + "%width", + "#border", + "frame", + "rules", + "#cellspacing", + "#cellpadding", + "align", + "bgcolor", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.table_frame = [ + "void", + "above", + "below", + "hsides", + "lhs", + "rhs", + "vsides", + "box", + "border", +]; + +gHTMLAttr.table_rules = ["none", "groups", "rows", "cols", "all"]; + +// Note; This is alignment of the table, +// not table contents, like all other table child elements +gHTMLAttr.table_align = gHAlign; + +gHTMLAttr.table_bgcolor = gHTMLColors; + +gHTMLAttr.tbody = [ + "align", + "!char", + "#charoff", + "valign", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.tbody_align = gHAlignTableContent; + +gHTMLAttr.tbody_valign = gVAlignTable; + +gHTMLAttr.td = [ + "abbr", + "axis", + "headers", + "scope", + "$#rowspan", + "$#colspan", + "align", + "!char", + "#charoff", + "valign", + "nowrap", + "bgcolor", + "%width", + "%height", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.td_scope = ["row", "col", "rowgroup", "colgroup"]; + +gHTMLAttr.td_rowspan = [ + "1", // default +]; + +gHTMLAttr.td_colspan = [ + "1", // default +]; + +gHTMLAttr.td_align = gHAlignTableContent; + +gHTMLAttr.td_valign = gVAlignTable; + +gHTMLAttr.td_nowrap = ["nowrap"]; + +gHTMLAttr.td_bgcolor = gHTMLColors; + +gHTMLAttr.textarea = [ + "name", + "$#rows", + "$#cols", + "disabled", + "readonly", + "#tabindex", + "!accesskey", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.textarea_disabled = ["disabled"]; + +gHTMLAttr.textarea_readonly = ["readonly"]; + +gHTMLAttr.tfoot = [ + "align", + "!char", + "#charoff", + "valign", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.tfoot_align = gHAlignTableContent; + +gHTMLAttr.tfoot_valign = gVAlignTable; + +gHTMLAttr.th = [ + "abbr", + "axis", + "headers", + "scope", + "$#rowspan", + "$#colspan", + "align", + "!char", + "#charoff", + "valign", + "nowrap", + "bgcolor", + "%width", + "%height", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.th_scope = ["row", "col", "rowgroup", "colgroup"]; + +gHTMLAttr.th_rowspan = [ + "1", // default +]; + +gHTMLAttr.th_colspan = [ + "1", // default +]; + +gHTMLAttr.th_align = gHAlignTableContent; + +gHTMLAttr.th_valign = gVAlignTable; + +gHTMLAttr.th_nowrap = ["nowrap"]; + +gHTMLAttr.th_bgcolor = gHTMLColors; + +gHTMLAttr.thead = [ + "align", + "!char", + "#charoff", + "valign", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.thead_align = gHAlignTableContent; + +gHTMLAttr.thead_valign = gVAlignTable; + +gHTMLAttr.title = ["^lang", "dir"]; + +gHTMLAttr.tr = [ + "align", + "!char", + "#charoff", + "valign", + "bgcolor", + "-", + "_core", + "-", + "^lang", + "dir", +]; + +gHTMLAttr.tr_align = gHAlignTableContent; + +gHTMLAttr.tr_valign = gVAlignTable; + +gHTMLAttr.tr_bgcolor = gHTMLColors; + +gHTMLAttr.tt = ["_core", "-", "^lang", "dir"]; + +gHTMLAttr.u = ["_core", "-", "^lang", "dir"]; +gHTMLAttr.ul = ["type", "compact", "-", "_core", "-", "^lang", "dir"]; + +gHTMLAttr.ul_type = ["disc", "square", "circle"]; + +gHTMLAttr.ul_compact = ["compact"]; + +// Prefix with "_" since this is reserved (it's stripped out) +gHTMLAttr._var = ["_core", "-", "^lang", "dir"]; + +// ================ JS Attributes ================ // +// These are element specific even handlers. +/* Most all elements use gCoreJSEvents, so those + are assumed except for those listed here with "noEvents" +*/ + +gJSAttr.a = ["onfocus", "onblur"]; + +gJSAttr.area = ["onfocus", "onblur"]; + +gJSAttr.body = ["onload", "onupload"]; + +gJSAttr.button = ["onfocus", "onblur"]; + +gJSAttr.form = ["onsubmit", "onreset"]; + +gJSAttr.frameset = ["onload", "onunload"]; + +gJSAttr.input = ["onfocus", "onblur", "onselect", "onchange"]; + +gJSAttr.label = ["onfocus", "onblur"]; + +gJSAttr.select = ["onfocus", "onblur", "onchange"]; + +gJSAttr.textarea = ["onfocus", "onblur", "onselect", "onchange"]; + +// Elements that don't have JSEvents: +gJSAttr.font = ["noJSEvents"]; + +gJSAttr.applet = ["noJSEvents"]; + +gJSAttr.isindex = ["noJSEvents"]; + +gJSAttr.iframe = ["noJSEvents"]; diff --git a/comm/suite/editor/components/dialogs/content/EdAECSSAttributes.js b/comm/suite/editor/components/dialogs/content/EdAECSSAttributes.js new file mode 100644 index 0000000000..977068bd70 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdAECSSAttributes.js @@ -0,0 +1,146 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdAdvancedEdit.js */ +/* import-globals-from EdDialogCommon.js */ + +// build attribute list in tree form from element attributes +function BuildCSSAttributeTable() { + var style = gElement.style; + if (style == undefined) { + dump("Inline styles undefined\n"); + return; + } + + var declLength = style.length; + + if (declLength == undefined || declLength == 0) { + if (declLength == undefined) { + dump("Failed to query the number of inline style declarations\n"); + } + + return; + } + + if (declLength > 0) { + for (var i = 0; i < declLength; ++i) { + var name = style.item(i); + var value = style.getPropertyValue(name); + AddTreeItem(name, value, "CSSAList", CSSAttrs); + } + } + + ClearCSSInputWidgets(); +} + +function onChangeCSSAttribute() { + var name = TrimString(gDialog.AddCSSAttributeNameInput.value); + if (!name) { + return; + } + + var value = TrimString(gDialog.AddCSSAttributeValueInput.value); + + // First try to update existing attribute + // If not found, add new attribute + if (!UpdateExistingAttribute(name, value, "CSSAList") && value) { + AddTreeItem(name, value, "CSSAList", CSSAttrs); + } +} + +function ClearCSSInputWidgets() { + gDialog.AddCSSAttributeTree.view.selection.clearSelection(); + gDialog.AddCSSAttributeNameInput.value = ""; + gDialog.AddCSSAttributeValueInput.value = ""; + SetTextboxFocus(gDialog.AddCSSAttributeNameInput); +} + +function onSelectCSSTreeItem() { + if (!gDoOnSelectTree) { + return; + } + + var tree = gDialog.AddCSSAttributeTree; + if (tree && tree.view.selection.count) { + gDialog.AddCSSAttributeNameInput.value = GetTreeItemAttributeStr( + getSelectedItem(tree) + ); + gDialog.AddCSSAttributeValueInput.value = GetTreeItemValueStr( + getSelectedItem(tree) + ); + } +} + +function onInputCSSAttributeName() { + var attName = TrimString( + gDialog.AddCSSAttributeNameInput.value + ).toLowerCase(); + var newValue = ""; + + var existingValue = GetAndSelectExistingAttributeValue(attName, "CSSAList"); + if (existingValue) { + newValue = existingValue; + } + + gDialog.AddCSSAttributeValueInput.value = newValue; +} + +function editCSSAttributeValue(targetCell) { + if (IsNotTreeHeader(targetCell)) { + gDialog.AddCSSAttributeValueInput.inputField.select(); + } +} + +function UpdateCSSAttributes() { + var CSSAList = document.getElementById("CSSAList"); + var styleString = ""; + for (var i = 0; i < CSSAList.childNodes.length; i++) { + var item = CSSAList.childNodes[i]; + var name = GetTreeItemAttributeStr(item); + var value = GetTreeItemValueStr(item); + // this code allows users to be sloppy in typing in values, and enter + // things like "foo: " and "bar;". This will trim off everything after the + // respective character. + if (name.includes(":")) { + name = name.substring(0, name.lastIndexOf(":")); + } + if (value.includes(";")) { + value = value.substring(0, value.lastIndexOf(";")); + } + if (i == CSSAList.childNodes.length - 1) { + // Last property. + styleString += name + ": " + value + ";"; + } else { + styleString += name + ": " + value + "; "; + } + } + if (styleString) { + // Use editor transactions if modifying the element directly in the document + doRemoveAttribute("style"); + doSetAttribute("style", styleString); // NOTE BUG 18894!!! + } else if (gElement.getAttribute("style")) { + doRemoveAttribute("style"); + } +} + +function RemoveCSSAttribute() { + // We only allow 1 selected item + if (gDialog.AddCSSAttributeTree.view.selection.count) { + // Remove the item from the tree + // We always rebuild complete "style" string, + // so no list of "removed" items + getSelectedItem(gDialog.AddCSSAttributeTree).remove(); + + ClearCSSInputWidgets(); + } +} + +function SelectCSSTree(index) { + gDoOnSelectTree = false; + try { + gDialog.AddCSSAttributeTree.selectedIndex = index; + } catch (e) {} + gDoOnSelectTree = true; +} diff --git a/comm/suite/editor/components/dialogs/content/EdAEHTMLAttributes.js b/comm/suite/editor/components/dialogs/content/EdAEHTMLAttributes.js new file mode 100644 index 0000000000..1f96762754 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdAEHTMLAttributes.js @@ -0,0 +1,367 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdAdvancedEdit.js */ +/* import-globals-from EdDialogCommon.js */ + +function BuildHTMLAttributeNameList() { + gDialog.AddHTMLAttributeNameInput.removeAllItems(); + + var elementName = gElement.localName; + var attNames = gHTMLAttr[elementName]; + + if (attNames && attNames.length) { + var menuitem; + + for (var i = 0; i < attNames.length; i++) { + var name = attNames[i]; + + if (name == "_core") { + // Signal to append the common 'core' attributes. + for (var j = 0; j < gCoreHTMLAttr.length; j++) { + name = gCoreHTMLAttr[j]; + + // only filtering rule used for core attributes as of 8-20-01 + // Add more rules if necessary. + if (name.includes("^")) { + name = name.replace(/\^/g, ""); + menuitem = gDialog.AddHTMLAttributeNameInput.appendItem(name, name); + menuitem.setAttribute("limitFirstChar", "true"); + } else { + gDialog.AddHTMLAttributeNameInput.appendItem(name, name); + } + } + } else if (name == "-") { + // Signal for separator + var popup = gDialog.AddHTMLAttributeNameInput.menupopup; + if (popup) { + var sep = document.createXULElement("menuseparator"); + if (sep) { + popup.appendChild(sep); + } + } + } else { + // Get information about value filtering + let forceOneChar = name.includes("!"); + let forceInteger = name.includes("#"); + let forceSignedInteger = name.includes("+"); + let forceIntOrPercent = name.includes("%"); + let limitFirstChar = name.includes("^"); + // let required = name.includes("$"); + + // Strip flag characters + name = name.replace(/[!^#%$+]/g, ""); + + menuitem = gDialog.AddHTMLAttributeNameInput.appendItem(name, name); + if (menuitem) { + // Signify "required" attributes by special style + // TODO: Don't do this until next version, when we add + // explanatory text and an 'Autofill Required Attributes' button + // if (required) + // menuitem.setAttribute("class", "menuitem-highlight-1"); + + // Set flags to filter value input + if (forceOneChar) { + menuitem.setAttribute("forceOneChar", "true"); + } + if (limitFirstChar) { + menuitem.setAttribute("limitFirstChar", "true"); + } + if (forceInteger) { + menuitem.setAttribute("forceInteger", "true"); + } + if (forceSignedInteger) { + menuitem.setAttribute("forceSignedInteger", "true"); + } + if (forceIntOrPercent) { + menuitem.setAttribute("forceIntOrPercent", "true"); + } + } + } + } + } +} + +// build attribute list in tree form from element attributes +function BuildHTMLAttributeTable() { + var nodeMap = gElement.attributes; + var i; + if (nodeMap.length > 0) { + var added = false; + for (i = 0; i < nodeMap.length; i++) { + let name = nodeMap[i].name.trim().toLowerCase(); + if ( + CheckAttributeNameSimilarity(nodeMap[i].nodeName, HTMLAttrs) || + name.startsWith("on") || + name == "style" + ) { + continue; // repeated or non-HTML attribute, ignore this one and go to next + } + if ( + !name.startsWith("_moz") && + AddTreeItem(name, nodeMap[i].value, "HTMLAList", HTMLAttrs) + ) { + added = true; + } + } + + if (added) { + SelectHTMLTree(0); + } + } +} + +function ClearHTMLInputWidgets() { + gDialog.AddHTMLAttributeTree.view.selection.clearSelection(); + gDialog.AddHTMLAttributeNameInput.value = ""; + gDialog.AddHTMLAttributeValueInput.value = ""; + SetTextboxFocus(gDialog.AddHTMLAttributeNameInput); +} + +function onSelectHTMLTreeItem() { + if (!gDoOnSelectTree) { + return; + } + + var tree = gDialog.AddHTMLAttributeTree; + if (tree && tree.view.selection.count) { + var inputName = TrimString( + gDialog.AddHTMLAttributeNameInput.value + ).toLowerCase(); + var selectedItem = getSelectedItem(tree); + var selectedName = selectedItem.firstChild.firstChild.getAttribute("label"); + + if (inputName == selectedName) { + // Already editing selected name - just update the value input + gDialog.AddHTMLAttributeValueInput.value = GetTreeItemValueStr( + selectedItem + ); + } else { + gDialog.AddHTMLAttributeNameInput.value = selectedName; + + // Change value input based on new selected name + onInputHTMLAttributeName(); + } + } +} + +function onInputHTMLAttributeName() { + let attName = gDialog.AddHTMLAttributeNameInput.value.toLowerCase().trim(); + + // Clear value widget, but prevent triggering update in tree + gUpdateTreeValue = false; + gDialog.AddHTMLAttributeValueInput.value = ""; + gUpdateTreeValue = true; + + if (attName) { + // Get value list for current attribute name + var valueListName; + + // Most elements have the "dir" attribute, + // so we have just one array for the allowed values instead + // requiring duplicate entries for each element in EdAEAttributes.js + if (attName == "dir") { + valueListName = "all_dir"; + } else { + valueListName = gElement.localName + "_" + attName; + } + + // Strip off leading "_" we sometimes use (when element name is reserved word) + if (valueListName.startsWith("_")) { + valueListName = valueListName.slice(1); + } + + var newValue = ""; + var listLen = 0; + + // Index to which widget we were using to edit the value + var deckIndex = gDialog.AddHTMLAttributeValueDeck.getAttribute( + "selectedIndex" + ); + + if (valueListName in gHTMLAttr) { + var valueList = gHTMLAttr[valueListName]; + + listLen = valueList.length; + if (listLen == 1) { + newValue = valueList[0]; + } + + // Note: For case where "value list" is actually just + // one (default) item, don't use menulist for that + if (listLen > 1) { + gDialog.AddHTMLAttributeValueMenulist.removeAllItems(); + + if (deckIndex != "1") { + // Switch to using editable menulist + gDialog.AddHTMLAttributeValueInput = + gDialog.AddHTMLAttributeValueMenulist; + gDialog.AddHTMLAttributeValueDeck.setAttribute("selectedIndex", "1"); + } + // Rebuild the list + for (var i = 0; i < listLen; i++) { + if (valueList[i] == "-") { + // Signal for separator + var popup = gDialog.AddHTMLAttributeValueInput.menupopup; + if (popup) { + var sep = document.createXULElement("menuseparator"); + if (sep) { + popup.appendChild(sep); + } + } + } else { + gDialog.AddHTMLAttributeValueMenulist.appendItem( + valueList[i], + valueList[i] + ); + } + } + } + } + + if (listLen <= 1 && deckIndex != "0") { + // No list: Use textbox for input instead + gDialog.AddHTMLAttributeValueInput = gDialog.AddHTMLAttributeValueTextbox; + gDialog.AddHTMLAttributeValueDeck.setAttribute("selectedIndex", "0"); + } + + // If attribute already exists in tree, use associated value, + // else use default found above + var existingValue = GetAndSelectExistingAttributeValue( + attName, + "HTMLAList" + ); + if (existingValue) { + newValue = existingValue; + } + + gDialog.AddHTMLAttributeValueInput.value = newValue; + + if (!existingValue) { + onInputHTMLAttributeValue(); + } + } +} + +function onInputHTMLAttributeValue() { + if (!gUpdateTreeValue) { + return; + } + + var name = TrimString(gDialog.AddHTMLAttributeNameInput.value); + if (!name) { + return; + } + + // Trim spaces only from left since we must allow spaces within the string + // (we always reset the input field's value below) + var value = TrimStringLeft(gDialog.AddHTMLAttributeValueInput.value); + if (value) { + // Do value filtering based on type of attribute + // (Do not use "forceInteger()" to avoid multiple + // resetting of input's value and flickering) + var selectedItem = gDialog.AddHTMLAttributeNameInput.selectedItem; + + if (selectedItem) { + if ( + selectedItem.getAttribute("forceOneChar") == "true" && + value.length > 1 + ) { + value = value.slice(0, 1); + } + + if (selectedItem.getAttribute("forceIntOrPercent") == "true") { + // Allow integer with optional "%" as last character + var percent = TrimStringRight(value).slice(-1); + value = value.replace(/\D+/g, ""); + if (percent == "%") { + value += percent; + } + } else if (selectedItem.getAttribute("forceInteger") == "true") { + value = value.replace(/\D+/g, ""); + } else if (selectedItem.getAttribute("forceSignedInteger") == "true") { + // Allow integer with optional "+" or "-" as first character + var sign = value[0]; + value = value.replace(/\D+/g, ""); + if (sign == "+" || sign == "-") { + value = sign + value; + } + } + + // Special case attributes + if (selectedItem.getAttribute("limitFirstChar") == "true") { + // Limit first character to letter, and all others to + // letters, numbers, and a few others + value = value + .replace(/^[^a-zA-Z\u0080-\uFFFF]/, "") + .replace(/[^a-zA-Z0-9_\.\-\:\u0080-\uFFFF]+/g, ""); + } + + // Update once only if it changed + if (value != gDialog.AddHTMLAttributeValueInput.value) { + gDialog.AddHTMLAttributeValueInput.value = value; + } + } + } + + // Update value in the tree list + // If not found, add new attribute + if (!UpdateExistingAttribute(name, value, "HTMLAList") && value) { + AddTreeItem(name, value, "HTMLAList", HTMLAttrs); + } +} + +function editHTMLAttributeValue(targetCell) { + if (IsNotTreeHeader(targetCell)) { + gDialog.AddHTMLAttributeValueInput.select(); + } +} + +// update the object with added and removed attributes +function UpdateHTMLAttributes() { + var HTMLAList = document.getElementById("HTMLAList"); + var i; + + // remove removed attributes + for (i = 0; i < HTMLRAttrs.length; i++) { + var name = HTMLRAttrs[i]; + + if (gElement.hasAttribute(name)) { + doRemoveAttribute(name); + } + } + + // Set added or changed attributes + for (i = 0; i < HTMLAList.childNodes.length; i++) { + var item = HTMLAList.childNodes[i]; + doSetAttribute(GetTreeItemAttributeStr(item), GetTreeItemValueStr(item)); + } +} + +function RemoveHTMLAttribute() { + // We only allow 1 selected item + if (gDialog.AddHTMLAttributeTree.view.selection.count) { + var item = getSelectedItem(gDialog.AddHTMLAttributeTree); + var attr = GetTreeItemAttributeStr(item); + + // remove the item from the attribute array + HTMLRAttrs[HTMLRAttrs.length] = attr; + RemoveNameFromAttArray(attr, HTMLAttrs); + + // Remove the item from the tree + item.remove(); + + // Clear inputs and selected item in tree + ClearHTMLInputWidgets(); + } +} + +function SelectHTMLTree(index) { + gDoOnSelectTree = false; + try { + gDialog.AddHTMLAttributeTree.selectedIndex = index; + } catch (e) {} + gDoOnSelectTree = true; +} diff --git a/comm/suite/editor/components/dialogs/content/EdAEJSEAttributes.js b/comm/suite/editor/components/dialogs/content/EdAEJSEAttributes.js new file mode 100644 index 0000000000..c15c938b3e --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdAEJSEAttributes.js @@ -0,0 +1,200 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdAdvancedEdit.js */ +/* import-globals-from EdDialogCommon.js */ + +function BuildJSEAttributeNameList() { + gDialog.AddJSEAttributeNameList.removeAllItems(); + + // Get events specific to current element + var elementName = gElement.localName; + if (elementName in gJSAttr) { + var attNames = gJSAttr[elementName]; + var i; + var popup; + var sep; + + if (attNames && attNames.length) { + // Since we don't allow user-editable JS events yet (but we will soon) + // simply remove the JS tab to not allow adding JS events + if (attNames[0] == "noJSEvents") { + var tab = document.getElementById("tabJSE"); + if (tab) { + tab.remove(); + } + + return; + } + + for (i = 0; i < attNames.length; i++) { + gDialog.AddJSEAttributeNameList.appendItem(attNames[i], attNames[i]); + } + + popup = gDialog.AddJSEAttributeNameList.firstChild; + if (popup) { + sep = document.createXULElement("menuseparator"); + if (sep) { + popup.appendChild(sep); + } + } + } + } + + // Always add core JS events unless we aborted above + for (i = 0; i < gCoreJSEvents.length; i++) { + if (gCoreJSEvents[i] == "-") { + if (!popup) { + popup = gDialog.AddJSEAttributeNameList.firstChild; + } + + sep = document.createXULElement("menuseparator"); + + if (popup && sep) { + popup.appendChild(sep); + } + } else { + gDialog.AddJSEAttributeNameList.appendItem( + gCoreJSEvents[i], + gCoreJSEvents[i] + ); + } + } + + gDialog.AddJSEAttributeNameList.selectedIndex = 0; + + // Use current name and value of first tree item if it exists + onSelectJSETreeItem(); +} + +// build attribute list in tree form from element attributes +function BuildJSEAttributeTable() { + var nodeMap = gElement.attributes; + if (nodeMap.length > 0) { + var added = false; + for (var i = 0; i < nodeMap.length; i++) { + let name = nodeMap[i].nodeName.toLowerCase(); + if (CheckAttributeNameSimilarity(nodeMap[i].nodeName, JSEAttrs)) { + // Repeated or non-JS handler, ignore this one and go to next. + continue; + } + if (!name.startsWith("on")) { + // Attribute isn't an event handler. + continue; + } + var value = gElement.getAttribute(nodeMap[i].nodeName); + if (AddTreeItem(name, value, "JSEAList", JSEAttrs)) { + // add item to tree + added = true; + } + } + + // Select first item + if (added) { + gDialog.AddJSEAttributeTree.selectedIndex = 0; + } + } +} + +function onSelectJSEAttribute() { + if (!gDoOnSelectTree) { + return; + } + + gDialog.AddJSEAttributeValueInput.value = GetAndSelectExistingAttributeValue( + gDialog.AddJSEAttributeNameList.label, + "JSEAList" + ); +} + +function onSelectJSETreeItem() { + var tree = gDialog.AddJSEAttributeTree; + if (tree && tree.view.selection.count) { + // Select attribute name in list + gDialog.AddJSEAttributeNameList.value = GetTreeItemAttributeStr( + getSelectedItem(tree) + ); + + // Set value input to that in tree (no need to update this in the tree) + gUpdateTreeValue = false; + gDialog.AddJSEAttributeValueInput.value = GetTreeItemValueStr( + getSelectedItem(tree) + ); + gUpdateTreeValue = true; + } +} + +function onInputJSEAttributeValue() { + if (gUpdateTreeValue) { + var name = TrimString(gDialog.AddJSEAttributeNameList.label); + var value = TrimString(gDialog.AddJSEAttributeValueInput.value); + + // Update value in the tree list + // Since we have a non-editable menulist, + // we MUST automatically add the event attribute if it doesn't exist + if (!UpdateExistingAttribute(name, value, "JSEAList") && value) { + AddTreeItem(name, value, "JSEAList", JSEAttrs); + } + } +} + +function editJSEAttributeValue(targetCell) { + if (IsNotTreeHeader(targetCell)) { + gDialog.AddJSEAttributeValueInput.inputField.select(); + } +} + +function UpdateJSEAttributes() { + var JSEAList = document.getElementById("JSEAList"); + var i; + + // remove removed attributes + for (i = 0; i < JSERAttrs.length; i++) { + var name = JSERAttrs[i]; + + if (gElement.hasAttribute(name)) { + doRemoveAttribute(name); + } + } + + // Add events + for (i = 0; i < JSEAList.childNodes.length; i++) { + var item = JSEAList.childNodes[i]; + + // set the event handler + doSetAttribute(GetTreeItemAttributeStr(item), GetTreeItemValueStr(item)); + } +} + +function RemoveJSEAttribute() { + // This differs from HTML and CSS panels: + // We reselect after removing, because there is not + // editable attribute name input, so we can't clear that + // like we do in other panels + var newIndex = gDialog.AddJSEAttributeTree.selectedIndex; + + // We only allow 1 selected item + if (gDialog.AddJSEAttributeTree.view.selection.count) { + var item = getSelectedItem(gDialog.AddJSEAttributeTree); + + // Name is the text of the treecell + var attr = GetTreeItemAttributeStr(item); + + // remove the item from the attribute array + if (newIndex >= JSEAttrs.length - 1) { + newIndex--; + } + + // remove the item from the attribute array + JSERAttrs[JSERAttrs.length] = attr; + RemoveNameFromAttArray(attr, JSEAttrs); + + // Remove the item from the tree + item.remove(); + + // Reselect an item + gDialog.AddJSEAttributeTree.selectedIndex = newIndex; + } +} diff --git a/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.js b/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.js new file mode 100644 index 0000000000..60e9009905 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.js @@ -0,0 +1,342 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdAEAttributes.js */ +/* import-globals-from EdAECSSAttributes.js */ +/* import-globals-from EdAEHTMLAttributes.js */ +/* import-globals-from EdAEJSEAttributes.js */ +/* import-globals-from EdDialogCommon.js */ + +/** ************ GLOBALS **************/ +var gElement = null; // handle to actual element edited + +var HTMLAttrs = []; // html attributes +var CSSAttrs = []; // css attributes +var JSEAttrs = []; // js events + +var HTMLRAttrs = []; // removed html attributes +var JSERAttrs = []; // removed js events + +/* Set false to allow changing selection in tree + without doing "onselect" handler actions +*/ +var gDoOnSelectTree = true; +var gUpdateTreeValue = true; + +/** ************ INITIALISATION && SETUP **************/ + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +/** + * function : void Startup(); + * parameters : none + * returns : none + * desc. : startup and initialisation, prepares dialog. + **/ +function Startup() { + var editor = GetCurrentEditor(); + + // Element to edit is passed in + if (!editor || !window.arguments[1]) { + dump("Advanced Edit: No editor or element to edit not supplied\n"); + window.close(); + return; + } + // This is the return value for the parent, + // who only needs to know if OK was clicked + window.opener.AdvancedEditOK = false; + + // The actual element edited (not a copy!) + gElement = window.arguments[1]; + + // place the tag name in the header + var tagLabel = document.getElementById("tagLabel"); + tagLabel.setAttribute("value", "<" + gElement.localName + ">"); + + // Create dialog object to store controls for easy access + gDialog.AddHTMLAttributeNameInput = document.getElementById( + "AddHTMLAttributeNameInput" + ); + + // We use a <deck> to switch between editable menulist and textbox + gDialog.AddHTMLAttributeValueDeck = document.getElementById( + "AddHTMLAttributeValueDeck" + ); + gDialog.AddHTMLAttributeValueMenulist = document.getElementById( + "AddHTMLAttributeValueMenulist" + ); + gDialog.AddHTMLAttributeValueTextbox = document.getElementById( + "AddHTMLAttributeValueTextbox" + ); + gDialog.AddHTMLAttributeValueInput = gDialog.AddHTMLAttributeValueTextbox; + + gDialog.AddHTMLAttributeTree = document.getElementById("HTMLATree"); + gDialog.AddCSSAttributeNameInput = document.getElementById( + "AddCSSAttributeNameInput" + ); + gDialog.AddCSSAttributeValueInput = document.getElementById( + "AddCSSAttributeValueInput" + ); + gDialog.AddCSSAttributeTree = document.getElementById("CSSATree"); + gDialog.AddJSEAttributeNameList = document.getElementById( + "AddJSEAttributeNameList" + ); + gDialog.AddJSEAttributeValueInput = document.getElementById( + "AddJSEAttributeValueInput" + ); + gDialog.AddJSEAttributeTree = document.getElementById("JSEATree"); + gDialog.okButton = document.documentElement.getButton("accept"); + + // build the attribute trees + BuildHTMLAttributeTable(); + BuildCSSAttributeTable(); + BuildJSEAttributeTable(); + + // Build attribute name arrays for menulists + BuildJSEAttributeNameList(); + BuildHTMLAttributeNameList(); + // No menulists for CSS panel (yet) + + // Set focus to Name editable menulist in HTML panel + SetTextboxFocus(gDialog.AddHTMLAttributeNameInput); + + // size the dialog properly + window.sizeToContent(); + + SetWindowLocation(); +} + +/** + * function : bool onAccept ( void ); + * parameters : none + * returns : boolean true to close the window + * desc. : event handler for ok button + **/ +function onAccept() { + var editor = GetCurrentEditor(); + editor.beginTransaction(); + try { + // Update our gElement attributes + UpdateHTMLAttributes(); + UpdateCSSAttributes(); + UpdateJSEAttributes(); + } catch (ex) { + dump(ex); + } + editor.endTransaction(); + + window.opener.AdvancedEditOK = true; + SaveWindowLocation(); +} + +// Helpers for removing and setting attributes +// Use editor transactions if modifying the element already in the document +// (Temporary element from a property dialog won't have a parent node) +function doRemoveAttribute(attrib) { + try { + var editor = GetCurrentEditor(); + if (gElement.parentNode) { + editor.removeAttribute(gElement, attrib); + } else { + gElement.removeAttribute(attrib); + } + } catch (ex) {} +} + +function doSetAttribute(attrib, value) { + try { + var editor = GetCurrentEditor(); + if (gElement.parentNode) { + editor.setAttribute(gElement, attrib, value); + } else { + gElement.setAttribute(attrib, value); + } + } catch (ex) {} +} + +/** + * function : bool CheckAttributeNameSimilarity ( string attName, array attArray ); + * parameters : attribute to look for, array of current attributes + * returns : true if attribute already exists, false if it does not + * desc. : checks to see if any other attributes by the same name as the arg supplied + * already exist. + **/ +function CheckAttributeNameSimilarity(attName, attArray) { + for (var i = 0; i < attArray.length; i++) { + if (attName.toLowerCase() == attArray[i].toLowerCase()) { + return true; + } + } + return false; +} + +/** + * function : bool UpdateExistingAttribute ( string attName, string attValue, string treeChildrenId ); + * parameters : attribute to look for, new value, ID of <treeChildren> node in XUL tree + * returns : true if attribute already exists in tree, false if it does not + * desc. : checks to see if any other attributes by the same name as the arg supplied + * already exist while setting the associated value if different from current value + **/ +function UpdateExistingAttribute(attName, attValue, treeChildrenId) { + var treeChildren = document.getElementById(treeChildrenId); + if (!treeChildren) { + return false; + } + + var name; + var i; + attName = TrimString(attName).toLowerCase(); + attValue = TrimString(attValue); + + for (i = 0; i < treeChildren.childNodes.length; i++) { + var item = treeChildren.childNodes[i]; + name = GetTreeItemAttributeStr(item); + if (name.toLowerCase() == attName) { + // Set the text in the "value' column treecell + SetTreeItemValueStr(item, attValue); + + // Select item just changed, + // but don't trigger the tree's onSelect handler + gDoOnSelectTree = false; + try { + selectTreeItem(treeChildren, item); + } catch (e) {} + gDoOnSelectTree = true; + + return true; + } + } + return false; +} + +/** + * function : string GetAndSelectExistingAttributeValue ( string attName, string treeChildrenId ); + * parameters : attribute to look for, ID of <treeChildren> node in XUL tree + * returns : value in from the tree or empty string if name not found + **/ +function GetAndSelectExistingAttributeValue(attName, treeChildrenId) { + if (!attName) { + return ""; + } + + var treeChildren = document.getElementById(treeChildrenId); + var name; + var i; + + for (i = 0; i < treeChildren.childNodes.length; i++) { + var item = treeChildren.childNodes[i]; + name = GetTreeItemAttributeStr(item); + if (name.toLowerCase() == attName.toLowerCase()) { + // Select item in the tree + // but don't trigger the tree's onSelect handler + gDoOnSelectTree = false; + try { + selectTreeItem(treeChildren, item); + } catch (e) {} + gDoOnSelectTree = true; + + // Get the text in the "value' column treecell + return GetTreeItemValueStr(item); + } + } + + // Attribute doesn't exist in tree, so remove selection + gDoOnSelectTree = false; + try { + treeChildren.parentNode.view.selection.clearSelection(); + } catch (e) {} + gDoOnSelectTree = true; + + return ""; +} + +/* Tree structure: + <treeItem> + <treeRow> + <treeCell> // Name Cell + <treeCell // Value Cell +*/ +function GetTreeItemAttributeStr(treeItem) { + if (treeItem) { + return TrimString(treeItem.firstChild.firstChild.getAttribute("label")); + } + + return ""; +} + +function GetTreeItemValueStr(treeItem) { + if (treeItem) { + return TrimString(treeItem.firstChild.lastChild.getAttribute("label")); + } + + return ""; +} + +function SetTreeItemValueStr(treeItem, value) { + if (treeItem && GetTreeItemValueStr(treeItem) != value) { + treeItem.firstChild.lastChild.setAttribute("label", value); + } +} + +function IsNotTreeHeader(treeCell) { + if (treeCell) { + return treeCell.parentNode.parentNode.nodeName != "treehead"; + } + + return false; +} + +function RemoveNameFromAttArray(attName, attArray) { + for (var i = 0; i < attArray.length; i++) { + if (attName.toLowerCase() == attArray[i].toLowerCase()) { + // Remove 1 array item + attArray.splice(i, 1); + break; + } + } +} + +// adds a generalised treeitem. +function AddTreeItem(name, value, treeChildrenId, attArray) { + attArray[attArray.length] = name; + var treeChildren = document.getElementById(treeChildrenId); + var treeitem = document.createXULElement("treeitem"); + var treerow = document.createXULElement("treerow"); + + var attrCell = document.createXULElement("treecell"); + attrCell.setAttribute("class", "propertylist"); + attrCell.setAttribute("label", name); + + var valueCell = document.createXULElement("treecell"); + valueCell.setAttribute("class", "propertylist"); + valueCell.setAttribute("label", value); + + treerow.appendChild(attrCell); + treerow.appendChild(valueCell); + treeitem.appendChild(treerow); + treeChildren.appendChild(treeitem); + + // Select item just added, but suppress calling the onSelect handler. + gDoOnSelectTree = false; + try { + selectTreeItem(treeChildren, treeitem); + } catch (e) {} + gDoOnSelectTree = true; + + return treeitem; +} + +function selectTreeItem(treeChildren, item) { + var index = treeChildren.parentNode.view.getIndexOfItem(item); + treeChildren.parentNode.view.selection.select(index); +} + +function getSelectedItem(tree) { + if (tree.view.selection.count == 1) { + return tree.view.getItemAtIndex(tree.currentIndex); + } + return null; +} diff --git a/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.xhtml b/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.xhtml new file mode 100644 index 0000000000..94942709a1 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.xhtml @@ -0,0 +1,182 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<!-- first checkin of the year 2000! --> +<!-- Ben Goodger, 12:50AM, 01/00/00 NZST --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/menulist.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EdAdvancedEdit.dtd"> + +<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + id="advancedEditDlg" + style="width: 40em;" + title="&WindowTitle.label;" + onload="Startup()"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <!-- element page functions --> + <script src="chrome://editor/content/EdAEHTMLAttributes.js"/> + <script src="chrome://editor/content/EdAECSSAttributes.js"/> + <script src="chrome://editor/content/EdAEJSEAttributes.js"/> + <script src="chrome://editor/content/EdAEAttributes.js"/> + + <!-- global dialog functions --> + <script src="chrome://editor/content/EdAdvancedEdit.js"/> + + <script src="chrome://messenger/content/customElements.js"/> + + <hbox> + <label value="¤tattributesfor.label;"/> + <label class="header" id="tagLabel"/> + </hbox> + + <separator class="thin"/> + + <tabbox flex="1"> + <tabs> + <tab label="&tabHTML.label;"/> + <tab label="&tabCSS.label;"/> + <tab label="&tabJSE.label;" id="tabJSE"/> + </tabs> + <tabpanels flex="1"> + <!-- ============================================================== --> + <!-- HTML Attributes --> + <!-- ============================================================== --> + <vbox> + <tree id="HTMLATree" class="AttributesTree" flex="1" + hidecolumnpicker="true" seltype="single" + onselect="onSelectHTMLTreeItem();" + onclick="onSelectHTMLTreeItem();" + ondblclick="editHTMLAttributeValue(event.target);"> + <treecols> + <treecol id="HTMLAttrCol" flex="35" label="&tree.attributeHeader.label;"/> + <splitter class="tree-splitter"/> + <treecol id="HTMLValCol" flex="65" label="&tree.valueHeader.label;"/> + </treecols> + <treechildren id="HTMLAList" flex="1"/> + </tree> + <hbox align="center"> + <label value="&editAttribute.label;"/> + <spacer flex="1"/> + <button label="&removeAttribute.label;" oncommand="RemoveHTMLAttribute();"/> + </hbox> + <grid> + <columns> + <column flex="1"/><column flex="1"/> + </columns> + <rows> + <row equalsize="always"> + <label control="AddHTMLAttributeNameInput" value="&AttName.label;"/> + <label control="AddHTMLAttributeValueInput" value="&AttValue.label;"/> + </row> + <row align="top" equalsize="always"> + <!-- Lists are built at runtime --> + <menulist is="menulist-editable" id="AddHTMLAttributeNameInput" + editable="true" flex="1" + oninput="onInputHTMLAttributeName();" + oncommand="onInputHTMLAttributeName();"/> + <deck id="AddHTMLAttributeValueDeck" selectedIndex="0"> + <hbox align="top"> + <textbox id="AddHTMLAttributeValueTextbox" flex="1" + oninput="onInputHTMLAttributeValue();"/> + </hbox> + <hbox align="top"> + <menulist is="menulist-editable" id="AddHTMLAttributeValueMenulist" + editable="true" flex="1" + oninput="onInputHTMLAttributeValue();" + oncommand="onInputHTMLAttributeValue();"/> + </hbox> + </deck> + </row> + </rows> + </grid> + </vbox> + <!-- ============================================================== --> + <!-- CSS Attributes --> + <!-- ============================================================== --> + <vbox> + <tree id="CSSATree" class="AttributesTree" flex="1" + hidecolumnpicker="true" seltype="single" + onselect="onSelectCSSTreeItem();" + onclick="onSelectCSSTreeItem();" + ondblclick="editCSSAttributeValue(event.target);"> + <treecols> + <treecol id="CSSPropCol" flex="35" label="&tree.propertyHeader.label;"/> + <splitter class="tree-splitter"/> + <treecol id="CSSValCol" flex="65" label="&tree.valueHeader.label;"/> + </treecols> + <treechildren id="CSSAList" flex="1"/> + </tree> + <hbox align="center"> + <label value="&editAttribute.label;"/> + <spacer flex="1"/> + <button label="&removeAttribute.label;" oncommand="RemoveCSSAttribute();"/> + </hbox> + <grid> + <columns> + <column flex="1"/><column flex="1"/> + </columns> + <rows> + <row equalsize="always"> + <label value="&PropertyName.label;"/> + <label value="&AttValue.label;"/> + </row> + <row align="top" equalsize="always"> + <textbox id="AddCSSAttributeNameInput" flex="1" + oninput="onInputCSSAttributeName();"/> + <textbox id="AddCSSAttributeValueInput" flex="1" + oninput="onChangeCSSAttribute();"/> + </row> + </rows> + </grid> + </vbox> + <!-- ============================================================== --> + <!-- JavaScript Event Handlers --> + <!-- ============================================================== --> + <vbox> + <tree id="JSEATree" class="AttributesTree" flex="1" + hidecolumnpicker="true" seltype="single" + onselect="onSelectJSETreeItem();" + onclick="onSelectJSETreeItem();" + ondblclick="editJSEAttributeValue(event.target);"> + <treecols> + <treecol id="AttrCol" flex="35" label="&tree.attributeHeader.label;"/> + <splitter class="tree-splitter"/> + <treecol id="HeaderCol" flex="65" label="&tree.valueHeader.label;"/> + </treecols> + <treechildren id="JSEAList" flex="1"/> + </tree> + <hbox align="center"> + <label value="&editAttribute.label;"/> + <spacer flex="1"/> + <button label="&removeAttribute.label;" oncommand="RemoveJSEAttribute()"/> + </hbox> + <grid> + <columns> + <column flex="1"/><column flex="1"/> + </columns> + <rows> + <row equalsize="always"> + <label value="&AttName.label;"/> + <label value="&AttValue.label;"/> + </row> + <row align="top" equalsize="always"> + <!-- List is built at runtime --> + <menulist id="AddJSEAttributeNameList" flex="1" + oncommand="onSelectJSEAttribute();"/> + <textbox id="AddJSEAttributeValueInput" flex="1" + oninput="onInputJSEAttributeValue();"/> + </row> + </rows> + </grid> + </vbox> + </tabpanels> + </tabbox> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdButtonProps.js b/comm/suite/editor/components/dialogs/content/EdButtonProps.js new file mode 100644 index 0000000000..1cd0ee7365 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdButtonProps.js @@ -0,0 +1,146 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +var insertNew; +var buttonElement; + +// dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + var editor = GetCurrentEditor(); + if (!editor) { + window.close(); + return; + } + + gDialog = { + buttonType: document.getElementById("ButtonType"), + buttonName: document.getElementById("ButtonName"), + buttonValue: document.getElementById("ButtonValue"), + buttonDisabled: document.getElementById("ButtonDisabled"), + buttonTabIndex: document.getElementById("ButtonTabIndex"), + buttonAccessKey: document.getElementById("ButtonAccessKey"), + MoreSection: document.getElementById("MoreSection"), + MoreFewerButton: document.getElementById("MoreFewerButton"), + RemoveButton: document.getElementById("RemoveButton"), + }; + + // Get a single selected button element + const kTagName = "button"; + try { + buttonElement = editor.getSelectedElement(kTagName); + } catch (e) {} + + if (buttonElement) { + // We found an element and don't need to insert one + insertNew = false; + } else { + insertNew = true; + + // We don't have an element selected, + // so create one with default attributes + try { + buttonElement = editor.createElementWithDefaults(kTagName); + } catch (e) {} + + if (!buttonElement) { + dump("Failed to get selected element or create a new one!\n"); + window.close(); + return; + } + // Hide button removing existing button + gDialog.RemoveButton.hidden = true; + } + + // Make a copy to use for AdvancedEdit + globalElement = buttonElement.cloneNode(false); + + InitDialog(); + + InitMoreFewer(); + + gDialog.buttonType.focus(); + + SetWindowLocation(); +} + +function InitDialog() { + var type = globalElement.getAttribute("type"); + var index = 0; + switch (type) { + case "button": + index = 2; + break; + case "reset": + index = 1; + break; + } + gDialog.buttonType.selectedIndex = index; + gDialog.buttonName.value = globalElement.getAttribute("name"); + gDialog.buttonValue.value = globalElement.getAttribute("value"); + gDialog.buttonDisabled.setAttribute( + "checked", + globalElement.hasAttribute("disabled") + ); + gDialog.buttonTabIndex.value = globalElement.getAttribute("tabindex"); + gDialog.buttonAccessKey.value = globalElement.getAttribute("accesskey"); +} + +function RemoveButton() { + RemoveContainer(buttonElement); + SaveWindowLocation(); + window.close(); +} + +function ValidateData() { + var attributes = { + type: ["", "reset", "button"][gDialog.buttonType.selectedIndex], + name: gDialog.buttonName.value, + value: gDialog.buttonValue.value, + tabindex: gDialog.buttonTabIndex.value, + accesskey: gDialog.buttonAccessKey.value, + }; + for (var a in attributes) { + if (attributes[a]) { + globalElement.setAttribute(a, attributes[a]); + } else { + globalElement.removeAttribute(a); + } + } + if (gDialog.buttonDisabled.checked) { + globalElement.setAttribute("disabled", ""); + } else { + globalElement.removeAttribute("disabled"); + } + return true; +} + +function onAccept() { + // All values are valid - copy to actual element in doc or + // element created to insert + ValidateData(); + + var editor = GetCurrentEditor(); + + editor.cloneAttributes(buttonElement, globalElement); + + if (insertNew) { + if (!InsertElementAroundSelection(buttonElement)) { + /* eslint-disable-next-line no-unsanitized/property */ + buttonElement.innerHTML = editor.outputToString( + "text/html", + kOutputSelectionOnly + ); + editor.insertElementAtSelection(buttonElement, true); + } + } + + SaveWindowLocation(); +} diff --git a/comm/suite/editor/components/dialogs/content/EdButtonProps.xhtml b/comm/suite/editor/components/dialogs/content/EdButtonProps.xhtml new file mode 100644 index 0000000000..70e4774f13 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdButtonProps.xhtml @@ -0,0 +1,92 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % edButtonProperties SYSTEM "chrome://editor/locale/EditorButtonProperties.dtd"> +%edButtonProperties; +<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd"> +%edDialogOverlay; +]> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup();" + buttons="accept,cancel"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdButtonProps.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <groupbox> + <hbox class="groupbox-title"> + <label class="header">&Settings.label;</label> + </hbox> + <grid><columns><column/><column/></columns> + <rows> + <row align="center"> + <label control="ButtonType" value="&ButtonType.label;" accesskey="&ButtonType.accesskey;"/> + <menulist id="ButtonType"> + <menupopup> + <menuitem label="&submit.value;"/> + <menuitem label="&reset.value;"/> + <menuitem label="&button.value;"/> + </menupopup> + </menulist> + </row> + <row align="center"> + <label control="ButtonName" value="&ButtonName.label;" accesskey="&ButtonName.accesskey;"/> + <textbox id="ButtonName"/> + </row> + <row align="center"> + <label control="ButtonValue" value="&ButtonValue.label;" accesskey="&ButtonValue.accesskey;"/> + <textbox id="ButtonValue"/> + </row> + </rows> + </grid> + <hbox> + <button id="MoreFewerButton" oncommand="onMoreFewer();" persist="more"/> + </hbox> + <grid id="MoreSection"><columns><column/><column/></columns> + <rows> + <row> + <spacer/> + <checkbox id="ButtonDisabled" label="&ButtonDisabled.label;" accesskey="&ButtonDisabled.accesskey;"/> + </row> + <row align="center"> + <label control="ButtonTabIndex" value="&tabIndex.label;" accesskey="&tabIndex.accesskey;"/> + <hbox> + <textbox id="ButtonTabIndex" class="narrow" oninput="forceInteger(this.id);"/> + </hbox> + </row> + <row align="center"> + <label control="ButtonAccessKey" value="&AccessKey.label;" accesskey="&AccessKey.accesskey;"/> + <hbox> + <textbox id="ButtonAccessKey" class="narrow"/> + </hbox> + </row> + </rows> + </grid> + </groupbox> + + <!-- from EdDialogOverlay --> + <hbox flex="1" style="margin-top: 0.2em"> + <button id="RemoveButton" label="&RemoveButton.label;" accesskey="&RemoveButton.accesskey;" oncommand="RemoveButton();"/> + <!-- This will right-align the button --> + <spacer flex="1"/> + <button id="AdvancedEditButton" + oncommand="onAdvancedEdit();" + label="&AdvancedEditButton.label;" + accesskey="&AdvancedEditButton.accessKey;" + tooltiptext="&AdvancedEditButton.tooltip;"/> + </hbox> + <separator class="groove"/> + +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdColorPicker.js b/comm/suite/editor/components/dialogs/content/EdColorPicker.js new file mode 100644 index 0000000000..95ce279368 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdColorPicker.js @@ -0,0 +1,297 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +// Cancel() is in EdDialogCommon.js + +var insertNew = true; +var tagname = "TAG NAME"; +var gColor = ""; +var LastPickedColor = ""; +var ColorType = "Text"; +var TextType = false; +var HighlightType = false; +var TableOrCell = false; +var LastPickedIsDefault = true; +var NoDefault = false; +var gColorObj; + +// dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancelColor); + +function Startup() { + if (!window.arguments[1]) { + dump("EdColorPicker: Missing color object param\n"); + return; + } + + // window.arguments[1] is object to get initial values and return color data + gColorObj = window.arguments[1]; + gColorObj.Cancel = false; + + gDialog.ColorPicker = document.getElementById("ColorPicker"); + gDialog.ColorInput = document.getElementById("ColorInput"); + gDialog.LastPickedButton = document.getElementById("LastPickedButton"); + gDialog.LastPickedColor = document.getElementById("LastPickedColor"); + gDialog.CellOrTableGroup = document.getElementById("CellOrTableGroup"); + gDialog.TableRadio = document.getElementById("TableRadio"); + gDialog.CellRadio = document.getElementById("CellRadio"); + gDialog.ColorSwatch = document.getElementById("ColorPickerSwatch"); + gDialog.Ok = document.documentElement.getButton("accept"); + + // The type of color we are setting: + // text: Text, Link, ActiveLink, VisitedLink, + // or background: Page, Table, or Cell + if (gColorObj.Type) { + ColorType = gColorObj.Type; + // Get string for dialog title from passed-in type + // (note constraint on editor.properties string name) + let IsCSSPrefChecked = Services.prefs.getBoolPref("editor.use_css"); + + if (GetCurrentEditor()) { + if (ColorType == "Page" && IsCSSPrefChecked && IsHTMLEditor()) { + document.title = GetString("BlockColor"); + } else { + document.title = GetString(ColorType + "Color"); + } + } + } + + gDialog.ColorInput.value = ""; + var tmpColor; + var haveTableRadio = false; + + switch (ColorType) { + case "Page": + tmpColor = gColorObj.PageColor; + if (tmpColor && tmpColor.toLowerCase() != "window") { + gColor = tmpColor; + } + break; + case "Table": + if (gColorObj.TableColor) { + gColor = gColorObj.TableColor; + } + break; + case "Cell": + if (gColorObj.CellColor) { + gColor = gColorObj.CellColor; + } + break; + case "TableOrCell": + TableOrCell = true; + document.getElementById("TableOrCellGroup").collapsed = false; + haveTableRadio = true; + if (gColorObj.SelectedType == "Cell") { + gColor = gColorObj.CellColor; + gDialog.CellOrTableGroup.selectedItem = gDialog.CellRadio; + gDialog.CellRadio.focus(); + } else { + gColor = gColorObj.TableColor; + gDialog.CellOrTableGroup.selectedItem = gDialog.TableRadio; + gDialog.TableRadio.focus(); + } + break; + case "Highlight": + HighlightType = true; + if (gColorObj.HighlightColor) { + gColor = gColorObj.HighlightColor; + } + break; + default: + // Any other type will change some kind of text, + TextType = true; + tmpColor = gColorObj.TextColor; + if (tmpColor && tmpColor.toLowerCase() != "windowtext") { + gColor = gColorObj.TextColor; + } + break; + } + + // Set initial color in input field and in the colorpicker + SetCurrentColor(gColor); + gDialog.ColorPicker.value = gColor; + + // Use last-picked colors passed in, or those persistent on dialog + if (TextType) { + if (!("LastTextColor" in gColorObj) || !gColorObj.LastTextColor) { + gColorObj.LastTextColor = gDialog.LastPickedColor.getAttribute( + "LastTextColor" + ); + } + LastPickedColor = gColorObj.LastTextColor; + } else if (HighlightType) { + if (!("LastHighlightColor" in gColorObj) || !gColorObj.LastHighlightColor) { + gColorObj.LastHighlightColor = gDialog.LastPickedColor.getAttribute( + "LastHighlightColor" + ); + } + LastPickedColor = gColorObj.LastHighlightColor; + } else { + if ( + !("LastBackgroundColor" in gColorObj) || + !gColorObj.LastBackgroundColor + ) { + gColorObj.LastBackgroundColor = gDialog.LastPickedColor.getAttribute( + "LastBackgroundColor" + ); + } + LastPickedColor = gColorObj.LastBackgroundColor; + } + + // Set method to detect clicking on OK button + // so we don't get fooled by changing "default" behavior + gDialog.Ok.setAttribute("onclick", "SetDefaultToOk()"); + + if (!LastPickedColor) { + // Hide the button, as there is no last color available. + gDialog.LastPickedButton.hidden = true; + } else { + gDialog.LastPickedColor.setAttribute( + "style", + "background-color: " + LastPickedColor + ); + + // Make "Last-picked" the default button, until the user selects a color. + gDialog.Ok.removeAttribute("default"); + gDialog.LastPickedButton.setAttribute("default", "true"); + } + + // Caller can prevent user from submitting an empty, i.e., default color + NoDefault = gColorObj.NoDefault; + if (NoDefault) { + // Hide the "Default button -- user must pick a color + document.getElementById("DefaultColorButton").collapsed = true; + } + + // Set focus to colorpicker if not set to table radio buttons above + if (!haveTableRadio) { + gDialog.ColorPicker.focus(); + } + + SetWindowLocation(); +} + +function SelectColor() { + var color = gDialog.ColorPicker.value; + if (color) { + SetCurrentColor(color); + } +} + +function RemoveColor() { + SetCurrentColor(""); + gDialog.ColorInput.focus(); + SetDefaultToOk(); +} + +function SelectColorByKeypress(aEvent) { + if (aEvent.charCode == aEvent.DOM_VK_SPACE) { + SelectColor(); + SetDefaultToOk(); + } +} + +function SelectLastPickedColor() { + SetCurrentColor(LastPickedColor); + if (onAccept()) { + // window.close(); + return true; + } + + return false; +} + +function SetCurrentColor(color) { + // TODO: Validate color? + if (!color) { + color = ""; + } + gColor = TrimString(color).toLowerCase(); + if (gColor == "mixed") { + gColor = ""; + } + gDialog.ColorInput.value = gColor; + SetColorSwatch(); +} + +function SetColorSwatch() { + // TODO: DON'T ALLOW SPACES? + var color = TrimString(gDialog.ColorInput.value); + if (color) { + gDialog.ColorSwatch.setAttribute("style", "background-color:" + color); + gDialog.ColorSwatch.removeAttribute("default"); + } else { + gDialog.ColorSwatch.setAttribute("style", "background-color:inherit"); + gDialog.ColorSwatch.setAttribute("default", "true"); + } +} + +function SetDefaultToOk() { + gDialog.LastPickedButton.removeAttribute("default"); + gDialog.Ok.setAttribute("default", "true"); + LastPickedIsDefault = false; +} + +function ValidateData() { + if (LastPickedIsDefault) { + gColor = LastPickedColor; + } else { + gColor = gDialog.ColorInput.value; + } + + gColor = TrimString(gColor).toLowerCase(); + + // TODO: Validate the color string! + + if (NoDefault && !gColor) { + ShowInputErrorMessage(GetString("NoColorError")); + SetTextboxFocus(gDialog.ColorInput); + return false; + } + return true; +} + +function onAccept(event) { + if (!ValidateData()) { + event.preventDefault(); + return; + } + + // Set return values and save in persistent color attributes + if (TextType) { + gColorObj.TextColor = gColor; + if (gColor.length > 0) { + gDialog.LastPickedColor.setAttribute("LastTextColor", gColor); + gColorObj.LastTextColor = gColor; + } + } else if (HighlightType) { + gColorObj.HighlightColor = gColor; + if (gColor.length > 0) { + gDialog.LastPickedColor.setAttribute("LastHighlightColor", gColor); + gColorObj.LastHighlightColor = gColor; + } + } else { + gColorObj.BackgroundColor = gColor; + if (gColor.length > 0) { + gDialog.LastPickedColor.setAttribute("LastBackgroundColor", gColor); + gColorObj.LastBackgroundColor = gColor; + } + // If table or cell requested, tell caller which element to set on + if (TableOrCell && gDialog.TableRadio.selected) { + gColorObj.Type = "Table"; + } + } + SaveWindowLocation(); +} + +function onCancelColor() { + // Tells caller that user canceled + gColorObj.Cancel = true; + SaveWindowLocation(); +} diff --git a/comm/suite/editor/components/dialogs/content/EdColorPicker.xhtml b/comm/suite/editor/components/dialogs/content/EdColorPicker.xhtml new file mode 100644 index 0000000000..c18bc90e62 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdColorPicker.xhtml @@ -0,0 +1,56 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EdColorPicker.dtd"> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup()"> + + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdColorPicker.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <hbox id="TableOrCellGroup" align="center" collapsed="true"> + <label control="CellOrTableGroup" value="&background.label;" accesskey="&background.accessKey;"/> + <radiogroup id="CellOrTableGroup" orient="horizontal"> + <radio id="TableRadio" label="&table.label;" accesskey="&table.accessKey;"/> + <radio id="CellRadio" label="&cell.label;" accesskey="&cell.accessKey;"/> + </radiogroup> + </hbox> + <label value="&chooseColor1.label;"/> + <html:input type="color" id="ColorPicker" + onclick="SetDefaultToOk();" + ondblclick="if (onAccept()) { window.close(); }" + onkeypress="SelectColorByKeypress(event);" + onchange="SelectColor();"/> + + <spacer class="spacer"/> + <vbox flex="1"> + <button id="LastPickedButton" crop="right" oncommand="SelectLastPickedColor();"> + <spacer id="LastPickedColor" + LastTextColor="" LastBackgroundColor="" + persist="LastTextColor LastBackgroundColor"/> + <label value="&lastPickedColor.label;" accesskey="&lastPickedColor.accessKey;" flex="1" style="text-align: center;"/> + </button> + <label value="&chooseColor2.label;" accesskey="&chooseColor2.accessKey;" control="ColorInput"/> + <label value="&setColorExample.label;"/> + <hbox align="center" flex="1="> + <textbox id="ColorInput" style="width: 8em" oninput="SetColorSwatch(); SetDefaultToOk();"/> + <spacer flex="1"/> + <spacer id="ColorPickerSwatch"/> + <spacer flex="1"/> + <button id="DefaultColorButton" label="&default.label;" accesskey="&default.accessKey;" + style="margin-right:0px;" oncommand="RemoveColor()"/> + </hbox> + </vbox> + <separator class="groove"/> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdColorProps.js b/comm/suite/editor/components/dialogs/content/EdColorProps.js new file mode 100644 index 0000000000..62d3f29c9a --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdColorProps.js @@ -0,0 +1,476 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + Behavior notes: + Radio buttons select "UseDefaultColors" vs. "UseCustomColors" modes. + If any color attribute is set in the body, mode is "Custom Colors", + even if 1 or more (but not all) are actually null (= "use default") + When in "Custom Colors" mode, all colors will be set on body tag, + even if they are just default colors, to assure compatible colors in page. + User cannot select "use default" for individual colors +*/ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +// Cancel() is in EdDialogCommon.js + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +var gBodyElement; +var prefs; +var gBackgroundImage; + +// Initialize in case we can't get them from prefs??? +var defaultTextColor = "#000000"; +var defaultLinkColor = "#000099"; +var defaultActiveColor = "#000099"; +var defaultVisitedColor = "#990099"; +var defaultBackgroundColor = "#FFFFFF"; +const styleStr = "style"; +const textStr = "text"; +const linkStr = "link"; +const vlinkStr = "vlink"; +const alinkStr = "alink"; +const bgcolorStr = "bgcolor"; +const backgroundStr = "background"; +const cssColorStr = "color"; +const cssBackgroundColorStr = "background-color"; +const cssBackgroundImageStr = "background-image"; +const colorStyle = cssColorStr + ": "; +const backColorStyle = cssBackgroundColorStr + ": "; +const backImageStyle = "; " + cssBackgroundImageStr + ": url("; + +var customTextColor; +var customLinkColor; +var customActiveColor; +var customVisitedColor; +var customBackgroundColor; +var previewBGColor; + +// dialog initialization code +function Startup() { + var editor = GetCurrentEditor(); + if (!editor) { + window.close(); + return; + } + + gDialog.ColorPreview = document.getElementById("ColorPreview"); + gDialog.NormalText = document.getElementById("NormalText"); + gDialog.LinkText = document.getElementById("LinkText"); + gDialog.ActiveLinkText = document.getElementById("ActiveLinkText"); + gDialog.VisitedLinkText = document.getElementById("VisitedLinkText"); + gDialog.PageColorGroup = document.getElementById("PageColorGroup"); + gDialog.DefaultColorsRadio = document.getElementById("DefaultColorsRadio"); + gDialog.CustomColorsRadio = document.getElementById("CustomColorsRadio"); + gDialog.BackgroundImageInput = document.getElementById( + "BackgroundImageInput" + ); + + try { + gBodyElement = editor.rootElement; + } catch (e) {} + + if (!gBodyElement) { + dump("Failed to get BODY element!\n"); + window.close(); + } + + // Set element we will edit + globalElement = gBodyElement.cloneNode(false); + + // Initialize default colors from browser prefs + var browserColors = GetDefaultBrowserColors(); + if (browserColors) { + // Use author's browser pref colors passed into dialog + defaultTextColor = browserColors.TextColor; + defaultLinkColor = browserColors.LinkColor; + defaultActiveColor = browserColors.ActiveLinkColor; + defaultVisitedColor = browserColors.VisitedLinkColor; + defaultBackgroundColor = browserColors.BackgroundColor; + } + + // We only need to test for this once per dialog load + gHaveDocumentUrl = GetDocumentBaseUrl(); + + InitDialog(); + + gDialog.PageColorGroup.focus(); + + SetWindowLocation(); +} + +function InitDialog() { + // Get image from document + gBackgroundImage = GetHTMLOrCSSStyleValue( + globalElement, + backgroundStr, + cssBackgroundImageStr + ); + if (/url\((.*)\)/.test(gBackgroundImage)) { + gBackgroundImage = RegExp.$1; + } + + if (gBackgroundImage) { + // Shorten data URIs for display. + shortenImageData(gBackgroundImage, gDialog.BackgroundImageInput); + gDialog.ColorPreview.setAttribute( + styleStr, + backImageStyle + gBackgroundImage + ");" + ); + } + + SetRelativeCheckbox(); + + customTextColor = GetHTMLOrCSSStyleValue(globalElement, textStr, cssColorStr); + customTextColor = ConvertRGBColorIntoHEXColor(customTextColor); + customLinkColor = globalElement.getAttribute(linkStr); + customActiveColor = globalElement.getAttribute(alinkStr); + customVisitedColor = globalElement.getAttribute(vlinkStr); + customBackgroundColor = GetHTMLOrCSSStyleValue( + globalElement, + bgcolorStr, + cssBackgroundColorStr + ); + customBackgroundColor = ConvertRGBColorIntoHEXColor(customBackgroundColor); + + var haveCustomColor = + customTextColor || + customLinkColor || + customVisitedColor || + customActiveColor || + customBackgroundColor; + + // Set default color explicitly for any that are missing + // PROBLEM: We are using "windowtext" and "window" for the Windows OS + // default color values. This works with CSS in preview window, + // but we should NOT use these as values for HTML attributes! + + if (!customTextColor) { + customTextColor = defaultTextColor; + } + if (!customLinkColor) { + customLinkColor = defaultLinkColor; + } + if (!customActiveColor) { + customActiveColor = defaultActiveColor; + } + if (!customVisitedColor) { + customVisitedColor = defaultVisitedColor; + } + if (!customBackgroundColor) { + customBackgroundColor = defaultBackgroundColor; + } + + if (haveCustomColor) { + // If any colors are set, then check the "Custom" radio button + gDialog.PageColorGroup.selectedItem = gDialog.CustomColorsRadio; + UseCustomColors(); + } else { + gDialog.PageColorGroup.selectedItem = gDialog.DefaultColorsRadio; + UseDefaultColors(); + } +} + +function GetColorAndUpdate(ColorWellID) { + // Only allow selecting when in custom mode + if (!gDialog.CustomColorsRadio.selected) { + return; + } + + var colorWell = document.getElementById(ColorWellID); + if (!colorWell) { + return; + } + + // Don't allow a blank color, i.e., using the "default" + var colorObj = { + NoDefault: true, + Type: "", + TextColor: 0, + PageColor: 0, + Cancel: false, + }; + + switch (ColorWellID) { + case "textCW": + colorObj.Type = "Text"; + colorObj.TextColor = customTextColor; + break; + case "linkCW": + colorObj.Type = "Link"; + colorObj.TextColor = customLinkColor; + break; + case "activeCW": + colorObj.Type = "ActiveLink"; + colorObj.TextColor = customActiveColor; + break; + case "visitedCW": + colorObj.Type = "VisitedLink"; + colorObj.TextColor = customVisitedColor; + break; + case "backgroundCW": + colorObj.Type = "Page"; + colorObj.PageColor = customBackgroundColor; + break; + } + + window.openDialog( + "chrome://editor/content/EdColorPicker.xhtml", + "_blank", + "chrome,close,titlebar,modal", + "", + colorObj + ); + + // User canceled the dialog + if (colorObj.Cancel) { + return; + } + + var color = ""; + switch (ColorWellID) { + case "textCW": + color = customTextColor = colorObj.TextColor; + break; + case "linkCW": + color = customLinkColor = colorObj.TextColor; + break; + case "activeCW": + color = customActiveColor = colorObj.TextColor; + break; + case "visitedCW": + color = customVisitedColor = colorObj.TextColor; + break; + case "backgroundCW": + color = customBackgroundColor = colorObj.BackgroundColor; + break; + } + + setColorWell(ColorWellID, color); + SetColorPreview(ColorWellID, color); +} + +function SetColorPreview(ColorWellID, color) { + switch (ColorWellID) { + case "textCW": + gDialog.NormalText.setAttribute(styleStr, colorStyle + color); + break; + case "linkCW": + gDialog.LinkText.setAttribute(styleStr, colorStyle + color); + break; + case "activeCW": + gDialog.ActiveLinkText.setAttribute(styleStr, colorStyle + color); + break; + case "visitedCW": + gDialog.VisitedLinkText.setAttribute(styleStr, colorStyle + color); + break; + case "backgroundCW": + // Must combine background color and image style values + var styleValue = backColorStyle + color; + if (gBackgroundImage) { + styleValue += ";" + backImageStyle + gBackgroundImage + ");"; + } + + gDialog.ColorPreview.setAttribute(styleStr, styleValue); + previewBGColor = color; + break; + } +} + +function UseCustomColors() { + SetElementEnabledById("TextButton", true); + SetElementEnabledById("LinkButton", true); + SetElementEnabledById("ActiveLinkButton", true); + SetElementEnabledById("VisitedLinkButton", true); + SetElementEnabledById("BackgroundButton", true); + SetElementEnabledById("Text", true); + SetElementEnabledById("Link", true); + SetElementEnabledById("Active", true); + SetElementEnabledById("Visited", true); + SetElementEnabledById("Background", true); + + SetColorPreview("textCW", customTextColor); + SetColorPreview("linkCW", customLinkColor); + SetColorPreview("activeCW", customActiveColor); + SetColorPreview("visitedCW", customVisitedColor); + SetColorPreview("backgroundCW", customBackgroundColor); + + setColorWell("textCW", customTextColor); + setColorWell("linkCW", customLinkColor); + setColorWell("activeCW", customActiveColor); + setColorWell("visitedCW", customVisitedColor); + setColorWell("backgroundCW", customBackgroundColor); +} + +function UseDefaultColors() { + SetColorPreview("textCW", defaultTextColor); + SetColorPreview("linkCW", defaultLinkColor); + SetColorPreview("activeCW", defaultActiveColor); + SetColorPreview("visitedCW", defaultVisitedColor); + SetColorPreview("backgroundCW", defaultBackgroundColor); + + // Setting to blank color will remove color from buttons, + setColorWell("textCW", ""); + setColorWell("linkCW", ""); + setColorWell("activeCW", ""); + setColorWell("visitedCW", ""); + setColorWell("backgroundCW", ""); + + // Disable color buttons and labels + SetElementEnabledById("TextButton", false); + SetElementEnabledById("LinkButton", false); + SetElementEnabledById("ActiveLinkButton", false); + SetElementEnabledById("VisitedLinkButton", false); + SetElementEnabledById("BackgroundButton", false); + SetElementEnabledById("Text", false); + SetElementEnabledById("Link", false); + SetElementEnabledById("Active", false); + SetElementEnabledById("Visited", false); + SetElementEnabledById("Background", false); +} + +function chooseFile() { + // Get a local image file, converted into URL format + GetLocalFileURL("img").then(fileURL => { + // Always try to relativize local file URLs + if (gHaveDocumentUrl) { + fileURL = MakeRelativeUrl(fileURL); + } + + gDialog.BackgroundImageInput.value = fileURL; + + SetRelativeCheckbox(); + ValidateAndPreviewImage(true); + SetTextboxFocus(gDialog.BackgroundImageInput); + }); +} + +function ChangeBackgroundImage() { + // Don't show error message for image while user is typing + ValidateAndPreviewImage(false); + SetRelativeCheckbox(); +} + +function ValidateAndPreviewImage(ShowErrorMessage) { + // First make a string with just background color + var styleValue = backColorStyle + previewBGColor + ";"; + + var retVal = true; + var image = TrimString(gDialog.BackgroundImageInput.value); + if (image) { + if (isImageDataShortened(image)) { + gBackgroundImage = restoredImageData(gDialog.BackgroundImageInput); + } else { + gBackgroundImage = image; + + // Display must use absolute URL if possible + var displayImage = gHaveDocumentUrl ? MakeAbsoluteUrl(image) : image; + styleValue += backImageStyle + displayImage + ");"; + } + } else { + gBackgroundImage = null; + } + + // Set style on preview (removes image if not valid) + gDialog.ColorPreview.setAttribute(styleStr, styleValue); + + // Note that an "empty" string is valid + return retVal; +} + +function ValidateData() { + var editor = GetCurrentEditor(); + try { + // Colors values are updated as they are picked, no validation necessary + if (gDialog.DefaultColorsRadio.selected) { + editor.removeAttributeOrEquivalent(globalElement, textStr, true); + globalElement.removeAttribute(linkStr); + globalElement.removeAttribute(vlinkStr); + globalElement.removeAttribute(alinkStr); + editor.removeAttributeOrEquivalent(globalElement, bgcolorStr, true); + } else { + // Do NOT accept the CSS "WindowsOS" color strings! + // Problem: We really should try to get the actual color values + // from windows, but I don't know how to do that! + var tmpColor = customTextColor.toLowerCase(); + if (tmpColor != "windowtext") { + editor.setAttributeOrEquivalent( + globalElement, + textStr, + customTextColor, + true + ); + } else { + editor.removeAttributeOrEquivalent(globalElement, textStr, true); + } + + tmpColor = customBackgroundColor.toLowerCase(); + if (tmpColor != "window") { + editor.setAttributeOrEquivalent( + globalElement, + bgcolorStr, + customBackgroundColor, + true + ); + } else { + editor.removeAttributeOrEquivalent(globalElement, bgcolorStr, true); + } + + globalElement.setAttribute(linkStr, customLinkColor); + globalElement.setAttribute(vlinkStr, customVisitedColor); + globalElement.setAttribute(alinkStr, customActiveColor); + } + + if (ValidateAndPreviewImage(true)) { + // A valid image may be null for no image + if (gBackgroundImage) { + globalElement.setAttribute(backgroundStr, gBackgroundImage); + } else { + editor.removeAttributeOrEquivalent(globalElement, backgroundStr, true); + } + + return true; + } + } catch (e) {} + return false; +} + +function onAccept(event) { + // If it's a file, convert to a data URL. + if (gBackgroundImage && /^file:/i.test(gBackgroundImage)) { + let nsFile = Services.io + .newURI(gBackgroundImage) + .QueryInterface(Ci.nsIFileURL).file; + if (nsFile.exists()) { + let reader = new FileReader(); + reader.addEventListener("load", function() { + gBackgroundImage = reader.result; + gDialog.BackgroundImageInput.value = reader.result; + if (onAccept(event)) { + window.close(); + } + }); + File.createFromNsIFile(nsFile).then(file => { + reader.readAsDataURL(file); + }); + event.preventDefault(); // Don't close just yet... + return false; + } + } + if (ValidateData()) { + // Copy attributes to element we are changing + try { + GetCurrentEditor().cloneAttributes(gBodyElement, globalElement); + } catch (e) {} + + SaveWindowLocation(); + return true; // do close the window + } + event.preventDefault(); + return false; +} diff --git a/comm/suite/editor/components/dialogs/content/EdColorProps.xhtml b/comm/suite/editor/components/dialogs/content/EdColorProps.xhtml new file mode 100644 index 0000000000..85393ed209 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdColorProps.xhtml @@ -0,0 +1,134 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % edColorPropertiesDTD SYSTEM "chrome://editor/locale/EditorColorProperties.dtd"> +%edColorPropertiesDTD; +<!ENTITY % composeEditorOverlayDTD SYSTEM "chrome://messenger/locale/messengercompose/mailComposeEditorOverlay.dtd"> +%composeEditorOverlayDTD; +<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd"> +%edDialogOverlay; +]> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup()"> + + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdColorProps.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <groupbox align="start"> + <hbox class="groupbox-title"> + <label class="header">&pageColors.label;</label> + </hbox> + <radiogroup id="PageColorGroup"> + <radio id="DefaultColorsRadio" label="&defaultColorsRadio.label;" oncommand="UseDefaultColors()" + accesskey="&defaultColorsRadio.accessKey;" + tooltiptext="&defaultColorsRadio.tooltip;" /> + <radio id="CustomColorsRadio" label="&customColorsRadio.label;" oncommand="UseCustomColors()" + accesskey="&customColorsRadio.accessKey;" + tooltiptext="&customColorsRadio.tooltip;" /> + </radiogroup> + <hbox class="indent"> + <grid> + <columns><column/><column/></columns> + <rows> + <row align="center"> + <label id="Text" control="TextButton" + value="&normalText.label;&colon.character;" + accesskey="&normalText.accessKey;"/> + <button id="TextButton" class="color-button" oncommand="GetColorAndUpdate('textCW');"> + <spacer id="textCW" class="color-well"/> + </button> + </row> + <row align="center"> + <label id="Link" control="LinkButton" + value="&linkText.label;&colon.character;" + accesskey="&linkText.accessKey;"/> + <button id="LinkButton" class="color-button" oncommand="GetColorAndUpdate('linkCW');"> + <spacer id="linkCW" class="color-well"/> + </button> + </row> + <row align="center"> + <label id="Active" control="ActiveLinkButton" + value="&activeLinkText.label;&colon.character;" + accesskey="&activeLinkText.accessKey;"/> + <button id="ActiveLinkButton" class="color-button" oncommand="GetColorAndUpdate('activeCW');"> + <spacer id="activeCW" class="color-well"/> + </button> + </row> + <row align="center"> + <label id="Visited" control="VisitedLinkButton" + value="&visitedLinkText.label;&colon.character;" + accesskey="&visitedLinkText.accessKey;"/> + <button id="VisitedLinkButton" class="color-button" oncommand="GetColorAndUpdate('visitedCW');"> + <spacer id="visitedCW" class="color-well"/> + </button> + </row> + <row align="center"> + <label id="Background" control="BackgroundButton" + value="&background.label;" + accesskey="&background.accessKey;"/> + <button id="BackgroundButton" class="color-button" oncommand="GetColorAndUpdate('backgroundCW');"> + <spacer id="backgroundCW" class="color-well"/> + </button> + </row> + </rows> + </grid> + <vbox id="ColorPreview" flex="1"> + <spacer flex="1"/> + <label class="larger" id="NormalText" value="&normalText.label;"/> + <spacer flex="1"/> + <label class="larger" id="LinkText" value="&linkText.label;"/> + <spacer flex="1"/> + <label class="larger" id="ActiveLinkText" value="&activeLinkText.label;"/> + <spacer flex="1"/> + <label class="larger" id="VisitedLinkText" value="&visitedLinkText.label;"/> + <spacer flex="1"/> + </vbox> + <spacer flex="1"/> + </hbox> + <spacer class="spacer"/> + </groupbox> + <spacer class="spacer"/> + <label control="BackgroundImageInput" + value="&backgroundImage.label;" + tooltiptext="&backgroundImage.tooltip;" + accesskey="&backgroundImage.accessKey;"/> + <tooltip id="shortenedDataURI"> + <label value="&backgroundImage.shortenedDataURI;"/> + </tooltip> + <textbox id="BackgroundImageInput" class="uri-element" oninput="ChangeBackgroundImage()" + tooltiptext="&backgroundImage.tooltip;" flex="1"/> + <hbox align="center"> + <checkbox id="MakeRelativeCheckbox" + for="BackgroundImageInput" + label="&makeUrlRelative.label;" + accesskey="&makeUrlRelative.accessKey;" + oncommand="MakeInputValueRelativeOrAbsolute(this);" + tooltiptext="&makeUrlRelative.tooltip;"/> + <spacer flex="1"/> + <button id="ChooseFile" + oncommand="chooseFile()" + label="&chooseFileButton.label;" + accesskey="&chooseFileButton.accessKey;"/> + </hbox> + <spacer class="smallspacer"/> + <hbox> + <spacer flex="1"/> + <button id="AdvancedEditButton" + oncommand="onAdvancedEdit();" + label="&AdvancedEditButton.label;" + accesskey="&AdvancedEditButton.accessKey;" + tooltiptext="&AdvancedEditButton.tooltip;"/> + </hbox> + <separator class="groove"/> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdConvertToTable.js b/comm/suite/editor/components/dialogs/content/EdConvertToTable.js new file mode 100644 index 0000000000..a149e708f8 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdConvertToTable.js @@ -0,0 +1,326 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +var gIndex; +var gCommaIndex = "0"; +var gSpaceIndex = "1"; +var gOtherIndex = "2"; + +// dialog initialization code +function Startup() { + if (!GetCurrentEditor()) { + window.close(); + return; + } + + gDialog.sepRadioGroup = document.getElementById("SepRadioGroup"); + gDialog.sepCharacterInput = document.getElementById("SepCharacterInput"); + gDialog.deleteSepCharacter = document.getElementById("DeleteSepCharacter"); + gDialog.collapseSpaces = document.getElementById("CollapseSpaces"); + + // We persist the user's separator character + gDialog.sepCharacterInput.value = gDialog.sepRadioGroup.getAttribute( + "character" + ); + + gIndex = gDialog.sepRadioGroup.getAttribute("index"); + + switch (gIndex) { + case gCommaIndex: + default: + gDialog.sepRadioGroup.selectedItem = document.getElementById("comma"); + break; + case gSpaceIndex: + gDialog.sepRadioGroup.selectedItem = document.getElementById("space"); + break; + case gOtherIndex: + gDialog.sepRadioGroup.selectedItem = document.getElementById("other"); + break; + } + + // Set initial enable state on character input and "collapse" checkbox + SelectCharacter(gIndex); + + SetWindowLocation(); +} + +function InputSepCharacter() { + var str = gDialog.sepCharacterInput.value; + + // Limit input to 1 character + if (str.length > 1) { + str = str.slice(0, 1); + } + + // We can never allow tag or entity delimiters for separator character + if (str == "<" || str == ">" || str == "&" || str == ";" || str == " ") { + str = ""; + } + + gDialog.sepCharacterInput.value = str; +} + +function SelectCharacter(radioGroupIndex) { + gIndex = radioGroupIndex; + SetElementEnabledById("SepCharacterInput", gIndex == gOtherIndex); + SetElementEnabledById("CollapseSpaces", gIndex == gSpaceIndex); +} + +/* eslint-disable complexity */ +function onAccept() { + var sepCharacter = ""; + switch (gIndex) { + case gCommaIndex: + sepCharacter = ","; + break; + case gSpaceIndex: + sepCharacter = " "; + break; + case gOtherIndex: + sepCharacter = gDialog.sepCharacterInput.value.slice(0, 1); + break; + } + + var editor = GetCurrentEditor(); + var str; + try { + str = editor.outputToString( + "text/html", + kOutputLFLineBreak | kOutputSelectionOnly + ); + } catch (e) {} + if (!str) { + SaveWindowLocation(); + return; + } + + // Replace nbsp with spaces: + str = str.replace(/\u00a0/g, " "); + + // Strip out </p> completely + str = str.replace(/\s*<\/p>\s*/g, ""); + + // Trim whitespace adjacent to <p> and <br> tags + // and replace <p> with <br> + // (which will be replaced with </tr> below) + str = str.replace(/\s*<p>\s*|\s*<br>\s*/g, "<br>"); + + // Trim leading <br>s + str = str.replace(/^(<br>)+/, ""); + + // Trim trailing <br>s + str = str.replace(/(<br>)+$/, ""); + + // Reduce multiple internal <br> to just 1 + // TODO: Maybe add a checkbox to let user decide + // str = str.replace(/(<br>)+/g, "<br>"); + + // Trim leading and trailing spaces + str = str.trim(); + + // Remove all tag contents so we don't replace + // separator character within tags + // Also converts lists to something useful + var stack = []; + var start; + var end; + var searchStart = 0; + var listSeparator = ""; + var listItemSeparator = ""; + var endList = false; + + do { + start = str.indexOf("<", searchStart); + + if (start >= 0) { + end = str.indexOf(">", start + 1); + if (end > start) { + let tagContent = str.slice(start + 1, end).trim(); + + if (/^ol|^ul|^dl/.test(tagContent)) { + // Replace list tag with <BR> to start new row + // at beginning of second or greater list tag + str = str.slice(0, start) + listSeparator + str.slice(end + 1); + if (listSeparator == "") { + listSeparator = "<br>"; + } + + // Reset for list item separation into cells + listItemSeparator = ""; + } else if (/^li|^dt|^dd/.test(tagContent)) { + // Start a new row if this is first item after the ending the last list + if (endList) { + listItemSeparator = "<br>"; + } + + // Start new cell at beginning of second or greater list items + str = str.slice(0, start) + listItemSeparator + str.slice(end + 1); + + if (endList || listItemSeparator == "") { + listItemSeparator = sepCharacter; + } + + endList = false; + } else { + // Find end tags + endList = /^\/ol|^\/ul|^\/dl/.test(tagContent); + if (endList || /^\/li|^\/dt|^\/dd/.test(tagContent)) { + // Strip out tag + str = str.slice(0, start) + str.slice(end + 1); + } else { + // Not a list-related tag: Store tag contents in an array + stack.push(tagContent); + + // Keep the "<" and ">" while removing from source string + start++; + str = str.slice(0, start) + str.slice(end); + } + } + } + searchStart = start + 1; + } + } while (start >= 0); + + // Replace separator characters with table cells + var replaceString; + if (gDialog.deleteSepCharacter.checked) { + replaceString = ""; + } else { + // Don't delete separator character, + // so include it at start of string to replace + replaceString = sepCharacter; + } + + replaceString += "<td>"; + + if (sepCharacter.length > 0) { + var tempStr = sepCharacter; + var regExpChars = ".!@#$%^&*-+[]{}()|\\/"; + if (regExpChars.includes(sepCharacter)) { + tempStr = "\\" + sepCharacter; + } + + if (gIndex == gSpaceIndex) { + // If checkbox is checked, + // one or more adjacent spaces are one separator + if (gDialog.collapseSpaces.checked) { + tempStr = "\\s+"; + } else { + tempStr = "\\s"; + } + } + var pattern = new RegExp(tempStr, "g"); + str = str.replace(pattern, replaceString); + } + + // Put back tag contents that we removed above + searchStart = 0; + var stackIndex = 0; + do { + start = str.indexOf("<", searchStart); + end = start + 1; + if (start >= 0 && str.charAt(end) == ">") { + // We really need a FIFO stack! + str = str.slice(0, end) + stack[stackIndex++] + str.slice(end); + } + searchStart = end; + } while (start >= 0); + + // End table row and start another for each br or p + str = str.replace(/\s*<br>\s*/g, "</tr>\n<tr><td>"); + + // Add the table tags and the opening and closing tr/td tags + // Default table attributes should be same as those used in nsHTMLEditor::CreateElementWithDefaults() + // (Default width="100%" is used in EdInsertTable.js) + str = + '<table border="1" width="100%" cellpadding="2" cellspacing="2">\n<tr><td>' + + str + + "</tr>\n</table>\n"; + + editor.beginTransaction(); + + // Delete the selection -- makes it easier to find where table will insert + var nodeBeforeTable = null; + var nodeAfterTable = null; + try { + editor.deleteSelection(editor.eNone, editor.eStrip); + + var anchorNodeBeforeInsert = editor.selection.anchorNode; + var offset = editor.selection.anchorOffset; + if (anchorNodeBeforeInsert.nodeType == Node.TEXT_NODE) { + // Text was split. Table should be right after the first or before + nodeBeforeTable = anchorNodeBeforeInsert.previousSibling; + nodeAfterTable = anchorNodeBeforeInsert; + } else { + // Table should be inserted right after node pointed to by selection + if (offset > 0) { + nodeBeforeTable = anchorNodeBeforeInsert.childNodes.item(offset - 1); + } + + nodeAfterTable = anchorNodeBeforeInsert.childNodes.item(offset); + } + + editor.insertHTML(str); + } catch (e) {} + + var table = null; + if (nodeAfterTable) { + var previous = nodeAfterTable.previousSibling; + if (previous && previous.nodeName.toLowerCase() == "table") { + table = previous; + } + } + if (!table && nodeBeforeTable) { + var next = nodeBeforeTable.nextSibling; + if (next && next.nodeName.toLowerCase() == "table") { + table = next; + } + } + + if (table) { + // Fixup table only if pref is set + var firstRow; + try { + if (Services.prefs.getBoolPref("editor.table.maintain_structure")) { + editor.normalizeTable(table); + } + + firstRow = editor.getFirstRow(table); + } catch (e) {} + + // Put caret in first cell + if (firstRow) { + var node2 = firstRow.firstChild; + do { + if ( + node2.nodeName.toLowerCase() == "td" || + node2.nodeName.toLowerCase() == "th" + ) { + try { + editor.selection.collapse(node2, 0); + } catch (e) {} + break; + } + node2 = node2.nextSibling; + } while (node2); + } + } + + editor.endTransaction(); + + // Save persisted attributes + gDialog.sepRadioGroup.setAttribute("index", gIndex); + if (gIndex == gOtherIndex) { + gDialog.sepRadioGroup.setAttribute("character", sepCharacter); + } + + SaveWindowLocation(); +} +/* eslint-enable complexity */ diff --git a/comm/suite/editor/components/dialogs/content/EdConvertToTable.xhtml b/comm/suite/editor/components/dialogs/content/EdConvertToTable.xhtml new file mode 100644 index 0000000000..d3d5c4a465 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdConvertToTable.xhtml @@ -0,0 +1,43 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EdConvertToTable.dtd"> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload = "Startup()" + style="min-width:20em"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <!--- Element-specific methods --> + <script src="chrome://editor/content/EdConvertToTable.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + <description class="wrap" flex="1">&instructions1.label;</description> + <description class="wrap" flex="1">&instructions2.label;</description> + <radiogroup id="SepRadioGroup" persist="index character" index="0" character=""> + <radio id="comma" label="&commaRadio.label;" oncommand="SelectCharacter('0');"/> + <radio id="space" label="&spaceRadio.label;" oncommand="SelectCharacter('1');"/> + <hbox> + <spacer class="radio-spacer"/> + <checkbox id="CollapseSpaces" label="&collapseSpaces.label;" + checked="true" persist="checked" + tooltiptext="&collapseSpaces.tooltip;"/> + </hbox> + <hbox align="center"> + <radio id="other" label="&otherRadio.label;" oncommand="SelectCharacter('2');"/> + <textbox class="narrow" id="SepCharacterInput" oninput="InputSepCharacter()"/> + </hbox> + </radiogroup> + <spacer class="spacer"/> + <checkbox id="DeleteSepCharacter" label="&deleteCharCheck.label;" persist="checked"/> + <separator class="groove"/> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdDialogCommon.js b/comm/suite/editor/components/dialogs/content/EdDialogCommon.js new file mode 100644 index 0000000000..c6b7c63778 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdDialogCommon.js @@ -0,0 +1,1038 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Each editor window must include this file + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* globals InitDialog, ChangeLinkLocation, ValidateData */ + +// Object to attach commonly-used widgets (all dialogs should use this) +var gDialog = {}; + +var gHaveDocumentUrl = false; +var gValidationError = false; + +// Use for 'defaultIndex' param in InitPixelOrPercentMenulist +const gPixel = 0; +const gPercent = 1; + +const gMaxPixels = 100000; // Used for image size, borders, spacing, and padding +// Gecko code uses 1000 for maximum rowspan, colspan +// Also, editing performance is really bad above this +const gMaxRows = 1000; +const gMaxColumns = 1000; +const gMaxTableSize = 1000000; // Width or height of table or cells + +// For dialogs that expand in size. Default is smaller size see "onMoreFewer()" below +var SeeMore = false; + +// A XUL element with id="location" for managing +// dialog location relative to parent window +var gLocation; + +// The element being edited - so AdvancedEdit can have access to it +var globalElement; + +/* Validate contents of an input field + * + * inputWidget The 'textbox' XUL element for text input of the attribute's value + * listWidget The 'menulist' XUL element for choosing "pixel" or "percent" + * May be null when no pixel/percent is used. + * minVal minimum allowed for input widget's value + * maxVal maximum allowed for input widget's value + * (when "listWidget" is used, maxVal is used for "pixel" maximum, + * 100% is assumed if "percent" is the user's choice) + * element The DOM element that we set the attribute on. May be null. + * attName Name of the attribute to set. May be null or ignored if "element" is null + * mustHaveValue If true, error dialog is displayed if "value" is empty string + * + * This calls "ValidateNumberRange()", which puts up an error dialog to inform the user. + * If error, we also: + * Shift focus and select contents of the inputWidget, + * Switch to appropriate panel of tabbed dialog if user implements "SwitchToValidate()", + * and/or will expand the dialog to full size if "More / Fewer" feature is implemented + * + * Returns the "value" as a string, or "" if error or input contents are empty + * The global "gValidationError" variable is set true if error was found + */ +function ValidateNumber( + inputWidget, + listWidget, + minVal, + maxVal, + element, + attName, + mustHaveValue, + mustShowMoreSection +) { + if (!inputWidget) { + gValidationError = true; + return ""; + } + + // Global error return value + gValidationError = false; + var maxLimit = maxVal; + var isPercent = false; + + var numString = TrimString(inputWidget.value); + if (numString || mustHaveValue) { + if (listWidget) { + isPercent = listWidget.selectedIndex == 1; + } + if (isPercent) { + maxLimit = 100; + } + + // This method puts up the error message + numString = ValidateNumberRange(numString, minVal, maxLimit, mustHaveValue); + if (!numString) { + // Switch to appropriate panel for error reporting + SwitchToValidatePanel(); + + // or expand dialog for users of "More / Fewer" button + if ( + "dialog" in window && + window.dialog && + "MoreSection" in gDialog && + gDialog.MoreSection + ) { + if (!SeeMore) { + onMoreFewer(); + } + } + + // Error - shift to offending input widget + SetTextboxFocus(inputWidget); + gValidationError = true; + } else { + if (isPercent) { + numString += "%"; + } + if (element) { + GetCurrentEditor().setAttributeOrEquivalent( + element, + attName, + numString, + true + ); + } + } + } else if (element) { + GetCurrentEditor().removeAttributeOrEquivalent(element, attName, true); + } + return numString; +} + +/* Validate contents of an input field + * + * value number to validate + * minVal minimum allowed for input widget's value + * maxVal maximum allowed for input widget's value + * (when "listWidget" is used, maxVal is used for "pixel" maximum, + * 100% is assumed if "percent" is the user's choice) + * mustHaveValue If true, error dialog is displayed if "value" is empty string + * + * If inputWidget's value is outside of range, or is empty when "mustHaveValue" = true, + * an error dialog is popuped up to inform the user. The focus is shifted + * to the inputWidget. + * + * Returns the "value" as a string, or "" if error or input contents are empty + * The global "gValidationError" variable is set true if error was found + */ +function ValidateNumberRange(value, minValue, maxValue, mustHaveValue) { + // Initialize global error flag + gValidationError = false; + value = TrimString(String(value)); + + // We don't show error for empty string unless caller wants to + if (!value && !mustHaveValue) { + return ""; + } + + var numberStr = ""; + + if (value.length > 0) { + // Extract just numeric characters + var number = Number(value.replace(/\D+/g, "")); + if (number >= minValue && number <= maxValue) { + // Return string version of the number + return String(number); + } + numberStr = String(number); + } + + var message = ""; + + if (numberStr.length > 0) { + // We have a number from user outside of allowed range + message = GetString("ValidateRangeMsg"); + message = message.replace(/%n%/, numberStr); + message += "\n "; + } + message += GetString("ValidateNumberMsg"); + + // Replace variable placeholders in message with number values + message = message.replace(/%min%/, minValue).replace(/%max%/, maxValue); + ShowInputErrorMessage(message); + + // Return an empty string to indicate error + gValidationError = true; + return ""; +} + +function SetTextboxFocusById(id) { + SetTextboxFocus(document.getElementById(id)); +} + +function SetTextboxFocus(textbox) { + if (textbox) { + // XXX Using the setTimeout is hacky workaround for bug 103197 + // Must create a new function to keep "textbox" in scope + setTimeout( + function(textbox) { + textbox.focus(); + textbox.select(); + }, + 0, + textbox + ); + } +} + +function ShowInputErrorMessage(message) { + Services.prompt.alert(window, GetString("InputError"), message); + window.focus(); +} + +// Get the text appropriate to parent container +// to determine what a "%" value is referring to. +// elementForAtt is element we are actually setting attributes on +// (a temporary copy of element in the doc to allow canceling), +// but elementInDoc is needed to find parent context in document +function GetAppropriatePercentString(elementForAtt, elementInDoc) { + var editor = GetCurrentEditor(); + try { + var name = elementForAtt.nodeName.toLowerCase(); + if (name == "td" || name == "th") { + return GetString("PercentOfTable"); + } + + // Check if element is within a table cell + if (editor.getElementOrParentByTagName("td", elementInDoc)) { + return GetString("PercentOfCell"); + } + return GetString("PercentOfWindow"); + } catch (e) { + return ""; + } +} + +function ClearListbox(listbox) { + if (listbox) { + listbox.clearSelection(); + while (listbox.hasChildNodes()) { + listbox.lastChild.remove(); + } + } +} + +function forceInteger(elementID) { + var editField = document.getElementById(elementID); + if (!editField) { + return; + } + + var stringIn = editField.value; + if (stringIn && stringIn.length > 0) { + // Strip out all nonnumeric characters + stringIn = stringIn.replace(/\D+/g, ""); + if (!stringIn) { + stringIn = ""; + } + + // Write back only if changed + if (stringIn != editField.value) { + editField.value = stringIn; + } + } +} + +function InitPixelOrPercentMenulist( + elementForAtt, + elementInDoc, + attribute, + menulistID, + defaultIndex +) { + if (!defaultIndex) { + defaultIndex = gPixel; + } + + // var size = elementForAtt.getAttribute(attribute); + var size = GetHTMLOrCSSStyleValue(elementForAtt, attribute, attribute); + var menulist = document.getElementById(menulistID); + var pixelItem; + var percentItem; + + if (!menulist) { + dump("NO MENULIST found for ID=" + menulistID + "\n"); + return size; + } + + menulist.removeAllItems(); + pixelItem = menulist.appendItem(GetString("Pixels")); + + if (!pixelItem) { + return 0; + } + + percentItem = menulist.appendItem( + GetAppropriatePercentString(elementForAtt, elementInDoc) + ); + if (size && size.length > 0) { + // Search for a "%" or "px" + if (size.includes("%")) { + // Strip out the % + size = size.substr(0, size.indexOf("%")); + if (percentItem) { + menulist.selectedItem = percentItem; + } + } else { + if (size.includes("px")) { + // Strip out the px + size = size.substr(0, size.indexOf("px")); + } + menulist.selectedItem = pixelItem; + } + } else { + menulist.selectedIndex = defaultIndex; + } + + return size; +} + +function onAdvancedEdit() { + // First validate data from widgets in the "simpler" property dialog + if (ValidateData()) { + // Set true if OK is clicked in the Advanced Edit dialog + window.AdvancedEditOK = false; + // Open the AdvancedEdit dialog, passing in the element to be edited + // (the copy named "globalElement") + window.openDialog( + "chrome://editor/content/EdAdvancedEdit.xhtml", + "_blank", + "chrome,close,titlebar,modal,resizable=yes", + "", + globalElement + ); + window.focus(); + if (window.AdvancedEditOK) { + // Copy edited attributes to the dialog widgets: + InitDialog(); + } + } +} + +function getColor(ColorPickerID) { + var colorPicker = document.getElementById(ColorPickerID); + var color; + if (colorPicker) { + // Extract color from colorPicker and assign to colorWell. + color = colorPicker.getAttribute("color"); + if (color && color == "") { + return null; + } + // Clear color so next if it's called again before + // color picker is actually used, we dedect the "don't set color" state + colorPicker.setAttribute("color", ""); + } + + return color; +} + +function setColorWell(ColorWellID, color) { + var colorWell = document.getElementById(ColorWellID); + if (colorWell) { + if (!color || color == "") { + // Don't set color (use default) + // Trigger change to not show color swatch + colorWell.setAttribute("default", "true"); + // Style in CSS sets "background-color", + // but color won't clear unless we do this: + colorWell.removeAttribute("style"); + } else { + colorWell.removeAttribute("default"); + // Use setAttribute so colorwell can be a XUL element, such as button + colorWell.setAttribute("style", "background-color:" + color); + } + } +} + +function getColorAndSetColorWell(ColorPickerID, ColorWellID) { + var color = getColor(ColorPickerID); + setColorWell(ColorWellID, color); + return color; +} + +function InitMoreFewer() { + // Set SeeMore bool to the OPPOSITE of the current state, + // which is automatically saved by using the 'persist="more"' + // attribute on the gDialog.MoreFewerButton button + // onMoreFewer will toggle it and redraw the dialog + SeeMore = gDialog.MoreFewerButton.getAttribute("more") != "1"; + onMoreFewer(); + gDialog.MoreFewerButton.setAttribute( + "accesskey", + GetString("PropertiesAccessKey") + ); +} + +function onMoreFewer() { + if (SeeMore) { + gDialog.MoreSection.collapsed = true; + gDialog.MoreFewerButton.setAttribute("more", "0"); + gDialog.MoreFewerButton.setAttribute("label", GetString("MoreProperties")); + SeeMore = false; + } else { + gDialog.MoreSection.collapsed = false; + gDialog.MoreFewerButton.setAttribute("more", "1"); + gDialog.MoreFewerButton.setAttribute("label", GetString("FewerProperties")); + SeeMore = true; + } + window.sizeToContent(); +} + +function SwitchToValidatePanel() { + // no default implementation + // Only EdTableProps.js currently implements this +} + +const nsIFilePicker = Ci.nsIFilePicker; + +/** + * @return {Promise} URL spec of the file chosen, or null + */ +function GetLocalFileURL(filterType) { + var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); + var fileType = "html"; + + if (filterType == "img") { + fp.init(window, GetString("SelectImageFile"), nsIFilePicker.modeOpen); + fp.appendFilters(nsIFilePicker.filterImages); + fileType = "image"; + } else if (filterType.startsWith("html")) { + // Current usage of this is in Link dialog, + // where we always want HTML first + fp.init(window, GetString("OpenHTMLFile"), nsIFilePicker.modeOpen); + + // When loading into Composer, direct user to prefer HTML files and text files, + // so we call separately to control the order of the filter list + fp.appendFilters(nsIFilePicker.filterHTML); + fp.appendFilters(nsIFilePicker.filterText); + + // Link dialog also allows linking to images + if (filterType.includes("img", 1)) { + fp.appendFilters(nsIFilePicker.filterImages); + } + } + // Default or last filter is "All Files" + fp.appendFilters(nsIFilePicker.filterAll); + + // set the file picker's current directory to last-opened location saved in prefs + SetFilePickerDirectory(fp, fileType); + + return new Promise(resolve => { + fp.open(rv => { + if (rv != nsIFilePicker.returnOK || !fp.file) { + resolve(null); + return; + } + SaveFilePickerDirectory(fp, fileType); + resolve(fp.fileURL.spec); + }); + }); +} + +function GetMetaElementByAttribute(name, value) { + if (name) { + name = name.toLowerCase(); + let editor = GetCurrentEditor(); + try { + return editor.document.querySelector( + "meta[" + name + '="' + value + '"]' + ); + } catch (e) {} + } + return null; +} + +function CreateMetaElementWithAttribute(name, value) { + let editor = GetCurrentEditor(); + try { + let metaElement = editor.createElementWithDefaults("meta"); + if (name) { + metaElement.setAttribute(name, value); + } + return metaElement; + } catch (e) {} + return null; +} + +// Change "content" attribute on a META element, +// or delete entire element it if content is empty +// This uses undoable editor transactions +function SetMetaElementContent(metaElement, content, insertNew, prepend) { + if (metaElement) { + var editor = GetCurrentEditor(); + try { + if (!content || content == "") { + if (!insertNew) { + editor.deleteNode(metaElement); + } + } else if (insertNew) { + metaElement.setAttribute("content", content); + if (prepend) { + PrependHeadElement(metaElement); + } else { + AppendHeadElement(metaElement); + } + } else { + editor.setAttribute(metaElement, "content", content); + } + } catch (e) {} + } +} + +function GetHeadElement() { + var editor = GetCurrentEditor(); + try { + return editor.document.querySelector("head"); + } catch (e) {} + + return null; +} + +function PrependHeadElement(element) { + var head = GetHeadElement(); + if (head) { + var editor = GetCurrentEditor(); + try { + // Use editor's undoable transaction + // XXX Here tried to prevent updating Selection with unknown 4th argument, + // but nsIEditor.setShouldTxnSetSelection is not used for that. + editor.insertNode(element, head, 0); + } catch (e) {} + } +} + +function AppendHeadElement(element) { + var head = GetHeadElement(); + if (head) { + var position = 0; + if (head.hasChildNodes()) { + position = head.childNodes.length; + } + + var editor = GetCurrentEditor(); + try { + // Use editor's undoable transaction + // XXX Here tried to prevent updating Selection with unknown 4th argument, + // but nsIEditor.setShouldTxnSetSelection is not used for that. + editor.insertNode(element, head, position); + } catch (e) {} + } +} + +function SetWindowLocation() { + gLocation = document.getElementById("location"); + if (gLocation) { + window.screenX = Math.max( + 0, + Math.min( + window.opener.screenX + Number(gLocation.getAttribute("offsetX")), + screen.availWidth - window.outerWidth + ) + ); + window.screenY = Math.max( + 0, + Math.min( + window.opener.screenY + Number(gLocation.getAttribute("offsetY")), + screen.availHeight - window.outerHeight + ) + ); + } +} + +function SaveWindowLocation() { + if (gLocation) { + gLocation.setAttribute("offsetX", window.screenX - window.opener.screenX); + gLocation.setAttribute("offsetY", window.screenY - window.opener.screenY); + } +} + +function onCancel() { + SaveWindowLocation(); +} + +function SetRelativeCheckbox(checkbox) { + if (!checkbox) { + checkbox = document.getElementById("MakeRelativeCheckbox"); + if (!checkbox) { + return; + } + } + + var editor = GetCurrentEditor(); + // Mail never allows relative URLs, so hide the checkbox + if (editor && editor.flags & Ci.nsIEditor.eEditorMailMask) { + checkbox.collapsed = true; + return; + } + + var input = document.getElementById(checkbox.getAttribute("for")); + if (!input) { + return; + } + + var url = TrimString(input.value); + var urlScheme = GetScheme(url); + + // Check it if url is relative (no scheme). + checkbox.checked = url.length > 0 && !urlScheme; + + // Now do checkbox enabling: + var enable = false; + + var docUrl = GetDocumentBaseUrl(); + var docScheme = GetScheme(docUrl); + + if (url && docUrl && docScheme) { + if (urlScheme) { + // Url is absolute + // If we can make a relative URL, then enable must be true! + // (this lets the smarts of MakeRelativeUrl do all the hard work) + enable = GetScheme(MakeRelativeUrl(url)).length == 0; + } else if (url[0] == "#") { + // Url is relative + // Check if url is a named anchor + // but document doesn't have a filename + // (it's probably "index.html" or "index.htm", + // but we don't want to allow a malformed URL) + var docFilename = GetFilename(docUrl); + enable = docFilename.length > 0; + } else { + // Any other url is assumed + // to be ok to try to make absolute + enable = true; + } + } + + SetElementEnabled(checkbox, enable); +} + +// oncommand handler for the Relativize checkbox in EditorOverlay.xhtml +function MakeInputValueRelativeOrAbsolute(checkbox) { + var input = document.getElementById(checkbox.getAttribute("for")); + if (!input) { + return; + } + + var docUrl = GetDocumentBaseUrl(); + if (!docUrl) { + // Checkbox should be disabled if not saved, + // but keep this error message in case we change that + Services.prompt.alert(window, "", GetString("SaveToUseRelativeUrl")); + window.focus(); + } else { + // Note that "checked" is opposite of its last state, + // which determines what we want to do here + if (checkbox.checked) { + input.value = MakeRelativeUrl(input.value); + } else { + input.value = MakeAbsoluteUrl(input.value); + } + + // Reset checkbox to reflect url state + SetRelativeCheckbox(checkbox); + } +} + +var IsBlockParent = [ + "applet", + "blockquote", + "body", + "center", + "dd", + "div", + "form", + "li", + "noscript", + "object", + "td", + "th", +]; + +var NotAnInlineParent = [ + "col", + "colgroup", + "dl", + "dir", + "menu", + "ol", + "table", + "tbody", + "tfoot", + "thead", + "tr", + "ul", +]; + +function nodeIsBreak(editor, node) { + return !node || node.localName == "br" || editor.nodeIsBlock(node); +} + +function InsertElementAroundSelection(element) { + var editor = GetCurrentEditor(); + editor.beginTransaction(); + + try { + // First get the selection as a single range + var range, start, end, offset; + var count = editor.selection.rangeCount; + if (count == 1) { + range = editor.selection.getRangeAt(0).cloneRange(); + } else { + range = editor.document.createRange(); + start = editor.selection.getRangeAt(0); + range.setStart(start.startContainer, start.startOffset); + end = editor.selection.getRangeAt(--count); + range.setEnd(end.endContainer, end.endOffset); + } + + // Flatten the selection to child nodes of the common ancestor + while (range.startContainer != range.commonAncestorContainer) { + range.setStartBefore(range.startContainer); + } + while (range.endContainer != range.commonAncestorContainer) { + range.setEndAfter(range.endContainer); + } + + if (editor.nodeIsBlock(element)) { + // Block element parent must be a valid block + while (!IsBlockParent.includes(range.commonAncestorContainer.localName)) { + range.selectNode(range.commonAncestorContainer); + } + } else { + if (!nodeIsBreak(editor, range.commonAncestorContainer)) { + // Fail if we're not inserting a block (use setInlineProperty instead) + return false; + } + if (NotAnInlineParent.includes(range.commonAncestorContainer.localName)) { + // Inline element parent must not be an invalid block + do { + range.selectNode(range.commonAncestorContainer); + } while ( + NotAnInlineParent.includes(range.commonAncestorContainer.localName) + ); + } else { + // Further insert block check + for (var i = range.startOffset; ; i++) { + if (i == range.endOffset) { + return false; + } + if ( + nodeIsBreak(editor, range.commonAncestorContainer.childNodes[i]) + ) { + break; + } + } + } + } + + // The range may be contained by body text, which should all be selected. + offset = range.startOffset; + start = range.startContainer.childNodes[offset]; + if (!nodeIsBreak(editor, start)) { + while (!nodeIsBreak(editor, start.previousSibling)) { + start = start.previousSibling; + offset--; + } + } + end = range.endContainer.childNodes[range.endOffset]; + if (end && !nodeIsBreak(editor, end.previousSibling)) { + while (!nodeIsBreak(editor, end)) { + end = end.nextSibling; + } + } + + // Now insert the node + // XXX Here tried to prevent updating Selection with unknown 4th argument, + // but nsIEditor.setShouldTxnSetSelection is not used for that. + editor.insertNode(element, range.commonAncestorContainer, offset); + offset = element.childNodes.length; + if (!editor.nodeIsBlock(element)) { + editor.setShouldTxnSetSelection(false); + } + + // Move all the old child nodes to the element + var empty = true; + while (start != end) { + var next = start.nextSibling; + editor.deleteNode(start); + editor.insertNode(start, element, element.childNodes.length); + empty = false; + start = next; + } + if (!editor.nodeIsBlock(element)) { + editor.setShouldTxnSetSelection(true); + } else { + // Also move a trailing <br> + if (start && start.localName == "br") { + editor.deleteNode(start); + editor.insertNode(start, element, element.childNodes.length); + empty = false; + } + // Still nothing? Insert a <br> so the node is not empty + if (empty) { + editor.insertNode( + editor.createElementWithDefaults("br"), + element, + element.childNodes.length + ); + } + + // Hack to set the selection just inside the element + editor.insertNode(editor.document.createTextNode(""), element, offset); + } + } finally { + editor.endTransaction(); + } + + return true; +} + +function nodeIsBlank(node) { + return node && node.nodeType == Node.TEXT_NODE && !/\S/.test(node.data); +} + +function nodeBeginsBlock(editor, node) { + while (nodeIsBlank(node)) { + node = node.nextSibling; + } + return nodeIsBreak(editor, node); +} + +function nodeEndsBlock(editor, node) { + while (nodeIsBlank(node)) { + node = node.previousSibling; + } + return nodeIsBreak(editor, node); +} + +// C++ function isn't exposed to JS :-( +function RemoveBlockContainer(element) { + var editor = GetCurrentEditor(); + editor.beginTransaction(); + + try { + var range = editor.document.createRange(); + range.selectNode(element); + var offset = range.startOffset; + var parent = element.parentNode; + + // May need to insert a break after the removed element + if ( + !nodeBeginsBlock(editor, element.nextSibling) && + !nodeEndsBlock(editor, element.lastChild) + ) { + editor.insertNode( + editor.createElementWithDefaults("br"), + parent, + range.endOffset + ); + } + + // May need to insert a break before the removed element, or if it was empty + if ( + !nodeEndsBlock(editor, element.previousSibling) && + !nodeBeginsBlock(editor, element.firstChild || element.nextSibling) + ) { + editor.insertNode( + editor.createElementWithDefaults("br"), + parent, + offset++ + ); + } + + // Now remove the element + editor.deleteNode(element); + + // Need to copy the contained nodes? + for (var i = 0; i < element.childNodes.length; i++) { + editor.insertNode( + element.childNodes[i].cloneNode(true), + parent, + offset++ + ); + } + } finally { + editor.endTransaction(); + } +} + +// C++ function isn't exposed to JS :-( +function RemoveContainer(element) { + var editor = GetCurrentEditor(); + editor.beginTransaction(); + + try { + var range = editor.document.createRange(); + var parent = element.parentNode; + // Allow for automatic joining of text nodes + // so we can't delete the container yet + // so we need to copy the contained nodes + for (var i = 0; i < element.childNodes.length; i++) { + range.selectNode(element); + editor.insertNode( + element.childNodes[i].cloneNode(true), + parent, + range.startOffset + ); + } + // Now remove the element + editor.deleteNode(element); + } finally { + editor.endTransaction(); + } +} + +function FillLinkMenulist(linkMenulist, headingsArray) { + var menupopup = linkMenulist.firstChild; + var editor = GetCurrentEditor(); + try { + var treeWalker = editor.document.createTreeWalker( + editor.document, + 1, + null, + true + ); + var headingList = []; + var anchorList = []; // for sorting + var anchorMap = {}; // for weeding out duplicates and making heading anchors unique + var anchor; + var i; + for ( + var element = treeWalker.nextNode(); + element; + element = treeWalker.nextNode() + ) { + // grab headings + // Skip headings that already have a named anchor as their first child + // (this may miss nearby anchors, but at least we don't insert another + // under the same heading) + if ( + element instanceof HTMLHeadingElement && + element.textContent && + !( + element.firstChild instanceof HTMLAnchorElement && + element.firstChild.name + ) + ) { + headingList.push(element); + } + + // grab named anchors + if (element instanceof HTMLAnchorElement && element.name) { + anchor = "#" + element.name; + if (!(anchor in anchorMap)) { + anchorList.push({ anchor, sortkey: anchor.toLowerCase() }); + anchorMap[anchor] = true; + } + } + + // grab IDs + if (element.id) { + anchor = "#" + element.id; + if (!(anchor in anchorMap)) { + anchorList.push({ anchor, sortkey: anchor.toLowerCase() }); + anchorMap[anchor] = true; + } + } + } + // add anchor for headings + for (i = 0; i < headingList.length; i++) { + var heading = headingList[i]; + + // Use just first 40 characters, don't add "...", + // and replace whitespace with "_" and strip non-word characters + anchor = + "#" + + ConvertToCDATAString( + TruncateStringAtWordEnd(heading.textContent, 40, false) + ); + + // Append "_" to any name already in the list + while (anchor in anchorMap) { + anchor += "_"; + } + anchorList.push({ anchor, sortkey: anchor.toLowerCase() }); + anchorMap[anchor] = true; + + // Save nodes in an array so we can create anchor node under it later + headingsArray[anchor] = heading; + } + if (anchorList.length) { + // case insensitive sort + anchorList.sort((a, b) => { + if (a.sortkey < b.sortkey) { + return -1; + } + if (a.sortkey > b.sortkey) { + return 1; + } + return 0; + }); + + for (i = 0; i < anchorList.length; i++) { + createMenuItem(menupopup, anchorList[i].anchor); + } + } else { + // Don't bother with named anchors in Mail. + if (editor && editor.flags & Ci.nsIEditor.eEditorMailMask) { + menupopup.remove(); + linkMenulist.removeAttribute("enablehistory"); + return; + } + var item = createMenuItem( + menupopup, + GetString("NoNamedAnchorsOrHeadings") + ); + item.setAttribute("disabled", "true"); + } + } catch (e) {} +} + +function createMenuItem(aMenuPopup, aLabel) { + var menuitem = document.createXULElement("menuitem"); + menuitem.setAttribute("label", aLabel); + aMenuPopup.appendChild(menuitem); + return menuitem; +} + +// Shared by Image and Link dialogs for the "Choose" button for links +function chooseLinkFile() { + GetLocalFileURL("html, img").then(fileURL => { + // Always try to relativize local file URLs + if (gHaveDocumentUrl) { + fileURL = MakeRelativeUrl(fileURL); + } + + gDialog.hrefInput.value = fileURL; + + // Do stuff specific to a particular dialog + // (This is defined separately in Image and Link dialogs) + ChangeLinkLocation(); + }); +} diff --git a/comm/suite/editor/components/dialogs/content/EdDialogTemplate.js b/comm/suite/editor/components/dialogs/content/EdDialogTemplate.js new file mode 100644 index 0000000000..ee74e8c871 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdDialogTemplate.js @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +// Cancel() is in EdDialogCommon.js +var insertNew = true; +var tagname = "TAG NAME"; + +// dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + if (!GetCurrentEditor()) { + window.close(); + return; + } + // gDialog is declared in EdDialogCommon.js + // Set commonly-used widgets like this: + gDialog.fooButton = document.getElementById("fooButton"); + + InitDialog(); + + // Set window location relative to parent window (based on persisted attributes) + SetWindowLocation(); + + // Set focus to first widget in dialog, e.g.: + SetTextboxFocus(gDialog.fooButton); +} + +function InitDialog() { + // Initialize all dialog widgets here, + // e.g., get attributes from an element for property dialog +} + +function onAccept() { + // Validate all user data and set attributes and possibly insert new element here + // If there's an error the user must correct, return false to keep dialog open. + + SaveWindowLocation(); +} diff --git a/comm/suite/editor/components/dialogs/content/EdDialogTemplate.xhtml b/comm/suite/editor/components/dialogs/content/EdDialogTemplate.xhtml new file mode 100644 index 0000000000..8f76af4654 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdDialogTemplate.xhtml @@ -0,0 +1,23 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<?xul-overlay href="chrome://editor/content/EdDialogOverlay.xhtml"?> + +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/Ed?????????.dtd"> +<!-- dialog containing a control requiring initial setup --> +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup()"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/Ed?????.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdDictionary.js b/comm/suite/editor/components/dialogs/content/EdDictionary.js new file mode 100644 index 0000000000..892e92dd1a --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdDictionary.js @@ -0,0 +1,164 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +var gSpellChecker; +var gWordToAdd; + +function Startup() { + if (!GetCurrentEditor()) { + window.close(); + return; + } + // Get the SpellChecker shell + if ("gSpellChecker" in window.opener && window.opener.gSpellChecker) { + gSpellChecker = window.opener.gSpellChecker; + } + + if (!gSpellChecker) { + dump("SpellChecker not found!!!\n"); + window.close(); + return; + } + // The word to add word is passed as the 2nd extra parameter in window.openDialog() + gWordToAdd = window.arguments[1]; + + gDialog.WordInput = document.getElementById("WordInput"); + gDialog.DictionaryList = document.getElementById("DictionaryList"); + + gDialog.WordInput.value = gWordToAdd; + FillDictionaryList(); + + // Select the supplied word if it is already in the list + SelectWordToAddInList(); + SetTextboxFocus(gDialog.WordInput); +} + +function ValidateWordToAdd() { + gWordToAdd = TrimString(gDialog.WordInput.value); + if (gWordToAdd.length > 0) { + return true; + } + return false; +} + +function SelectWordToAddInList() { + for (var i = 0; i < gDialog.DictionaryList.getRowCount(); i++) { + var wordInList = gDialog.DictionaryList.getItemAtIndex(i); + if (wordInList && gWordToAdd == wordInList.label) { + gDialog.DictionaryList.selectedIndex = i; + break; + } + } +} + +function AddWord() { + if (ValidateWordToAdd()) { + try { + gSpellChecker.AddWordToDictionary(gWordToAdd); + } catch (e) { + dump( + "Exception occurred in gSpellChecker.AddWordToDictionary\nWord to add probably already existed\n" + ); + } + + // Rebuild the dialog list + FillDictionaryList(); + + SelectWordToAddInList(); + gDialog.WordInput.value = ""; + } +} + +function ReplaceWord() { + if (ValidateWordToAdd()) { + var selItem = gDialog.DictionaryList.selectedItem; + if (selItem) { + try { + gSpellChecker.RemoveWordFromDictionary(selItem.label); + } catch (e) {} + + try { + // Add to the dictionary list + gSpellChecker.AddWordToDictionary(gWordToAdd); + + // Just change the text on the selected item instead of rebuilding the list. + // The items are richlist items, so the label sits in the first child. + selItem.firstChild.setAttribute("value", gWordToAdd); + } catch (e) { + // Rebuild list and select the word - it was probably already in the list + dump("Exception occurred adding word in ReplaceWord\n"); + FillDictionaryList(); + SelectWordToAddInList(); + } + } + } +} + +function RemoveWord() { + var selIndex = gDialog.DictionaryList.selectedIndex; + if (selIndex >= 0) { + var word = gDialog.DictionaryList.selectedItem.label; + + // Remove word from list + gDialog.DictionaryList.selectedItem.remove(); + + // Remove from dictionary + try { + // Not working: BUG 43348 + gSpellChecker.RemoveWordFromDictionary(word); + } catch (e) { + dump("Failed to remove word from dictionary\n"); + } + + ResetSelectedItem(selIndex); + } +} + +function FillDictionaryList() { + var selIndex = gDialog.DictionaryList.selectedIndex; + + // Clear the current contents of the list + ClearListbox(gDialog.DictionaryList); + + // Get the list from the spell checker + gSpellChecker.GetPersonalDictionary(); + + var haveList = false; + + // Get words until an empty string is returned + do { + var word = gSpellChecker.GetPersonalDictionaryWord(); + if (word != "") { + gDialog.DictionaryList.appendItem(word, ""); + haveList = true; + } + } while (word != ""); + + // XXX: BUG 74467: If list is empty, it doesn't layout to full height correctly + // (ignores "rows" attribute) (bug is latered, so we are fixing here for now) + if (!haveList) { + gDialog.DictionaryList.appendItem("", ""); + } + + ResetSelectedItem(selIndex); +} + +function ResetSelectedItem(index) { + var lastIndex = gDialog.DictionaryList.getRowCount() - 1; + if (index > lastIndex) { + index = lastIndex; + } + + // If we didn't have a selected item, + // set it to the first item + if (index == -1 && lastIndex >= 0) { + index = 0; + } + + gDialog.DictionaryList.selectedIndex = index; +} diff --git a/comm/suite/editor/components/dialogs/content/EdDictionary.xhtml b/comm/suite/editor/components/dialogs/content/EdDictionary.xhtml new file mode 100644 index 0000000000..cebbe1c192 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdDictionary.xhtml @@ -0,0 +1,59 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorPersonalDictionary.dtd"> +<dialog buttons="cancel" title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + persist="screenX screenY" + onload="Startup()"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdDictionary.js"/> + + <grid> + <columns><column style="width: 15em" flex="1"/><column flex="1"/></columns> + <rows> + <row> + <label value="&wordEditField.label;" + control="WordInput" + accesskey="&wordEditField.accessKey;"/> + <spacer/> + </row> + <row> + <textbox id="WordInput" flex="1"/> + <button id="AddWord" oncommand="AddWord()" label="&AddButton.label;" + accesskey="&AddButton.accessKey;"/> + </row> + <row> + <label value="&DictionaryList.label;" + control="DictionaryList" + accesskey="&DictionaryList.accessKey;"/> + <spacer/> + </row> + <row> + <richlistbox id="DictionaryList" + class="theme-listbox" + flex="1" + height="150px"/> + <vbox flex="1"> + <button id="ReplaceWord" oncommand="ReplaceWord()" label="&ReplaceButton.label;" + accesskey="&ReplaceButton.accessKey;"/> + <spacer class="spacer"/> + <button id="RemoveWord" oncommand="RemoveWord()" label="&RemoveButton.label;" + accesskey="&RemoveButton.accessKey;"/> + <spacer class="spacer"/> + <spacer flex="1"/> + <button dlgtype="cancel" class="exit-dialog" id="close" label="&CloseButton.label;" + default="true" oncommand="onClose();" + accesskey="&CloseButton.accessKey;"/> + </vbox> + </row> + </rows> + </grid> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdFieldSetProps.js b/comm/suite/editor/components/dialogs/content/EdFieldSetProps.js new file mode 100644 index 0000000000..1799a2b28f --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdFieldSetProps.js @@ -0,0 +1,196 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +var insertNew; +var fieldsetElement; +var newLegend; +var legendElement; + +// dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + var editor = GetCurrentEditor(); + if (!editor) { + dump("Failed to get active editor!\n"); + window.close(); + return; + } + + gDialog.editText = document.getElementById("EditText"); + gDialog.legendText = document.getElementById("LegendText"); + gDialog.legendAlign = document.getElementById("LegendAlign"); + gDialog.RemoveFieldSet = document.getElementById("RemoveFieldSet"); + + // Get a single selected field set element + const kTagName = "fieldset"; + try { + // Find a selected fieldset, or if one is at start or end of selection. + fieldsetElement = editor.getSelectedElement(kTagName); + if (!fieldsetElement) { + fieldsetElement = editor.getElementOrParentByTagName( + kTagName, + editor.selection.anchorNode + ); + } + if (!fieldsetElement) { + fieldsetElement = editor.getElementOrParentByTagName( + kTagName, + editor.selection.focusNode + ); + } + } catch (e) {} + + if (fieldsetElement) { + // We found an element and don't need to insert one + insertNew = false; + } else { + insertNew = true; + + // We don't have an element selected, + // so create one with default attributes + try { + fieldsetElement = editor.createElementWithDefaults(kTagName); + } catch (e) {} + + if (!fieldsetElement) { + dump("Failed to get selected element or create a new one!\n"); + window.close(); + return; + } + // Hide button removing existing fieldset + gDialog.RemoveFieldSet.hidden = true; + } + + legendElement = fieldsetElement.querySelector("legend"); + if (legendElement) { + newLegend = false; + var range = editor.document.createRange(); + range.selectNode(legendElement); + gDialog.legendText.value = range.toString(); + if (legendElement.innerHTML.includes("<")) { + gDialog.editText.checked = false; + gDialog.editText.disabled = false; + gDialog.legendText.disabled = true; + gDialog.editText.addEventListener( + "command", + () => + Services.prompt.alert( + window, + GetString("Alert"), + GetString("EditTextWarning") + ), + { capture: false, once: true } + ); + gDialog.RemoveFieldSet.focus(); + } else { + SetTextboxFocus(gDialog.legendText); + } + } else { + newLegend = true; + + // We don't have an element selected, + // so create one with default attributes + + legendElement = editor.createElementWithDefaults("legend"); + if (!legendElement) { + dump("Failed to get selected element or create a new one!\n"); + window.close(); + return; + } + SetTextboxFocus(gDialog.legendText); + } + + // Make a copy to use for AdvancedEdit + globalElement = legendElement.cloneNode(false); + + InitDialog(); + + SetWindowLocation(); +} + +function InitDialog() { + gDialog.legendAlign.value = GetHTMLOrCSSStyleValue( + globalElement, + "align", + "caption-side" + ); +} + +function RemoveFieldSet() { + var editor = GetCurrentEditor(); + editor.beginTransaction(); + try { + if (!newLegend) { + editor.deleteNode(legendElement); + } + RemoveBlockContainer(fieldsetElement); + } finally { + editor.endTransaction(); + } + SaveWindowLocation(); + window.close(); +} + +function ValidateData() { + if (gDialog.legendAlign.value) { + globalElement.setAttribute("align", gDialog.legendAlign.value); + } else { + globalElement.removeAttribute("align"); + } + return true; +} + +function onAccept() { + // All values are valid - copy to actual element in doc + ValidateData(); + + var editor = GetCurrentEditor(); + + editor.beginTransaction(); + + try { + editor.cloneAttributes(legendElement, globalElement); + + if (insertNew) { + if (gDialog.legendText.value) { + fieldsetElement.appendChild(legendElement); + legendElement.appendChild( + editor.document.createTextNode(gDialog.legendText.value) + ); + } + InsertElementAroundSelection(fieldsetElement); + } else if (gDialog.editText.checked) { + editor.setShouldTxnSetSelection(false); + + if (gDialog.legendText.value) { + if (newLegend) { + editor.insertNode(legendElement, fieldsetElement, 0); + } else { + while (legendElement.firstChild) { + editor.deleteNode(legendElement.lastChild); + } + } + editor.insertNode( + editor.document.createTextNode(gDialog.legendText.value), + legendElement, + 0 + ); + } else if (!newLegend) { + editor.deleteNode(legendElement); + } + + editor.setShouldTxnSetSelection(true); + } + } finally { + editor.endTransaction(); + } + + SaveWindowLocation(); +} diff --git a/comm/suite/editor/components/dialogs/content/EdFieldSetProps.xhtml b/comm/suite/editor/components/dialogs/content/EdFieldSetProps.xhtml new file mode 100644 index 0000000000..0d67fad276 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdFieldSetProps.xhtml @@ -0,0 +1,67 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % edFieldSetProperties SYSTEM "chrome://editor/locale/EditorFieldSetProperties.dtd"> +%edFieldSetProperties; +<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd"> +%edDialogOverlay; +]> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup();" + buttons="accept,cancel"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdFieldSetProps.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <groupbox> + <hbox class="groupbox-title"> + <label class="header" accesskey="&Legend.accesskey;">&Legend.label;</label> + </hbox> + <grid><columns><column/><column/></columns> + <rows> + <row align="center"> + <checkbox id="EditText" label="&EditLegendText.label;" accesskey="&EditLegendText.accesskey;" checked="true" disabled="true" + oncommand="gDialog.legendText.disabled = !gDialog.editText.checked;"/> + <textbox id="LegendText" accesskey="&Legend.accesskey;"/> + </row> + <row align="center"> + <label control="LegendAlign" value="&LegendAlign.label;" accesskey="&LegendAlign.accesskey;"/> + <menulist id="LegendAlign"> + <menupopup> + <menuitem label="&AlignDefault.label;"/> + <menuitem label="&AlignLeft.label;" value="left"/> + <menuitem label="&AlignCenter.label;" value="center"/> + <menuitem label="&AlignRight.label;" value="right"/> + </menupopup> + </menulist> + </row> + </rows> + </grid> + </groupbox> + + <!-- from EdDialogOverlay --> + <hbox flex="1" style="margin-top: 0.2em"> + <button id="RemoveFieldSet" label="&RemoveFieldSet.label;" accesskey="&RemoveFieldSet.accesskey;" oncommand="RemoveFieldSet();"/> + <!-- This will right-align the button --> + <spacer flex="1"/> + <button id="AdvancedEditButton" + oncommand="onAdvancedEdit();" + label="&AdvancedEditButton.label;" + accesskey="&AdvancedEditButton.accessKey;" + tooltiptext="&AdvancedEditButton.tooltip;"/> + </hbox> + <separator class="groove"/> + +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdFormProps.js b/comm/suite/editor/components/dialogs/content/EdFormProps.js new file mode 100644 index 0000000000..9391fa4316 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdFormProps.js @@ -0,0 +1,136 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +var gForm; +var insertNew; +var formElement; +var formActionWarning; + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + var editor = GetCurrentEditor(); + if (!editor) { + dump("Failed to get active editor!\n"); + window.close(); + return; + } + + gForm = { + Name: document.getElementById("FormName"), + Action: document.getElementById("FormAction"), + Method: document.getElementById("FormMethod"), + EncType: document.getElementById("FormEncType"), + Target: document.getElementById("FormTarget"), + }; + gDialog.MoreSection = document.getElementById("MoreSection"); + gDialog.MoreFewerButton = document.getElementById("MoreFewerButton"); + gDialog.RemoveForm = document.getElementById("RemoveForm"); + + // Get a single selected form element + const kTagName = "form"; + try { + formElement = editor.getSelectedElement(kTagName); + if (!formElement) { + formElement = editor.getElementOrParentByTagName( + kTagName, + editor.selection.anchorNode + ); + } + if (!formElement) { + formElement = editor.getElementOrParentByTagName( + kTagName, + editor.selection.focusNode + ); + } + } catch (e) {} + + if (formElement) { + // We found an element and don't need to insert one + insertNew = false; + formActionWarning = formElement.hasAttribute("action"); + } else { + insertNew = true; + formActionWarning = true; + + // We don't have an element selected, + // so create one with default attributes + try { + formElement = editor.createElementWithDefaults(kTagName); + } catch (e) {} + + if (!formElement) { + dump("Failed to get selected element or create a new one!\n"); + window.close(); + return; + } + // Hide button removing existing form + gDialog.RemoveForm.hidden = true; + } + + // Make a copy to use for AdvancedEdit + globalElement = formElement.cloneNode(false); + + InitDialog(); + + InitMoreFewer(); + + SetTextboxFocus(gForm.Name); + + SetWindowLocation(); +} + +function InitDialog() { + for (var attribute in gForm) { + gForm[attribute].value = globalElement.getAttribute(attribute); + } +} + +function RemoveForm() { + RemoveBlockContainer(formElement); + SaveWindowLocation(); + window.close(); +} + +function ValidateData() { + for (var attribute in gForm) { + if (gForm[attribute].value) { + globalElement.setAttribute(attribute, gForm[attribute].value); + } else { + globalElement.removeAttribute(attribute); + } + } + return true; +} + +function onAccept(event) { + if (formActionWarning && !gForm.Action.value) { + Services.prompt.alert( + window, + GetString("Alert"), + GetString("NoFormAction") + ); + gForm.Action.focus(); + formActionWarning = false; + event.preventDefault(); + return; + } + // All values are valid - copy to actual element in doc or + // element created to insert + ValidateData(); + + var editor = GetCurrentEditor(); + + editor.cloneAttributes(formElement, globalElement); + + if (insertNew) { + InsertElementAroundSelection(formElement); + } + + SaveWindowLocation(); +} diff --git a/comm/suite/editor/components/dialogs/content/EdFormProps.xhtml b/comm/suite/editor/components/dialogs/content/EdFormProps.xhtml new file mode 100644 index 0000000000..275daef785 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdFormProps.xhtml @@ -0,0 +1,98 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/menulist.css" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % edFormProperties SYSTEM "chrome://editor/locale/EditorFormProperties.dtd"> +%edFormProperties; +<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd"> +%edDialogOverlay; +]> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup();" + buttons="accept,cancel"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdFormProps.js"/> + + <script src="chrome://messenger/content/customElements.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <groupbox> + <hbox class="groupbox-title"> + <label class="header">&Settings.label;</label> + </hbox> + <grid><columns><column/><column/></columns> + <rows> + <row align="center"> + <label control="FormName" value="&FormName.label;" accesskey="&FormName.accesskey;"/> + <textbox id="FormName"/> + </row> + <row align="center"> + <label control="FormAction" value="&FormAction.label;" accesskey="&FormAction.accesskey;"/> + <textbox id="FormAction"/> + </row> + <row align="center"> + <label control="FormMethod" value="&FormMethod.label;" accesskey="&FormMethod.accesskey;"/> + <hbox> + <menulist is="menulist-editable" id="FormMethod" editable="true" autoSelectMenuitem="true"> + <menupopup> + <menuitem label="GET"/> + <menuitem label="POST"/> + </menupopup> + </menulist> + </hbox> + </row> + <hbox> + <button id="MoreFewerButton" oncommand="onMoreFewer();" persist="more"/> + </hbox> + <rows id="MoreSection"> + <row align="center"> + <label control="FormEncType" value="&FormEncType.label;" accesskey="&FormEncType.accesskey;"/> + <menulist is="menulist-editable" id="FormEncType" editable="true" autoSelectMenuitem="true"> + <menupopup> + <menuitem label="application/x-www-form-urlencoded"/> + <menuitem label="multipart/form-data"/> + <menuitem label="text/plain"/> + </menupopup> + </menulist> + </row> + <row align="center"> + <label control="FormTarget" value="&FormTarget.label;" accesskey="&FormTarget.accesskey;"/> + <menulist is="menulist-editable" id="FormTarget" editable="true" autoSelectMenuitem="true"> + <menupopup> + <menuitem label="_blank"/> + <menuitem label="_self"/> + <menuitem label="_parent"/> + <menuitem label="_top"/> + </menupopup> + </menulist> + </row> + </rows> + </rows> + </grid> + </groupbox> + + <!-- from EdDialogOverlay --> + <hbox flex="1" style="margin-top: 0.2em"> + <button id="RemoveForm" label="&RemoveForm.label;" accesskey="&RemoveForm.accesskey;" oncommand="RemoveForm();"/> + <!-- This will right-align the button --> + <spacer flex="1"/> + <button id="AdvancedEditButton" + oncommand="onAdvancedEdit();" + label="&AdvancedEditButton.label;" + accesskey="&AdvancedEditButton.accessKey;" + tooltiptext="&AdvancedEditButton.tooltip;"/> + </hbox> + <separator class="groove"/> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdHLineProps.js b/comm/suite/editor/components/dialogs/content/EdHLineProps.js new file mode 100644 index 0000000000..eff9c06a41 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdHLineProps.js @@ -0,0 +1,227 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +var tagName = "hr"; +var gHLineElement; +var width; +var height; +var align; +var shading; +const gMaxHRSize = 1000; // This is hard-coded in nsHTMLHRElement::StringToAttribute() + +// dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + var editor = GetCurrentEditor(); + if (!editor) { + window.close(); + return; + } + try { + // Get the selected horizontal line + gHLineElement = editor.getSelectedElement(tagName); + } catch (e) {} + + if (!gHLineElement) { + // We should never be here if not editing an existing HLine + window.close(); + return; + } + gDialog.heightInput = document.getElementById("height"); + gDialog.widthInput = document.getElementById("width"); + gDialog.leftAlign = document.getElementById("leftAlign"); + gDialog.centerAlign = document.getElementById("centerAlign"); + gDialog.rightAlign = document.getElementById("rightAlign"); + gDialog.alignGroup = gDialog.rightAlign.radioGroup; + gDialog.shading = document.getElementById("3dShading"); + gDialog.pixelOrPercentMenulist = document.getElementById( + "pixelOrPercentMenulist" + ); + + // Make a copy to use for AdvancedEdit and onSaveDefault + globalElement = gHLineElement.cloneNode(false); + + // Initialize control values based on existing attributes + InitDialog(); + + // SET FOCUS TO FIRST CONTROL + SetTextboxFocus(gDialog.widthInput); + + // Resize window + window.sizeToContent(); + + SetWindowLocation(); +} + +// Set dialog widgets with attribute data +// We get them from globalElement copy so this can be used +// by AdvancedEdit(), which is shared by all property dialogs +function InitDialog() { + // Just to be confusing, "size" is used instead of height because it does + // not accept % values, only pixels + var height = GetHTMLOrCSSStyleValue(globalElement, "size", "height"); + if (height.includes("px")) { + height = height.substr(0, height.indexOf("px")); + } + if (!height) { + height = 2; // Default value + } + + // We will use "height" here and in UI + gDialog.heightInput.value = height; + + // Get the width attribute of the element, stripping out "%" + // This sets contents of menulist (adds pixel and percent menuitems elements) + gDialog.widthInput.value = InitPixelOrPercentMenulist( + globalElement, + gHLineElement, + "width", + "pixelOrPercentMenulist" + ); + + var marginLeft = GetHTMLOrCSSStyleValue( + globalElement, + "align", + "margin-left" + ).toLowerCase(); + var marginRight = GetHTMLOrCSSStyleValue( + globalElement, + "align", + "margin-right" + ).toLowerCase(); + align = marginLeft + " " + marginRight; + gDialog.leftAlign.checked = align == "left left" || align == "0px auto"; + gDialog.centerAlign.checked = + align == "center center" || align == "auto auto" || align == " "; + gDialog.rightAlign.checked = align == "right right" || align == "auto 0px"; + + if (gDialog.centerAlign.checked) { + gDialog.alignGroup.selectedItem = gDialog.centerAlign; + } else if (gDialog.rightAlign.checked) { + gDialog.alignGroup.selectedItem = gDialog.rightAlign; + } else { + gDialog.alignGroup.selectedItem = gDialog.leftAlign; + } + + gDialog.shading.checked = !globalElement.hasAttribute("noshade"); +} + +function onSaveDefault() { + // "false" means set attributes on the globalElement, + // not the real element being edited + if (ValidateData()) { + var alignInt; + if (align == "left") { + alignInt = 0; + } else if (align == "right") { + alignInt = 2; + } else { + alignInt = 1; + } + Services.prefs.setIntPref("editor.hrule.align", alignInt); + + var percent; + var widthInt; + var heightInt; + + if (width) { + if (width.includes("%")) { + percent = true; + widthInt = Number(width.substr(0, width.indexOf("%"))); + } else { + percent = false; + widthInt = Number(width); + } + } else { + percent = true; + widthInt = Number(100); + } + + heightInt = height ? Number(height) : 2; + + Services.prefs.setIntPref("editor.hrule.width", widthInt); + Services.prefs.setBoolPref("editor.hrule.width_percent", percent); + Services.prefs.setIntPref("editor.hrule.height", heightInt); + Services.prefs.setBoolPref("editor.hrule.shading", shading); + + // Write the prefs out NOW! + Services.prefs.savePrefFile(null); + } +} + +// Get and validate data from widgets. +// Set attributes on globalElement so they can be accessed by AdvancedEdit() +function ValidateData() { + // Height is always pixels + height = ValidateNumber( + gDialog.heightInput, + null, + 1, + gMaxHRSize, + globalElement, + "size", + false + ); + if (gValidationError) { + return false; + } + + width = ValidateNumber( + gDialog.widthInput, + gDialog.pixelOrPercentMenulist, + 1, + gMaxPixels, + globalElement, + "width", + false + ); + if (gValidationError) { + return false; + } + + align = "left"; + if (gDialog.centerAlign.selected) { + // Don't write out default attribute + align = ""; + } else if (gDialog.rightAlign.selected) { + align = "right"; + } + if (align) { + globalElement.setAttribute("align", align); + } else { + try { + GetCurrentEditor().removeAttributeOrEquivalent( + globalElement, + "align", + true + ); + } catch (e) {} + } + + if (gDialog.shading.checked) { + shading = true; + globalElement.removeAttribute("noshade"); + } else { + shading = false; + globalElement.setAttribute("noshade", "noshade"); + } + return true; +} + +function onAccept(event) { + if (ValidateData()) { + // Copy attributes from the globalElement to the document element + try { + GetCurrentEditor().cloneAttributes(gHLineElement, globalElement); + } catch (e) {} + return; + } + event.preventDefault(); +} diff --git a/comm/suite/editor/components/dialogs/content/EdHLineProps.xhtml b/comm/suite/editor/components/dialogs/content/EdHLineProps.xhtml new file mode 100644 index 0000000000..fbcfa3b594 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdHLineProps.xhtml @@ -0,0 +1,80 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % edHLineProperties SYSTEM "chrome://editor/locale/EditorHLineProperties.dtd"> +%edHLineProperties; +<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd"> +%edDialogOverlay; +]> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup()"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <!--- Element-specific methods --> + <script src="chrome://editor/content/EdHLineProps.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <groupbox> + <hbox class="groupbox-title"> + <label class="header">&dimensionsBox.label;</label> + </hbox> + <grid> + <columns><column/><column/><column /></columns> + <rows> + <row align="center"> + <label control="width" + value="&widthEditField.label;" + accesskey="&widthEditField.accessKey;"/> + <textbox class="narrow" id="width" flex="1" oninput="forceInteger('width')"/> + <menulist id="pixelOrPercentMenulist" /> + <!-- menupopup and menuitems added by JS --> + </row> + <row align="center"> + <label control="height" + value="&heightEditField.label;" + accesskey="&heightEditField.accessKey;"/> + <textbox class="narrow" id="height" oninput="forceInteger('height')"/> + <label value="&pixelsPopup.value;" /> + </row> + </rows> + </grid> + <checkbox id="3dShading" label="&threeDShading.label;" accesskey="&threeDShading.accessKey;"/> + </groupbox> + <groupbox> + <hbox class="groupbox-title"> + <label class="header">&alignmentBox.label;</label> + </hbox> + <radiogroup id="alignmentGroup" orient="horizontal"> + <spacer class="spacer"/> + <radio id="leftAlign" label="&leftRadio.label;" accesskey="&leftRadio.accessKey;"/> + <radio id="centerAlign" label="¢erRadio.label;" accesskey="¢erRadio.accessKey;"/> + <radio id="rightAlign" label="&rightRadio.label;" accesskey="&rightRadio.accessKey;"/> + </radiogroup> + </groupbox> + <spacer class="spacer"/> + <hbox> + <button id="SaveDefault" label="&saveSettings.label;" + accesskey="&saveSettings.accessKey;" + oncommand="onSaveDefault()" + tooltiptext="&saveSettings.tooltip;" /> + <spacer flex="1"/> + <button id="AdvancedEditButton" + oncommand="onAdvancedEdit();" + label="&AdvancedEditButton.label;" + accesskey="&AdvancedEditButton.accessKey;" + tooltiptext="&AdvancedEditButton.tooltip;"/> + </hbox> + <separator class="groove"/> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdImageDialog.js b/comm/suite/editor/components/dialogs/content/EdImageDialog.js new file mode 100644 index 0000000000..0e697dbd6f --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdImageDialog.js @@ -0,0 +1,661 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + Note: We encourage non-empty alt text for images inserted into a page. + When there's no alt text, we always write 'alt=""' as the attribute, since "alt" is a required attribute. + We allow users to not have alt text by checking a "Don't use alterate text" radio button, + and we don't accept spaces as valid alt text. A space used to be required to avoid the error message + if user didn't enter alt text, but is unnecessary now that we no longer annoy the user + with the error dialog if alt="" is present on an img element. + We trim all spaces at the beginning and end of user's alt text +*/ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +var gInsertNewImage = true; +var gDoAltTextError = false; +var gConstrainOn = false; +// Note used in current version, but these are set correctly +// and could be used to reset width and height used for constrain ratio +var gConstrainWidth = 0; +var gConstrainHeight = 0; +var imageElement; +var gImageMap = 0; +var gCanRemoveImageMap = false; +var gRemoveImageMap = false; +var gImageMapDisabled = false; +var gActualWidth = ""; +var gActualHeight = ""; +var gOriginalSrc = ""; +var gTimerID; +var gValidateTab; +var gInsertNewIMap; + +// These must correspond to values in EditorDialog.css for each theme +// (unfortunately, setting "style" attribute here doesn't work!) +var gPreviewImageWidth = 80; +var gPreviewImageHeight = 50; + +// dialog initialization code + +function ImageStartup() { + gDialog.tabBox = document.getElementById("TabBox"); + gDialog.tabLocation = document.getElementById("imageLocationTab"); + gDialog.tabDimensions = document.getElementById("imageDimensionsTab"); + gDialog.tabBorder = document.getElementById("imageBorderTab"); + gDialog.srcInput = document.getElementById("srcInput"); + gDialog.titleInput = document.getElementById("titleInput"); + gDialog.altTextInput = document.getElementById("altTextInput"); + gDialog.altTextRadioGroup = document.getElementById("altTextRadioGroup"); + gDialog.altTextRadio = document.getElementById("altTextRadio"); + gDialog.noAltTextRadio = document.getElementById("noAltTextRadio"); + gDialog.actualSizeRadio = document.getElementById("actualSizeRadio"); + gDialog.constrainCheckbox = document.getElementById("constrainCheckbox"); + gDialog.widthInput = document.getElementById("widthInput"); + gDialog.heightInput = document.getElementById("heightInput"); + gDialog.widthUnitsMenulist = document.getElementById("widthUnitsMenulist"); + gDialog.heightUnitsMenulist = document.getElementById("heightUnitsMenulist"); + gDialog.imagelrInput = document.getElementById("imageleftrightInput"); + gDialog.imagetbInput = document.getElementById("imagetopbottomInput"); + gDialog.border = document.getElementById("border"); + gDialog.alignTypeSelect = document.getElementById("alignTypeSelect"); + gDialog.ImageHolder = document.getElementById("preview-image-holder"); + gDialog.PreviewWidth = document.getElementById("PreviewWidth"); + gDialog.PreviewHeight = document.getElementById("PreviewHeight"); + gDialog.PreviewSize = document.getElementById("PreviewSize"); + gDialog.PreviewImage = null; + gDialog.OkButton = document.documentElement.getButton("accept"); +} + +// Set dialog widgets with attribute data +// We get them from globalElement copy so this can be used +// by AdvancedEdit(), which is shared by all property dialogs +function InitImage() { + // Set the controls to the image's attributes + var src = globalElement.getAttribute("src"); + + // For image insertion the 'src' attribute is null. + if (src) { + // Shorten data URIs for display. + shortenImageData(src, gDialog.srcInput); + } + + // Set "Relativize" checkbox according to current URL state + SetRelativeCheckbox(); + + // Force loading of image from its source and show preview image + LoadPreviewImage(); + + gDialog.titleInput.value = globalElement.getAttribute("title"); + + var hasAltText = globalElement.hasAttribute("alt"); + var altText = globalElement.getAttribute("alt"); + gDialog.altTextInput.value = altText; + if (altText || (!hasAltText && globalElement.hasAttribute("src"))) { + gDialog.altTextRadioGroup.selectedItem = gDialog.altTextRadio; + } else if (hasAltText) { + gDialog.altTextRadioGroup.selectedItem = gDialog.noAltTextRadio; + } + SetAltTextDisabled( + gDialog.altTextRadioGroup.selectedItem == gDialog.noAltTextRadio + ); + + // setup the height and width widgets + var width = InitPixelOrPercentMenulist( + globalElement, + gInsertNewImage ? null : imageElement, + "width", + "widthUnitsMenulist", + gPixel + ); + var height = InitPixelOrPercentMenulist( + globalElement, + gInsertNewImage ? null : imageElement, + "height", + "heightUnitsMenulist", + gPixel + ); + + // Set actual radio button if both set values are the same as actual + SetSizeWidgets(width, height); + + gDialog.widthInput.value = gConstrainWidth = width || gActualWidth || ""; + gDialog.heightInput.value = gConstrainHeight = height || gActualHeight || ""; + + // set spacing editfields + gDialog.imagelrInput.value = globalElement.getAttribute("hspace"); + gDialog.imagetbInput.value = globalElement.getAttribute("vspace"); + + // dialog.border.value = globalElement.getAttribute("border"); + var bv = GetHTMLOrCSSStyleValue(globalElement, "border", "border-top-width"); + if (bv.includes("px")) { + // Strip out the px + bv = bv.substr(0, bv.indexOf("px")); + } else if (bv == "thin") { + bv = "1"; + } else if (bv == "medium") { + bv = "3"; + } else if (bv == "thick") { + bv = "5"; + } + gDialog.border.value = bv; + + // Get alignment setting + var align = globalElement.getAttribute("align"); + if (align) { + align = align.toLowerCase(); + } + + switch (align) { + case "top": + case "middle": + case "right": + case "left": + gDialog.alignTypeSelect.value = align; + break; + default: + // Default or "bottom" + gDialog.alignTypeSelect.value = "bottom"; + } + + // Get image map for image + gImageMap = GetImageMap(); + + doOverallEnabling(); + doDimensionEnabling(); +} + +function SetSizeWidgets(width, height) { + if ( + !(width || height) || + (gActualWidth && + gActualHeight && + width == gActualWidth && + height == gActualHeight) + ) { + gDialog.actualSizeRadio.radioGroup.selectedItem = gDialog.actualSizeRadio; + } + + if (!gDialog.actualSizeRadio.selected) { + // Decide if user's sizes are in the same ratio as actual sizes + if (gActualWidth && gActualHeight) { + if (gActualWidth > gActualHeight) { + gDialog.constrainCheckbox.checked = + Math.round((gActualHeight * width) / gActualWidth) == height; + } else { + gDialog.constrainCheckbox.checked = + Math.round((gActualWidth * height) / gActualHeight) == width; + } + } + } +} + +// Disable alt text input when "Don't use alt" radio is checked +function SetAltTextDisabled(disable) { + gDialog.altTextInput.disabled = disable; +} + +function GetImageMap() { + var usemap = globalElement.getAttribute("usemap"); + if (usemap) { + gCanRemoveImageMap = true; + let mapname = usemap.substr(1); + try { + return GetCurrentEditor().document.querySelector( + '[name="' + mapname + '"]' + ); + } catch (e) {} + } else { + gCanRemoveImageMap = false; + } + + return null; +} + +function chooseFile() { + if (gTimerID) { + clearTimeout(gTimerID); + } + + // Put focus into the input field + SetTextboxFocus(gDialog.srcInput); + + GetLocalFileURL("img").then(fileURL => { + // Always try to relativize local file URLs + if (gHaveDocumentUrl) { + fileURL = MakeRelativeUrl(fileURL); + } + + gDialog.srcInput.value = fileURL; + + SetRelativeCheckbox(); + doOverallEnabling(); + LoadPreviewImage(); + }); +} + +function PreviewImageLoaded() { + if (gDialog.PreviewImage) { + // Image loading has completed -- we can get actual width + gActualWidth = gDialog.PreviewImage.naturalWidth; + gActualHeight = gDialog.PreviewImage.naturalHeight; + + if (gActualWidth && gActualHeight) { + // Use actual size or scale to fit preview if either dimension is too large + var width = gActualWidth; + var height = gActualHeight; + if (gActualWidth > gPreviewImageWidth) { + width = gPreviewImageWidth; + height = gActualHeight * (gPreviewImageWidth / gActualWidth); + } + if (height > gPreviewImageHeight) { + height = gPreviewImageHeight; + width = gActualWidth * (gPreviewImageHeight / gActualHeight); + } + gDialog.PreviewImage.width = width; + gDialog.PreviewImage.height = height; + + gDialog.PreviewWidth.setAttribute("value", gActualWidth); + gDialog.PreviewHeight.setAttribute("value", gActualHeight); + + gDialog.PreviewSize.collapsed = false; + gDialog.ImageHolder.collapsed = false; + + SetSizeWidgets(gDialog.widthInput.value, gDialog.heightInput.value); + } + + if (gDialog.actualSizeRadio.selected) { + SetActualSize(); + } + } +} + +function LoadPreviewImage() { + gDialog.PreviewSize.collapsed = true; + // XXXbz workaround for bug 265416 / bug 266284 + gDialog.ImageHolder.collapsed = true; + + var imageSrc = TrimString(gDialog.srcInput.value); + if (!imageSrc) { + return; + } + if (isImageDataShortened(imageSrc)) { + imageSrc = restoredImageData(gDialog.srcInput); + } + + try { + // Remove the image URL from image cache so it loads fresh + // (if we don't do this, loads after the first will always use image cache + // and we won't see image edit changes or be able to get actual width and height) + + // We must have an absolute URL to preview it or remove it from the cache + imageSrc = MakeAbsoluteUrl(imageSrc); + + if (GetScheme(imageSrc)) { + let uri = Services.io.newURI(imageSrc); + if (uri) { + let imgCache = Cc["@mozilla.org/image/cache;1"].getService( + Ci.imgICache + ); + + // This returns error if image wasn't in the cache; ignore that + imgCache.removeEntry(uri); + } + } + } catch (e) {} + + if (gDialog.PreviewImage) { + removeEventListener("load", PreviewImageLoaded, true); + } + + if (gDialog.ImageHolder.hasChildNodes()) { + gDialog.ImageHolder.firstChild.remove(); + } + + gDialog.PreviewImage = document.createElementNS( + "http://www.w3.org/1999/xhtml", + "img" + ); + if (gDialog.PreviewImage) { + // set the src before appending to the document -- see bug 198435 for why + // this is needed. + // XXXbz that bug is long-since fixed. Is this still needed? + gDialog.PreviewImage.addEventListener("load", PreviewImageLoaded, true); + gDialog.PreviewImage.src = imageSrc; + gDialog.ImageHolder.appendChild(gDialog.PreviewImage); + } +} + +function SetActualSize() { + gDialog.widthInput.value = gActualWidth ? gActualWidth : ""; + gDialog.widthUnitsMenulist.selectedIndex = 0; + gDialog.heightInput.value = gActualHeight ? gActualHeight : ""; + gDialog.heightUnitsMenulist.selectedIndex = 0; + doDimensionEnabling(); +} + +function ChangeImageSrc() { + if (gTimerID) { + clearTimeout(gTimerID); + } + + gTimerID = setTimeout(LoadPreviewImage, 800); + + SetRelativeCheckbox(); + doOverallEnabling(); +} + +function doDimensionEnabling() { + // Enabled unless "Actual Size" is selected + var enable = !gDialog.actualSizeRadio.selected; + + // BUG 74145: After input field is disabled, + // setting it enabled causes blinking caret to appear + // even though focus isn't set to it. + SetElementEnabledById("heightInput", enable); + SetElementEnabledById("heightLabel", enable); + SetElementEnabledById("heightUnitsMenulist", enable); + + SetElementEnabledById("widthInput", enable); + SetElementEnabledById("widthLabel", enable); + SetElementEnabledById("widthUnitsMenulist", enable); + + var constrainEnable = + enable && + gDialog.widthUnitsMenulist.selectedIndex == 0 && + gDialog.heightUnitsMenulist.selectedIndex == 0; + + SetElementEnabledById("constrainCheckbox", constrainEnable); +} + +function doOverallEnabling() { + var enabled = TrimString(gDialog.srcInput.value) != ""; + + SetElementEnabled(gDialog.OkButton, enabled); + SetElementEnabledById("AdvancedEditButton1", enabled); + SetElementEnabledById("imagemapLabel", enabled); + SetElementEnabledById("removeImageMap", gCanRemoveImageMap); +} + +function ToggleConstrain() { + // If just turned on, save the current width and height as basis for constrain ratio + // Thus clicking on/off lets user say "Use these values as aspect ration" + if ( + gDialog.constrainCheckbox.checked && + !gDialog.constrainCheckbox.disabled && + gDialog.widthUnitsMenulist.selectedIndex == 0 && + gDialog.heightUnitsMenulist.selectedIndex == 0 + ) { + gConstrainWidth = Number(TrimString(gDialog.widthInput.value)); + gConstrainHeight = Number(TrimString(gDialog.heightInput.value)); + } +} + +function constrainProportions(srcID, destID) { + var srcElement = document.getElementById(srcID); + if (!srcElement) { + return; + } + + var destElement = document.getElementById(destID); + if (!destElement) { + return; + } + + // always force an integer (whether we are constraining or not) + forceInteger(srcID); + + if ( + !gActualWidth || + !gActualHeight || + !(gDialog.constrainCheckbox.checked && !gDialog.constrainCheckbox.disabled) + ) { + return; + } + + // double-check that neither width nor height is in percent mode; bail if so! + if ( + gDialog.widthUnitsMenulist.selectedIndex != 0 || + gDialog.heightUnitsMenulist.selectedIndex != 0 + ) { + return; + } + + // This always uses the actual width and height ratios + // which is kind of funky if you change one number without the constrain + // and then turn constrain on and change a number + // I prefer the old strategy (below) but I can see some merit to this solution + if (srcID == "widthInput") { + destElement.value = Math.round( + (srcElement.value * gActualHeight) / gActualWidth + ); + } else { + destElement.value = Math.round( + (srcElement.value * gActualWidth) / gActualHeight + ); + } + + /* + // With this strategy, the width and height ratio + // can be reset to whatever the user entered. + if (srcID == "widthInput") { + destElement.value = Math.round( srcElement.value * gConstrainHeight / gConstrainWidth ); + } else { + destElement.value = Math.round( srcElement.value * gConstrainWidth / gConstrainHeight ); + } + */ +} + +function removeImageMap() { + gRemoveImageMap = true; + gCanRemoveImageMap = false; + SetElementEnabledById("removeImageMap", false); +} + +function SwitchToValidatePanel() { + if ( + gDialog.tabBox && + gValidateTab && + gDialog.tabBox.selectedTab != gValidateTab + ) { + gDialog.tabBox.selectedTab = gValidateTab; + } +} + +// Get data from widgets, validate, and set for the global element +// accessible to AdvancedEdit() [in EdDialogCommon.js] +function ValidateImage() { + var editor = GetCurrentEditor(); + if (!editor) { + return false; + } + + gValidateTab = gDialog.tabLocation; + if (!gDialog.srcInput.value) { + Services.prompt.alert( + window, + GetString("Alert"), + GetString("MissingImageError") + ); + SwitchToValidatePanel(); + gDialog.srcInput.focus(); + return false; + } + + // We must convert to "file:///" or "http://" format else image doesn't load! + let src = gDialog.srcInput.value.trim(); + + if (isImageDataShortened(src)) { + src = restoredImageData(gDialog.srcInput); + } else { + var checkbox = document.getElementById("MakeRelativeCheckbox"); + try { + if (checkbox && !checkbox.checked) { + src = Services.uriFixup.getFixupURIInfo( + src, + Ci.nsIURIFixup.FIXUP_FLAG_NONE + ).preferredURI.spec; + } + } catch (e) {} + + globalElement.setAttribute("src", src); + } + + let title = gDialog.titleInput.value.trim(); + if (title) { + globalElement.setAttribute("title", title); + } else { + globalElement.removeAttribute("title"); + } + + // Force user to enter Alt text only if "Alternate text" radio is checked + // Don't allow just spaces in alt text + var alt = ""; + var useAlt = gDialog.altTextRadioGroup.selectedItem == gDialog.altTextRadio; + if (useAlt) { + alt = TrimString(gDialog.altTextInput.value); + } + + if (alt || !useAlt) { + globalElement.setAttribute("alt", alt); + } else if (!gDoAltTextError) { + globalElement.removeAttribute("alt"); + } else { + Services.prompt.alert(window, GetString("Alert"), GetString("NoAltText")); + SwitchToValidatePanel(); + gDialog.altTextInput.focus(); + return false; + } + + var width = ""; + var height = ""; + + gValidateTab = gDialog.tabDimensions; + if (!gDialog.actualSizeRadio.selected) { + // Get user values for width and height + width = ValidateNumber( + gDialog.widthInput, + gDialog.widthUnitsMenulist, + 1, + gMaxPixels, + globalElement, + "width", + false, + true + ); + if (gValidationError) { + return false; + } + + height = ValidateNumber( + gDialog.heightInput, + gDialog.heightUnitsMenulist, + 1, + gMaxPixels, + globalElement, + "height", + false, + true + ); + if (gValidationError) { + return false; + } + } + + // We always set the width and height attributes, even if same as actual. + // This speeds up layout of pages since sizes are known before image is loaded + if (!width) { + width = gActualWidth; + } + if (!height) { + height = gActualHeight; + } + + // Remove existing width and height only if source changed + // and we couldn't obtain actual dimensions + var srcChanged = src != gOriginalSrc; + if (width) { + editor.setAttributeOrEquivalent(globalElement, "width", width, true); + } else if (srcChanged) { + editor.removeAttributeOrEquivalent(globalElement, "width", true); + } + + if (height) { + editor.setAttributeOrEquivalent(globalElement, "height", height, true); + } else if (srcChanged) { + editor.removeAttributeOrEquivalent(globalElement, "height", true); + } + + // spacing attributes + gValidateTab = gDialog.tabBorder; + ValidateNumber( + gDialog.imagelrInput, + null, + 0, + gMaxPixels, + globalElement, + "hspace", + false, + true, + true + ); + if (gValidationError) { + return false; + } + + ValidateNumber( + gDialog.imagetbInput, + null, + 0, + gMaxPixels, + globalElement, + "vspace", + false, + true + ); + if (gValidationError) { + return false; + } + + // note this is deprecated and should be converted to stylesheets + ValidateNumber( + gDialog.border, + null, + 0, + gMaxPixels, + globalElement, + "border", + false, + true + ); + if (gValidationError) { + return false; + } + + // Default or setting "bottom" means don't set the attribute + // Note that the attributes "left" and "right" are opposite + // of what we use in the UI, which describes where the TEXT wraps, + // not the image location (which is what the HTML describes) + switch (gDialog.alignTypeSelect.value) { + case "top": + case "middle": + case "right": + case "left": + editor.setAttributeOrEquivalent( + globalElement, + "align", + gDialog.alignTypeSelect.value, + true + ); + break; + default: + try { + editor.removeAttributeOrEquivalent(globalElement, "align", true); + } catch (e) {} + } + + return true; +} diff --git a/comm/suite/editor/components/dialogs/content/EdImageLinkLoader.js b/comm/suite/editor/components/dialogs/content/EdImageLinkLoader.js new file mode 100755 index 0000000000..5b88a5703f --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdImageLinkLoader.js @@ -0,0 +1,145 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +var gMsgCompProcessLink = false; +var gMsgCompInputElement = null; +var gMsgCompPrevInputValue = null; +var gMsgCompPrevMozDoNotSendAttribute; +var gMsgCompAttachSourceElement = null; + +function OnLoadDialog() { + gMsgCompAttachSourceElement = document.getElementById("AttachSourceToMail"); + var editor = GetCurrentEditor(); + if ( + gMsgCompAttachSourceElement && + editor && + editor.flags & Ci.nsIEditor.eEditorMailMask + ) { + SetRelativeCheckbox = function() { + SetAttachCheckbox(); + }; + // initialize the AttachSourceToMail checkbox + gMsgCompAttachSourceElement.hidden = false; + + switch (document.documentElement.id) { + case "imageDlg": + gMsgCompInputElement = gDialog.srcInput; + gMsgCompProcessLink = false; + break; + case "linkDlg": + gMsgCompInputElement = gDialog.hrefInput; + gMsgCompProcessLink = true; + break; + } + if (gMsgCompInputElement) { + SetAttachCheckbox(); + gMsgCompPrevMozDoNotSendAttribute = globalElement.getAttribute( + "moz-do-not-send" + ); + } + } +} +addEventListener("load", OnLoadDialog, false); + +function OnAcceptDialog() { + // Auto-convert file URLs to data URLs. If we're in the link properties + // dialog convert only when requested - for the image dialog do it always. + if (gMsgCompInputElement && + /^file:/i.test(gMsgCompInputElement.value.trim()) && + (gMsgCompAttachSourceElement.checked || !gMsgCompProcessLink)) { + var dataURI = GenerateDataURL(gMsgCompInputElement.value.trim()); + gMsgCompInputElement.value = dataURI; + gMsgCompAttachSourceElement.checked = true; + } + DoAttachSourceCheckbox(); +} +document.addEventListener("dialogaccept", OnAcceptDialog, true); + +function SetAttachCheckbox() { + var resetCheckbox = false; + var mozDoNotSend = globalElement.getAttribute("moz-do-not-send"); + + // In case somebody played with the advanced property and changed the moz-do-not-send attribute + if (mozDoNotSend != gMsgCompPrevMozDoNotSendAttribute) { + gMsgCompPrevMozDoNotSendAttribute = mozDoNotSend; + resetCheckbox = true; + } + + // Has the URL changed + if ( + gMsgCompInputElement && + gMsgCompInputElement.value != gMsgCompPrevInputValue + ) { + gMsgCompPrevInputValue = gMsgCompInputElement.value; + resetCheckbox = true; + } + + if (gMsgCompInputElement && resetCheckbox) { + // Here is the rule about how to set the checkbox Attach Source To Message: + // If the attribute "moz-do-not-send" has not been set, we look at the scheme of the URL + // and at some preference to decide what is the best for the user. + // If it is set to "false", the checkbox is checked, otherwise unchecked. + var attach = false; + if (mozDoNotSend == null) { + // We haven't yet set the "moz-do-not-send" attribute. + var inputValue = gMsgCompInputElement.value.trim(); + if (/^(file|data):/i.test(inputValue)) { + // For files or data URLs, default to attach them. + attach = true; + } else if ( + !gMsgCompProcessLink && // Implies image dialogue. + /^https?:/i.test(inputValue) + ) { + // For images loaded via http(s) we default to the preference value. + attach = Services.prefs.getBoolPref("mail.compose.attach_http_images"); + } + } else { + attach = mozDoNotSend == "false"; + } + + gMsgCompAttachSourceElement.checked = attach; + } +} + +function DoAttachSourceCheckbox() { + gMsgCompPrevMozDoNotSendAttribute = (!gMsgCompAttachSourceElement.checked).toString(); + globalElement.setAttribute( + "moz-do-not-send", + gMsgCompPrevMozDoNotSendAttribute + ); +} + +function GenerateDataURL(url) { + var file = Services.io.newURI(url).QueryInterface(Ci.nsIFileURL).file; + var contentType = Cc["@mozilla.org/mime;1"] + .getService(Ci.nsIMIMEService) + .getTypeFromFile(file); + var inputStream = Cc[ + "@mozilla.org/network/file-input-stream;1" + ].createInstance(Ci.nsIFileInputStream); + inputStream.init(file, 0x01, 0o600, 0); + var stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance( + Ci.nsIBinaryInputStream + ); + stream.setInputStream(inputStream); + let data = ""; + while (stream.available() > 0) { + data += stream.readBytes(stream.available()); + } + let encoded = btoa(data); + stream.close(); + return ( + "data:" + + contentType + + ";filename=" + + encodeURIComponent(file.leafName) + + ";base64," + + encoded + ); +} diff --git a/comm/suite/editor/components/dialogs/content/EdImageProps.js b/comm/suite/editor/components/dialogs/content/EdImageProps.js new file mode 100644 index 0000000000..5b73488dcb --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdImageProps.js @@ -0,0 +1,293 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ +/* import-globals-from EdImageDialog.js */ + +var gAnchorElement = null; +var gLinkElement = null; +var gOriginalHref = ""; +var gHNodeArray = {}; + +// dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + var editor = GetCurrentEditor(); + if (!editor) { + window.close(); + return; + } + + ImageStartup(); + gDialog.hrefInput = document.getElementById("hrefInput"); + gDialog.makeRelativeLink = document.getElementById("MakeRelativeLink"); + gDialog.showLinkBorder = document.getElementById("showLinkBorder"); + gDialog.linkTab = document.getElementById("imageLinkTab"); + gDialog.linkAdvanced = document.getElementById("LinkAdvancedEditButton"); + + // Get a single selected image element + var tagName = "img"; + if ("arguments" in window && window.arguments[0]) { + imageElement = window.arguments[0]; + // We've been called from form field properties, so we can't insert a link + gDialog.linkTab.remove(); + gDialog.linkTab = null; + } else { + // First check for <input type="image"> + try { + imageElement = editor.getSelectedElement("input"); + + if (!imageElement || imageElement.getAttribute("type") != "image") { + // Get a single selected image element + imageElement = editor.getSelectedElement(tagName); + if (imageElement) { + gAnchorElement = editor.getElementOrParentByTagName( + "href", + imageElement + ); + } + } + } catch (e) {} + } + + if (imageElement) { + // We found an element and don't need to insert one + if (imageElement.hasAttribute("src")) { + gInsertNewImage = false; + gActualWidth = imageElement.naturalWidth; + gActualHeight = imageElement.naturalHeight; + } + } else { + gInsertNewImage = true; + + // We don't have an element selected, + // so create one with default attributes + try { + imageElement = editor.createElementWithDefaults(tagName); + } catch (e) {} + + if (!imageElement) { + dump("Failed to get selected element or create a new one!\n"); + window.close(); + return; + } + try { + gAnchorElement = editor.getSelectedElement("href"); + } catch (e) {} + } + + // Make a copy to use for AdvancedEdit + globalElement = imageElement.cloneNode(false); + + // We only need to test for this once per dialog load + gHaveDocumentUrl = GetDocumentBaseUrl(); + + InitDialog(); + if (gAnchorElement) { + gOriginalHref = gAnchorElement.getAttribute("href"); + // Make a copy to use for AdvancedEdit + gLinkElement = gAnchorElement.cloneNode(false); + } else { + gLinkElement = editor.createElementWithDefaults("a"); + } + gDialog.hrefInput.value = gOriginalHref; + + FillLinkMenulist(gDialog.hrefInput, gHNodeArray); + ChangeLinkLocation(); + + // Save initial source URL + gOriginalSrc = gDialog.srcInput.value; + + // By default turn constrain on, but both width and height must be in pixels + gDialog.constrainCheckbox.checked = + gDialog.widthUnitsMenulist.selectedIndex == 0 && + gDialog.heightUnitsMenulist.selectedIndex == 0; + + // Start in "Link" tab if 2nd argument is true + if (gDialog.linkTab && "arguments" in window && window.arguments[1]) { + document.getElementById("TabBox").selectedTab = gDialog.linkTab; + SetTextboxFocus(gDialog.hrefInput); + } else { + SetTextboxFocus(gDialog.srcInput); + } + + SetWindowLocation(); +} + +// Set dialog widgets with attribute data +// We get them from globalElement copy so this can be used +// by AdvancedEdit(), which is shared by all property dialogs +function InitDialog() { + InitImage(); + var border = TrimString(gDialog.border.value); + gDialog.showLinkBorder.checked = border != "" && border > 0; +} + +function ChangeLinkLocation() { + var href = TrimString(gDialog.hrefInput.value); + SetRelativeCheckbox(gDialog.makeRelativeLink); + gDialog.showLinkBorder.disabled = !href; + gDialog.linkAdvanced.disabled = !href; + gLinkElement.setAttribute("href", href); +} + +function ToggleShowLinkBorder() { + if (gDialog.showLinkBorder.checked) { + var border = TrimString(gDialog.border.value); + if (!border || border == "0") { + gDialog.border.value = "2"; + } + } else { + gDialog.border.value = "0"; + } +} + +// Get data from widgets, validate, and set for the global element +// accessible to AdvancedEdit() [in EdDialogCommon.js] +function ValidateData() { + return ValidateImage(); +} + +function onAccept(event) { + // Use this now (default = false) so Advanced Edit button dialog doesn't trigger error message + gDoAltTextError = true; + + if (ValidateData()) { + if ("arguments" in window && window.arguments[0]) { + SaveWindowLocation(); + return; + } + + var editor = GetCurrentEditor(); + + editor.beginTransaction(); + + try { + if (gRemoveImageMap) { + globalElement.removeAttribute("usemap"); + if (gImageMap) { + editor.deleteNode(gImageMap); + gInsertNewIMap = true; + gImageMap = null; + } + } else if (gImageMap) { + // un-comment to see that inserting image maps does not work! + /* + gImageMap = editor.createElementWithDefaults("map"); + gImageMap.setAttribute("name", "testing"); + var testArea = editor.createElementWithDefaults("area"); + testArea.setAttribute("shape", "circle"); + testArea.setAttribute("coords", "86,102,52"); + testArea.setAttribute("href", "test"); + gImageMap.appendChild(testArea); + */ + + // Assign to map if there is one + var mapName = gImageMap.getAttribute("name"); + if (mapName != "") { + globalElement.setAttribute("usemap", "#" + mapName); + if (globalElement.getAttribute("border") == "") { + globalElement.setAttribute("border", 0); + } + } + } + + // Create or remove the link as appropriate + var href = gDialog.hrefInput.value; + if (href != gOriginalHref) { + if (href && !gInsertNewImage) { + EditorSetTextProperty("a", "href", href); + // gAnchorElement is needed for cloning attributes later. + if (!gAnchorElement) { + gAnchorElement = editor.getElementOrParentByTagName( + "href", + imageElement + ); + } + } else { + EditorRemoveTextProperty("href", ""); + } + } + + // If inside a link, always write the 'border' attribute + if (href) { + if (gDialog.showLinkBorder.checked) { + // Use default = 2 if border attribute is empty + if (!globalElement.hasAttribute("border")) { + globalElement.setAttribute("border", "2"); + } + } else { + globalElement.setAttribute("border", "0"); + } + } + + if (gInsertNewImage) { + if (href) { + gLinkElement.appendChild(imageElement); + editor.insertElementAtSelection(gLinkElement, true); + } else { + // 'true' means delete the selection before inserting + editor.insertElementAtSelection(imageElement, true); + } + } + + // Check to see if the link was to a heading + // Do this last because it moves the caret (BAD!) + if (href in gHNodeArray) { + var anchorNode = editor.createElementWithDefaults("a"); + if (anchorNode) { + anchorNode.name = href.substr(1); + // Remember to use editor method so it is undoable! + editor.insertNode(anchorNode, gHNodeArray[href], 0); + } + } + // All values are valid - copy to actual element in doc or + // element we just inserted + editor.cloneAttributes(imageElement, globalElement); + if (gAnchorElement) { + editor.cloneAttributes(gAnchorElement, gLinkElement); + } + + // If document is empty, the map element won't insert, + // so always insert the image first + if (gImageMap && gInsertNewIMap) { + // Insert the ImageMap element at beginning of document + var body = editor.rootElement; + editor.setShouldTxnSetSelection(false); + editor.insertNode(gImageMap, body, 0); + editor.setShouldTxnSetSelection(true); + } + } catch (e) { + dump(e); + } + + editor.endTransaction(); + + SaveWindowLocation(); + return; + } + + gDoAltTextError = false; + + event.preventDefault(); +} + +function onLinkAdvancedEdit() { + window.AdvancedEditOK = false; + window.openDialog( + "chrome://editor/content/EdAdvancedEdit.xhtml", + "_blank", + "chrome,close,titlebar,modal,resizable=yes", + "", + gLinkElement + ); + window.focus(); + if (window.AdvancedEditOK) { + gDialog.hrefInput.value = gLinkElement.getAttribute("href"); + } +} diff --git a/comm/suite/editor/components/dialogs/content/EdImageProps.xhtml b/comm/suite/editor/components/dialogs/content/EdImageProps.xhtml new file mode 100644 index 0000000000..11d7d03026 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdImageProps.xhtml @@ -0,0 +1,116 @@ +<?xml version="1.0"?> +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % edImageProperties SYSTEM "chrome://editor/locale/EditorImageProperties.dtd"> +%edImageProperties; +<!ENTITY % composeEditorOverlayDTD SYSTEM "chrome://messenger/locale/messengercompose/mailComposeEditorOverlay.dtd"> +%composeEditorOverlayDTD; +<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd"> +%edDialogOverlay; +]> + +<!-- dialog containing a control requiring initial setup --> +<dialog id="imageDlg" title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup()" + buttons="accept,cancel"> + + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdImageProps.js"/> + <script src="chrome://editor/content/EdImageDialog.js"/> + <script src="chrome://editor/content/EdImageLinkLoader.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <tabbox id="TabBox"> + <tabs flex="1"> + <tab id="imageLocationTab" label="&imageLocationTab.label;"/> + <tab id="imageDimensionsTab" label="&imageDimensionsTab.label;"/> + <tab id="imageAppearanceTab" label="&imageAppearanceTab.label;"/> + <tab id="imageLinkTab" label="&imageLinkTab.label;"/> + </tabs> + <tabpanels> +#include edImage.inc.xhtml + <vbox> + <spacer class="spacer"/> + <vbox id="LinkLocationBox"> + <label control="hrefInput" + accesskey="&LinkURLEditField2.accessKey;" + width="1">&LinkURLEditField2.label;</label> + <textbox id="hrefInput" type="text" + class="uri-element padded" oninput="ChangeLinkLocation();"/> + <hbox align="center"> + <checkbox id="MakeRelativeLink" + for="hrefInput" + label="&makeUrlRelative.label;" + accesskey="&makeUrlRelative.accessKey;" + oncommand="MakeInputValueRelativeOrAbsolute(this);" + tooltiptext="&makeUrlRelative.tooltip;"/> + <spacer flex="1"/> + <button label="&chooseFileLinkButton.label;" accesskey="&chooseFileLinkButton.accessKey;" + oncommand="chooseLinkFile();"/> + </hbox> + </vbox> + <spacer class="spacer"/> + <hbox> + <checkbox id="showLinkBorder" + label="&showImageLinkBorder.label;" + accesskey="&showImageLinkBorder.accessKey;" + oncommand="ToggleShowLinkBorder();"/> + <spacer flex="1"/> + <button id="LinkAdvancedEditButton" + label="&LinkAdvancedEditButton.label;" + accesskey="&LinkAdvancedEditButton.accessKey;" + tooltiptext="&LinkAdvancedEditButton.tooltip;" + oncommand="onLinkAdvancedEdit();"/> + </hbox> + </vbox> + </tabpanels> + </tabbox> + + <hbox align="end"> + <groupbox id="imagePreview" orient="horizontal" flex="1"> + <hbox class="groupbox-title"> + <label class="header">&previewBox.label;</label> + </hbox> + <hbox id="preview-image-box" align="center"> + <spacer flex="1"/> + <description id="preview-image-holder"/> + <spacer flex="1"/> + </hbox> + <vbox id="PreviewSize" collapsed="true"> + <spacer flex="1"/> + <label value="&actualSize.label;"/> + <hbox> + <label value="&widthEditField.label;"/> + <spacer flex="1"/> + <label id="PreviewWidth"/> + </hbox> + <hbox> + <label value="&heightEditField.label;"/> + <spacer flex="1"/> + <label id="PreviewHeight"/> + </hbox> + <spacer flex="1"/> + </vbox> + </groupbox> + + <vbox id="AdvancedEdit"> + <hbox flex="1" style="margin-top: 0.2em" align="center"> + <!-- This will right-align the button --> + <spacer flex="1"/> + <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;" + accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/> + </hbox> + </vbox> + </hbox> + <separator class="groove"/> + +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdInputImage.js b/comm/suite/editor/components/dialogs/content/EdInputImage.js new file mode 100644 index 0000000000..556acc7b13 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdInputImage.js @@ -0,0 +1,189 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ +/* import-globals-from EdImageDialog.js */ + +// dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + var editor = GetCurrentEditor(); + if (!editor) { + window.close(); + return; + } + + gDialog = { + inputName: document.getElementById("InputName"), + inputDisabled: document.getElementById("InputDisabled"), + inputTabIndex: document.getElementById("InputTabIndex"), + }; + + ImageStartup(); + + // Get a single selected input element + var tagName = "input"; + try { + imageElement = editor.getSelectedElement(tagName); + } catch (e) {} + + if (imageElement) { + // We found an element and don't need to insert one + gInsertNewImage = false; + } else { + gInsertNewImage = true; + + // We don't have an element selected, + // so create one with default attributes + try { + imageElement = editor.createElementWithDefaults(tagName); + } catch (e) {} + + if (!imageElement) { + dump("Failed to get selected element or create a new one!\n"); + window.close(); + return; + } + var imgElement; + try { + imgElement = editor.getSelectedElement("img"); + } catch (e) {} + + if (imgElement) { + // We found an image element, convert it to an input type="image" + var attributes = [ + "src", + "alt", + "width", + "height", + "hspace", + "vspace", + "border", + "align", + "usemap", + "ismap", + ]; + for (let i in attributes) { + imageElement.setAttribute( + attributes[i], + imgElement.getAttribute(attributes[i]) + ); + } + } + } + + // Make a copy to use for AdvancedEdit + globalElement = imageElement.cloneNode(false); + + // We only need to test for this once per dialog load + gHaveDocumentUrl = GetDocumentBaseUrl(); + + InitDialog(); + + // Save initial source URL + gOriginalSrc = gDialog.srcInput.value; + + // By default turn constrain on, but both width and height must be in pixels + gDialog.constrainCheckbox.checked = + gDialog.widthUnitsMenulist.selectedIndex == 0 && + gDialog.heightUnitsMenulist.selectedIndex == 0; + + SetTextboxFocus(gDialog.inputName); + + SetWindowLocation(); +} + +function InitDialog() { + InitImage(); + gDialog.inputName.value = globalElement.getAttribute("name"); + gDialog.inputDisabled.setAttribute( + "checked", + globalElement.hasAttribute("disabled") + ); + gDialog.inputTabIndex.value = globalElement.getAttribute("tabindex"); +} + +function ValidateData() { + if (!ValidateImage()) { + return false; + } + if (gDialog.inputName.value) { + globalElement.setAttribute("name", gDialog.inputName.value); + } else { + globalElement.removeAttribute("name"); + } + if (gDialog.inputTabIndex.value) { + globalElement.setAttribute("tabindex", gDialog.inputTabIndex.value); + } else { + globalElement.removeAttribute("tabindex"); + } + if (gDialog.inputDisabled.checked) { + globalElement.setAttribute("disabled", ""); + } else { + globalElement.removeAttribute("disabled"); + } + globalElement.setAttribute("type", "image"); + return true; +} + +function onAccept(event) { + // Show alt text error only once + // (we don't initialize doAltTextError=true + // so Advanced edit button dialog doesn't trigger that error message) + // Use this now (default = false) so Advanced Edit button dialog doesn't trigger error message + gDoAltTextError = true; + + if (ValidateData()) { + var editor = GetCurrentEditor(); + editor.beginTransaction(); + + try { + if (gRemoveImageMap) { + globalElement.removeAttribute("usemap"); + if (gImageMap) { + editor.deleteNode(gImageMap); + gInsertNewIMap = true; + gImageMap = null; + } + } else if (gImageMap) { + // Assign to map if there is one + var mapName = gImageMap.getAttribute("name"); + if (mapName != "") { + globalElement.setAttribute("usemap", "#" + mapName); + if (globalElement.getAttribute("border") == "") { + globalElement.setAttribute("border", 0); + } + } + } + + if (gInsertNewImage) { + // 'true' means delete the selection before inserting + // in case were are converting an image to an input type="image" + editor.insertElementAtSelection(imageElement, true); + } + editor.cloneAttributes(imageElement, globalElement); + + // If document is empty, the map element won't insert, + // so always insert the image element first + if (gImageMap && gInsertNewIMap) { + // Insert the ImageMap element at beginning of document + var body = editor.rootElement; + editor.setShouldTxnSetSelection(false); + editor.insertNode(gImageMap, body, 0); + editor.setShouldTxnSetSelection(true); + } + } catch (e) {} + + editor.endTransaction(); + + SaveWindowLocation(); + + return; + } + event.preventDefault(); +} diff --git a/comm/suite/editor/components/dialogs/content/EdInputImage.xhtml b/comm/suite/editor/components/dialogs/content/EdInputImage.xhtml new file mode 100644 index 0000000000..d3fc8c8270 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdInputImage.xhtml @@ -0,0 +1,104 @@ +<?xml version="1.0"?> +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % edInputProperties SYSTEM "chrome://editor/locale/EditorInputProperties.dtd"> +%edInputProperties; +<!ENTITY % edImageProperties SYSTEM "chrome://editor/locale/EditorImageProperties.dtd"> +%edImageProperties; +<!ENTITY % composeEditorOverlayDTD SYSTEM "chrome://messenger/locale/messengercompose/mailComposeEditorOverlay.dtd"> +%composeEditorOverlayDTD; +<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd"> +%edDialogOverlay; +]> + +<dialog title="&windowTitleImage.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup();"> + + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdInputImage.js"/> + <script src="chrome://editor/content/EdImageDialog.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <tabbox id="TabBox"> + <tabs flex="1"> + <tab id="imageInputTab" label="&imageInputTab.label;"/> + <tab id="imageLocationTab" label="&imageLocationTab.label;"/> + <tab id="imageDimensionsTab" label="&imageDimensionsTab.label;"/> + <tab id="imageAppearanceTab" label="&imageAppearanceTab.label;"/> + </tabs> + <tabpanels> + <groupbox> + <hbox class="groupbox-title"> + <label class="header">&InputSettings.label;</label> + </hbox> + <grid><columns><column/><column/></columns> + <rows> + <row align="center"> + <label value="&InputName.label;"/> + <textbox id="InputName"/> + </row> + <row> + <spacer/> + <checkbox id="InputDisabled" label="&InputDisabled.label;"/> + </row> + <row align="center"> + <label value="&tabIndex.label;"/> + <hbox> + <textbox id="InputTabIndex" class="narrow" oninput="forceInteger(this.id);"/> + </hbox> + </row> + </rows> + </grid> + </groupbox> +#include edImage.inc.xhtml + </tabpanels> + </tabbox> + + <hbox align="end"> + <groupbox id="imagePreview" orient="horizontal" flex="1"> + <hbox class="groupbox-title"> + <label class="header">&previewBox.label;</label> + </hbox> + <hbox id="preview-image-box" align="center"> + <spacer flex="1"/> + <description id="preview-image-holder"/> + <spacer flex="1"/> + </hbox> + <vbox id="PreviewSize" collapsed="true"> + <spacer flex="1"/> + <label value="&actualSize.label;"/> + <hbox> + <label value="&widthEditField.label;"/> + <spacer flex="1"/> + <label id="PreviewWidth"/> + </hbox> + <hbox> + <label value="&heightEditField.label;"/> + <spacer flex="1"/> + <label id="PreviewHeight"/> + </hbox> + <spacer flex="1"/> + </vbox> + </groupbox> + + <vbox id="AdvancedEdit"> + <hbox flex="1" style="margin-top: 0.2em" align="center"> + <!-- This will right-align the button --> + <spacer flex="1"/> + <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;" + accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/> + </hbox> + <separator id="advancedSeparator" class="groove"/> + </vbox> + </hbox> + +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdInputProps.js b/comm/suite/editor/components/dialogs/content/EdInputProps.js new file mode 100644 index 0000000000..a737e263c7 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdInputProps.js @@ -0,0 +1,345 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +var insertNew; +var inputElement; + +// dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + var editor = GetCurrentEditor(); + if (!editor) { + dump("Failed to get active editor!\n"); + window.close(); + return; + } + + gDialog = { + accept: document.documentElement.getButton("accept"), + inputType: document.getElementById("InputType"), + inputNameDeck: document.getElementById("InputNameDeck"), + inputName: document.getElementById("InputName"), + inputValueDeck: document.getElementById("InputValueDeck"), + inputValue: document.getElementById("InputValue"), + inputDeck: document.getElementById("InputDeck"), + inputChecked: document.getElementById("InputChecked"), + inputSelected: document.getElementById("InputSelected"), + inputReadOnly: document.getElementById("InputReadOnly"), + inputDisabled: document.getElementById("InputDisabled"), + inputTabIndex: document.getElementById("InputTabIndex"), + inputAccessKey: document.getElementById("InputAccessKey"), + inputSize: document.getElementById("InputSize"), + inputMaxLength: document.getElementById("InputMaxLength"), + inputAccept: document.getElementById("InputAccept"), + MoreSection: document.getElementById("MoreSection"), + MoreFewerButton: document.getElementById("MoreFewerButton"), + AdvancedEditButton: document.getElementById("AdvancedEditButton"), + AdvancedEditDeck: document.getElementById("AdvancedEditDeck"), + }; + + // Get a single selected input element + const kTagName = "input"; + try { + inputElement = editor.getSelectedElement(kTagName); + } catch (e) {} + + if (inputElement) { + // We found an element and don't need to insert one + insertNew = false; + } else { + insertNew = true; + + // We don't have an element selected, + // so create one with default attributes + try { + inputElement = editor.createElementWithDefaults(kTagName); + } catch (e) {} + + if (!inputElement) { + dump("Failed to get selected element or create a new one!\n"); + window.close(); + return; + } + + var imgElement = editor.getSelectedElement("img"); + if (imgElement) { + // We found an image element, convert it to an input type="image" + inputElement.setAttribute("type", "image"); + + var attributes = [ + "src", + "alt", + "width", + "height", + "hspace", + "vspace", + "border", + "align", + ]; + for (let i in attributes) { + inputElement.setAttribute( + attributes[i], + imgElement.getAttribute(attributes[i]) + ); + } + } else { + inputElement.setAttribute("value", GetSelectionAsText()); + } + } + + // Make a copy to use for AdvancedEdit + globalElement = inputElement.cloneNode(false); + + InitDialog(); + + InitMoreFewer(); + + gDialog.inputType.focus(); + + SetWindowLocation(); +} + +function InitDialog() { + var type = globalElement.getAttribute("type"); + var index = 0; + switch (type) { + case "button": + index = 9; + break; + case "checkbox": + index = 2; + break; + case "file": + index = 6; + break; + case "hidden": + index = 7; + break; + case "image": + index = 8; + break; + case "password": + index = 1; + break; + case "radio": + index = 3; + break; + case "reset": + index = 5; + break; + case "submit": + index = 4; + break; + } + gDialog.inputType.selectedIndex = index; + gDialog.inputName.value = globalElement.getAttribute("name"); + gDialog.inputValue.value = globalElement.getAttribute("value"); + gDialog.inputChecked.setAttribute( + "checked", + globalElement.hasAttribute("checked") + ); + gDialog.inputSelected.setAttribute( + "checked", + globalElement.hasAttribute("checked") + ); + gDialog.inputReadOnly.setAttribute( + "checked", + globalElement.hasAttribute("readonly") + ); + gDialog.inputDisabled.setAttribute( + "checked", + globalElement.hasAttribute("disabled") + ); + gDialog.inputTabIndex.value = globalElement.getAttribute("tabindex"); + gDialog.inputAccessKey.value = globalElement.getAttribute("accesskey"); + gDialog.inputSize.value = globalElement.getAttribute("size"); + gDialog.inputMaxLength.value = globalElement.getAttribute("maxlength"); + gDialog.inputAccept.value = globalElement.getAttribute("accept"); + SelectInputType(); +} + +function SelectInputType() { + var index = gDialog.inputType.selectedIndex; + gDialog.AdvancedEditDeck.setAttribute("selectedIndex", 0); + gDialog.inputNameDeck.setAttribute("selectedIndex", 0); + gDialog.inputValueDeck.setAttribute("selectedIndex", 0); + gDialog.inputValue.disabled = false; + gDialog.inputChecked.disabled = index != 2; + gDialog.inputSelected.disabled = index != 3; + gDialog.inputReadOnly.disabled = index > 1; + gDialog.inputTabIndex.disabled = index == 7; + gDialog.inputAccessKey.disabled = index == 7; + gDialog.inputSize.disabled = index > 1; + gDialog.inputMaxLength.disabled = index > 1; + gDialog.inputAccept.disabled = index != 6; + switch (index) { + case 0: + case 1: + gDialog.inputValueDeck.setAttribute("selectedIndex", 1); + gDialog.inputDeck.setAttribute("selectedIndex", 2); + break; + case 2: + gDialog.inputDeck.setAttribute("selectedIndex", 0); + break; + case 3: + gDialog.inputDeck.setAttribute("selectedIndex", 1); + gDialog.inputNameDeck.setAttribute("selectedIndex", 1); + break; + case 6: + gDialog.inputValue.disabled = true; + gDialog.inputAccept.disabled = false; + break; + case 8: + gDialog.inputValue.disabled = true; + gDialog.AdvancedEditDeck.setAttribute("selectedIndex", 1); + gDialog.inputName.removeEventListener("input", onInput); + break; + case 7: + gDialog.inputValueDeck.setAttribute("selectedIndex", 1); + break; + } + onInput(); +} + +function onInput() { + var disabled = false; + switch (gDialog.inputType.selectedIndex) { + case 3: + disabled = disabled || !gDialog.inputValue.value; + break; + case 4: + case 5: + break; + case 8: + disabled = !globalElement.hasAttribute("src"); + break; + default: + disabled = !gDialog.inputName.value; + break; + } + if (gDialog.accept.disabled != disabled) { + gDialog.accept.disabled = disabled; + gDialog.AdvancedEditButton.disabled = disabled; + } +} + +function doImageProperties() { + window.openDialog( + "chrome://editor/content/EdImageProps.xhtml", + "_blank", + "chrome,close,titlebar,modal", + globalElement + ); + window.focus(); + onInput(); +} + +function ValidateData() { + var attributes = { + type: "", + name: gDialog.inputName.value, + value: gDialog.inputValue.value, + tabindex: gDialog.inputTabIndex.value, + accesskey: "", + size: "", + maxlength: "", + accept: "", + }; + var index = gDialog.inputType.selectedIndex; + var flags = { + checked: false, + readonly: false, + disabled: gDialog.inputDisabled.checked, + }; + switch (index) { + case 1: + attributes.type = "password"; + // Falls through + case 0: + flags.readonly = gDialog.inputReadOnly.checked; + attributes.size = gDialog.inputSize.value; + attributes.maxlength = gDialog.inputMaxLength.value; + break; + case 2: + attributes.type = "checkbox"; + flags.checked = gDialog.inputChecked.checked; + break; + case 3: + attributes.type = "radio"; + flags.checked = gDialog.inputSelected.checked; + break; + case 4: + attributes.type = "submit"; + attributes.accesskey = gDialog.inputAccessKey.value; + break; + case 5: + attributes.type = "reset"; + attributes.accesskey = gDialog.inputAccessKey.value; + break; + case 6: + attributes.type = "file"; + attributes.accept = gDialog.inputAccept.value; + attributes.value = ""; + break; + case 7: + attributes.type = "hidden"; + attributes.tabindex = ""; + break; + case 8: + attributes.type = "image"; + attributes.value = ""; + break; + case 9: + attributes.type = "button"; + attributes.accesskey = gDialog.inputAccessKey.value; + break; + } + for (var a in attributes) { + if (attributes[a]) { + globalElement.setAttribute(a, attributes[a]); + } else { + globalElement.removeAttribute(a); + } + } + for (var f in flags) { + if (flags[f]) { + globalElement.setAttribute(f, ""); + } else { + globalElement.removeAttribute(f); + } + } + return true; +} + +function onAccept(event) { + if (ValidateData()) { + // All values are valid - copy to actual element in doc or + // element created to insert + + var editor = GetCurrentEditor(); + + editor.cloneAttributes(inputElement, globalElement); + + if (insertNew) { + try { + // 'true' means delete the selection before inserting + // in case were are converting an image to an input type="image" + editor.insertElementAtSelection(inputElement, true); + } catch (e) { + dump(e); + } + } + + SaveWindowLocation(); + + return; + } + event.preventDefault(); +} diff --git a/comm/suite/editor/components/dialogs/content/EdInputProps.xhtml b/comm/suite/editor/components/dialogs/content/EdInputProps.xhtml new file mode 100644 index 0000000000..c6011ee896 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdInputProps.xhtml @@ -0,0 +1,135 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % edInputProperties SYSTEM "chrome://editor/locale/EditorInputProperties.dtd"> +%edInputProperties; +<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd"> +%edDialogOverlay; +]> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup();" + buttons="accept,cancel"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdInputProps.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <groupbox> + <hbox class="groupbox-title"> + <label class="header" control="InputType" accesskey="&InputType.accesskey;">&InputType.label;</label> + </hbox> + <menulist id="InputType" oncommand="SelectInputType();"> + <menupopup> + <menuitem label="&text.value;"/> + <menuitem label="&password.value;"/> + <menuitem label="&checkbox.value;"/> + <menuitem label="&radio.value;"/> + <menuitem label="&submit.value;"/> + <menuitem label="&reset.value;"/> + <menuitem label="&file.value;"/> + <menuitem label="&hidden.value;"/> + <menuitem label="&image.value;"/> + <menuitem label="&button.value;"/> + </menupopup> + </menulist> + </groupbox> + + <groupbox> + <hbox class="groupbox-title"> + <label class="header">&InputSettings.label;</label> + </hbox> + <grid><columns><column/><column/></columns> + <rows> + <row align="center"> + <deck id="InputNameDeck"> + <label control="InputName" value="&InputName.label;" accesskey="&InputName.accesskey;"/> + <label control="InputName" value="&GroupName.label;" accesskey="&GroupName.accesskey;"/> + </deck> + <textbox id="InputName" oninput="onInput();"/> + </row> + <row align="center"> + <deck id="InputValueDeck"> + <label control="InputValue" value="&InputValue.label;" accesskey="&InputValue.accesskey;"/> + <label control="InputValue" value="&InitialValue.label;" accesskey="&InitialValue.accesskey;"/> + </deck> + <textbox id="InputValue" oninput="onInput();"/> + </row> + <row> + <spacer/> + <deck id="InputDeck" persist="index"> + <checkbox id="InputChecked" label="&InputChecked.label;" accesskey="&InputChecked.accesskey;"/> + <checkbox id="InputSelected" label="&InputSelected.label;" accesskey="&InputSelected.accesskey;"/> + <checkbox id="InputReadOnly" label="&InputReadOnly.label;" accesskey="&InputReadOnly.accesskey;"/> + </deck> + </row> + </rows> + </grid> + <hbox> + <button id="MoreFewerButton" oncommand="onMoreFewer();" persist="more"/> + </hbox> + <grid id="MoreSection" align="start"> + <columns><column/><column/></columns> + <rows> + <row> + <spacer/> + <checkbox id="InputDisabled" label="&InputDisabled.label;" accesskey="&InputDisabled.accesskey;"/> + </row> + <row align="center"> + <label control="InputTabIndex" value="&tabIndex.label;" accesskey="&tabIndex.accesskey;"/> + <hbox> + <textbox id="InputTabIndex" class="narrow" oninput="forceInteger(this.id);"/> + </hbox> + </row> + <row align="center"> + <label control="InputAccessKey" value="&AccessKey.label;" accesskey="&AccessKey.accesskey;"/> + <hbox> + <textbox id="InputAccessKey" class="narrow"/> + </hbox> + </row> + <row align="center"> + <label control="InputSize" value="&TextSize.label;" accesskey="&TextSize.accesskey;"/> + <hbox> + <textbox id="InputSize" class="narrow" oninput="forceInteger(this.id);"/> + </hbox> + </row> + <row align="center"> + <label control="InputMaxLength" value="&TextLength.label;" accesskey="&TextLength.accesskey;"/> + <hbox> + <textbox id="InputMaxLength" class="narrow" oninput="forceInteger(this.id);"/> + </hbox> + </row> + <row align="center"> + <label control="InputAccept" value="&Accept.label;" accesskey="&Accept.accesskey;"/> + <textbox id="InputAccept"/> + </row> + </rows> + </grid> + </groupbox> + + <!-- from EdDialogOverlay --> + <hbox flex="1" style="margin-top: 0.2em"> + <!-- This will right-align the button --> + <spacer flex="1"/> + <deck id="AdvancedEditDeck"> + <button id="AdvancedEditButton" + oncommand="onAdvancedEdit();" + label="&AdvancedEditButton.label;" + accesskey="&AdvancedEditButton.accessKey;" + tooltiptext="&AdvancedEditButton.tooltip;"/> + <button label="&ImageProperties.label;" accesskey="&ImageProperties.accesskey;" oncommand="doImageProperties();"/> + </deck> + </hbox> + <separator class="groove"/> + +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdInsSrc.js b/comm/suite/editor/components/dialogs/content/EdInsSrc.js new file mode 100644 index 0000000000..0f0304ef1e --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdInsSrc.js @@ -0,0 +1,160 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Insert Source HTML dialog */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +var gFullDataStrings = new Map(); +var gShortDataStrings = new Map(); +var gListenerAttached = false; + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + let editor = GetCurrentEditor(); + if (!editor) { + window.close(); + return; + } + + document.documentElement.getButton("accept").removeAttribute("default"); + + // Create dialog object to store controls for easy access + gDialog.srcInput = document.getElementById("srcInput"); + + // Attach a paste listener so we can detect pasted data URIs we need to shorten. + gDialog.srcInput.addEventListener("paste", onPaste); + + let selection; + try { + selection = editor.outputToString( + "text/html", + kOutputFormatted | kOutputSelectionOnly | kOutputWrap + ); + } catch (e) {} + if (selection) { + selection = selection.replace(/<body[^>]*>/, "").replace(/<\/body>/, ""); + + // Shorten data URIs for display. + selection = replaceDataURIs(selection); + + if (selection) { + gDialog.srcInput.value = selection; + } + } + // Set initial focus + gDialog.srcInput.focus(); + SetWindowLocation(); +} + +function replaceDataURIs(input) { + return input.replace(/(data:.+;base64,)([^"' >]+)/gi, function( + match, + nonDataPart, + dataPart + ) { + if (gShortDataStrings.has(dataPart)) { + // We found the exact same data URI, just return the shortened URI. + return nonDataPart + gShortDataStrings.get(dataPart); + } + + let l = 5; + let key; + // Normally we insert the ellipsis after five characters but if it's not unique + // we include more data. + do { + key = dataPart.substr(0, l) + "…" + dataPart.substr(dataPart.length - 10); + l++; + } while (gFullDataStrings.has(key) && l < dataPart.length - 10); + gFullDataStrings.set(key, dataPart); + gShortDataStrings.set(dataPart, key); + + // Attach listeners. In case anyone copies/cuts from the HTML window, + // we want to restore the data URI on the clipboard. + if (!gListenerAttached) { + gDialog.srcInput.addEventListener("copy", onCopyOrCut); + gDialog.srcInput.addEventListener("cut", onCopyOrCut); + gListenerAttached = true; + } + + return nonDataPart + key; + }); +} + +function onCopyOrCut(event) { + let startPos = gDialog.srcInput.selectionStart; + if (startPos == undefined) { + return; + } + let endPos = gDialog.srcInput.selectionEnd; + let clipboard = gDialog.srcInput.value.substring(startPos, endPos); + + // Add back the original data URIs we stashed away earlier. + clipboard = clipboard.replace(/(data:.+;base64,)([^"' >]+)/gi, function( + match, + nonDataPart, + key + ) { + if (!gFullDataStrings.has(key)) { + // User changed data URI. + return match; + } + return nonDataPart + gFullDataStrings.get(key); + }); + event.clipboardData.setData("text/plain", clipboard); + if (event.type == "cut") { + // We have to cut the selection manually. + gDialog.srcInput.value = + gDialog.srcInput.value.substr(0, startPos) + + gDialog.srcInput.value.substr(endPos); + } + event.preventDefault(); +} + +function onPaste(event) { + let startPos = gDialog.srcInput.selectionStart; + if (startPos == undefined) { + return; + } + let endPos = gDialog.srcInput.selectionEnd; + let clipboard = event.clipboardData.getData("text/plain"); + + // We do out own paste by replacing the selection with the pre-processed + // clipboard data. + gDialog.srcInput.value = + gDialog.srcInput.value.substr(0, startPos) + + replaceDataURIs(clipboard) + + gDialog.srcInput.value.substr(endPos); + event.preventDefault(); +} + +function onAccept(event) { + let html = gDialog.srcInput.value; + if (!html) { + event.preventDefault(); + return; + } + + // Add back the original data URIs we stashed away earlier. + html = html.replace(/(data:.+;base64,)([^"' >]+)/gi, function( + match, + nonDataPart, + key + ) { + if (!gFullDataStrings.has(key)) { + // User changed data URI. + return match; + } + return nonDataPart + gFullDataStrings.get(key); + }); + + try { + GetCurrentEditor().insertHTML(html); + } catch (e) {} + SaveWindowLocation(); +} diff --git a/comm/suite/editor/components/dialogs/content/EdInsSrc.xhtml b/comm/suite/editor/components/dialogs/content/EdInsSrc.xhtml new file mode 100644 index 0000000000..32b89ebefd --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdInsSrc.xhtml @@ -0,0 +1,42 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorInsertSource.dtd"> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + onload = "Startup()" + buttonlabelaccept="&insertButton.label;" + buttonaccesskeyaccept="&insertButton.accesskey;"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://global/content/globalOverlay.js"/> + <script src="chrome://global/content/editMenuOverlay.js"/> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdInsSrc.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <label id="srcMessage" value="&sourceEditField.label;"/> + <vbox flex="1" style="width: 30em; height: 20em;"> + <html:textarea id="srcInput" rows="18" flex="1"/> + </vbox> + <!-- Will this accept the embedded HTML tags? --> + <hbox> + <spacer class="bigspacer"/> + <label value="&example.label;"/> + <label class="bold" value="&exampleOpenTag.label;"/> + <label class="bold italic" value="&exampleText.label;"/> + <label class="bold" value="&exampleCloseTag.label;"/> + </hbox> + <spacer class="spacer"/> + <separator class="groove"/> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdInsertChars.js b/comm/suite/editor/components/dialogs/content/EdInsertChars.js new file mode 100644 index 0000000000..6ee88afcdd --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdInsertChars.js @@ -0,0 +1,409 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +// ------------------------------------------------------------------ +// From Unicode 3.0 Page 54. 3.11 Conjoining Jamo Behavior +var SBase = 0xac00; +var LBase = 0x1100; +var VBase = 0x1161; +var TBase = 0x11a7; +var LCount = 19; +var VCount = 21; +var TCount = 28; +var NCount = VCount * TCount; +// End of Unicode 3.0 + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onClose); + +// dialog initialization code +function Startup() { + if (!GetCurrentEditor()) { + window.close(); + return; + } + + StartupLatin(); + + // Set a variable on the opener window so we + // can track ownership of close this window with it + window.opener.InsertCharWindow = window; + window.sizeToContent(); + + SetWindowLocation(); +} + +function onAccept(event) { + // Insert the character + try { + GetCurrentEditor().insertText(LatinM.label); + } catch (e) {} + + // Set persistent attributes to save + // which category, letter, and character modifier was used + CategoryGroup.setAttribute("category", category); + CategoryGroup.setAttribute("letter_index", indexL); + CategoryGroup.setAttribute("char_index", indexM); + + // Don't close the dialog + event.preventDefault(); +} + +// Don't allow inserting in HTML Source Mode +function onFocus() { + var enable = true; + if ("gEditorDisplayMode" in window.opener) { + enable = !window.opener.IsInHTMLSourceMode(); + } + + SetElementEnabled(document.documentElement.getButton("accept"), enable); +} + +function onClose() { + window.opener.InsertCharWindow = null; + SaveWindowLocation(); +} + +// ------------------------------------------------------------------ +var LatinL; +var LatinM; +var LatinL_Label; +var LatinM_Label; +var indexL = 0; +var indexM = 0; +var indexM_AU = 0; +var indexM_AL = 0; +var indexM_U = 0; +var indexM_L = 0; +var indexM_S = 0; +var LItems = 0; +var category; +var CategoryGroup; +var initialize = true; + +function StartupLatin() { + LatinL = document.getElementById("LatinL"); + LatinM = document.getElementById("LatinM"); + LatinL_Label = document.getElementById("LatinL_Label"); + LatinM_Label = document.getElementById("LatinM_Label"); + + var Symbol = document.getElementById("Symbol"); + var AccentUpper = document.getElementById("AccentUpper"); + var AccentLower = document.getElementById("AccentLower"); + var Upper = document.getElementById("Upper"); + var Lower = document.getElementById("Lower"); + CategoryGroup = document.getElementById("CatGrp"); + + // Initialize which radio button is set from persistent attribute... + var category = CategoryGroup.getAttribute("category"); + + // ...as well as indexes into the letter and character lists + var index = Number(CategoryGroup.getAttribute("letter_index")); + if (index && index >= 0) { + indexL = index; + } + index = Number(CategoryGroup.getAttribute("char_index")); + if (index && index >= 0) { + indexM = index; + } + + switch (category) { + case "AccentUpper": // Uppercase Diacritical + CategoryGroup.selectedItem = AccentUpper; + indexM_AU = indexM; + break; + case "AccentLower": // Lowercase Diacritical + CategoryGroup.selectedItem = AccentLower; + indexM_AL = indexM; + break; + case "Upper": // Uppercase w/o Diacritical + CategoryGroup.selectedItem = Upper; + indexM_U = indexM; + break; + case "Lower": // Lowercase w/o Diacritical + CategoryGroup.selectedItem = Lower; + indexM_L = indexM; + break; + default: + category = "Symbol"; + CategoryGroup.selectedItem = Symbol; + indexM_S = indexM; + break; + } + + ChangeCategory(category); + initialize = false; +} + +function ChangeCategory(newCategory) { + if (category != newCategory || initialize) { + category = newCategory; + // Note: Must do L before M to set LatinL.selectedIndex + UpdateLatinL(); + UpdateLatinM(); + UpdateCharacter(); + } +} + +function SelectLatinLetter() { + if (LatinL.selectedIndex != indexL) { + indexL = LatinL.selectedIndex; + UpdateLatinM(); + UpdateCharacter(); + } +} + +function SelectLatinModifier() { + if (LatinM.selectedIndex != indexM) { + indexM = LatinM.selectedIndex; + UpdateCharacter(); + } +} +function DisableLatinL(disable) { + if (disable) { + LatinL_Label.setAttribute("disabled", "true"); + LatinL.setAttribute("disabled", "true"); + } else { + LatinL_Label.removeAttribute("disabled"); + LatinL.removeAttribute("disabled"); + } +} + +function UpdateLatinL() { + LatinL.removeAllItems(); + if (category == "AccentUpper" || category == "AccentLower") { + DisableLatinL(false); + // No Q or q + var alphabet = + category == "AccentUpper" + ? "ABCDEFGHIJKLMNOPRSTUVWXYZ" + : "abcdefghijklmnoprstuvwxyz"; + for (var letter = 0; letter < alphabet.length; letter++) { + LatinL.appendItem(alphabet.charAt(letter)); + } + + LatinL.selectedIndex = indexL; + } else { + // Other categories don't hinge on a "letter" + DisableLatinL(true); + // Note: don't change the indexL so it can be used next time + } +} + +function UpdateLatinM() { + LatinM.removeAllItems(); + var i, accent; + switch (category) { + case "AccentUpper": // Uppercase Diacritical + accent = upper[indexL]; + for (i = 0; i < accent.length; i++) { + LatinM.appendItem(accent.charAt(i)); + } + + if (indexM_AU < accent.length) { + indexM = indexM_AU; + } else { + indexM = accent.length - 1; + } + indexM_AU = indexM; + break; + + case "AccentLower": // Lowercase Diacritical + accent = lower[indexL]; + for (i = 0; i < accent.length; i++) { + LatinM.appendItem(accent.charAt(i)); + } + + if (indexM_AL < accent.length) { + indexM = indexM_AL; + } else { + indexM = lower[indexL].length - 1; + } + indexM_AL = indexM; + break; + + case "Upper": // Uppercase w/o Diacritical + for (i = 0; i < otherupper.length; i++) { + LatinM.appendItem(otherupper.charAt(i)); + } + + if (indexM_U < otherupper.length) { + indexM = indexM_U; + } else { + indexM = otherupper.length - 1; + } + indexM_U = indexM; + break; + + case "Lower": // Lowercase w/o Diacritical + for (i = 0; i < otherlower.length; i++) { + LatinM.appendItem(otherlower.charAt(i)); + } + + if (indexM_L < otherlower.length) { + indexM = indexM_L; + } else { + indexM = otherlower.length - 1; + } + indexM_L = indexM; + break; + + case "Symbol": // Symbol + for (i = 0; i < symbol.length; i++) { + LatinM.appendItem(symbol.charAt(i)); + } + + if (indexM_S < symbol.length) { + indexM = indexM_S; + } else { + indexM = symbol.length - 1; + } + indexM_S = indexM; + break; + } + LatinM.selectedIndex = indexM; +} + +function UpdateCharacter() { + indexM = LatinM.selectedIndex; + + switch (category) { + case "AccentUpper": // Uppercase Diacritical + indexM_AU = indexM; + break; + case "AccentLower": // Lowercase Diacritical + indexM_AL = indexM; + break; + case "Upper": // Uppercase w/o Diacritical + indexM_U = indexM; + break; + case "Lower": // Lowercase w/o Diacritical + indexM_L = indexM; + break; + case "Symbol": + indexM_S = indexM; + break; + } + // dump("Letter Index="+indexL+", Character Index="+indexM+", Character = "+LatinM.label+"\n"); +} + +const upper = [ + // A + "\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u0100\u0102\u0104\u01cd\u01de\u01de\u01e0\u01fa\u0200\u0202\u0226\u1e00\u1ea0\u1ea2\u1ea4\u1ea6\u1ea8\u1eaa\u1eac\u1eae\u1eb0\u1eb2\u1eb4\u1eb6", + // B + "\u0181\u0182\u0184\u1e02\u1e04\u1e06", + // C + "\u00c7\u0106\u0108\u010a\u010c\u0187\u1e08", + // D + "\u010e\u0110\u0189\u018a\u1e0a\u1e0c\u1e0e\u1e10\u1e12", + // E + "\u00C8\u00C9\u00CA\u00CB\u0112\u0114\u0116\u0118\u011A\u0204\u0206\u0228\u1e14\u1e16\u1e18\u1e1a\u1e1c\u1eb8\u1eba\u1ebc\u1ebe\u1ec0\u1ec2\u1ec4\u1ec6", + // F + "\u1e1e", + // G + "\u011c\u011E\u0120\u0122\u01e4\u01e6\u01f4\u1e20", + // H + "\u0124\u0126\u021e\u1e22\u1e24\u1e26\u1e28\u1e2a", + // I + "\u00CC\u00CD\u00CE\u00CF\u0128\u012a\u012C\u012e\u0130\u0208\u020a\u1e2c\u1e2e\u1ec8\u1eca", + // J + "\u0134\u01f0", + // K + "\u0136\u0198\u01e8\u1e30\u1e32\u1e34", + // L + "\u0139\u013B\u013D\u013F\u0141\u1e36\u1e38\u1e3a\u1e3c", + // M + "\u1e3e\u1e40\u1e42", + // N + "\u00D1\u0143\u0145\u0147\u014A\u01F8\u1e44\u1e46\u1e48\u1e4a", + // O + "\u00D2\u00D3\u00D4\u00D5\u00D6\u014C\u014E\u0150\u01ea\u01ec\u020c\u020e\u022A\u022C\u022E\u0230\u1e4c\u1e4e\u1e50\u1e52\u1ecc\u1ece\u1ed0\u1ed2\u1ed4\u1ed6\u1ed8\u1eda\u1edc\u1ede\u1ee0\u1ee2", + // P + "\u1e54\u1e56", + // No Q + // R + "\u0154\u0156\u0158\u0210\u0212\u1e58\u1e5a\u1e5c\u1e5e", + // S + "\u015A\u015C\u015E\u0160\u0218\u1e60\u1e62\u1e64\u1e66\u1e68", + // T + "\u0162\u0164\u0166\u021A\u1e6a\u1e6c\u1e6e\u1e70", + // U + "\u00D9\u00DA\u00DB\u00DC\u0168\u016A\u016C\u016E\u0170\u0172\u0214\u0216\u1e72\u1e74\u1e76\u1e78\u1e7a\u1ee4\u1ee6\u1ee8\u1eea\u1eec\u1eee\u1ef0", + // V + "\u1e7c\u1e7e", + // W + "\u0174\u1e80\u1e82\u1e84\u1e86\u1e88", + // X + "\u1e8a\u1e8c", + // Y + "\u00DD\u0176\u0178\u0232\u1e8e\u1ef2\u1ef4\u1ef6\u1ef8", + // Z + "\u0179\u017B\u017D\u0224\u1e90\u1e92\u1e94", +]; + +const lower = [ + // a + "\u00e0\u00e1\u00e2\u00e3\u00e4\u00e5\u0101\u0103\u0105\u01ce\u01df\u01e1\u01fb\u0201\u0203\u0227\u1e01\u1e9a\u1ea1\u1ea3\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7", + // b + "\u0180\u0183\u0185\u1e03\u1e05\u1e07", + // c + "\u00e7\u0107\u0109\u010b\u010d\u0188\u1e09", + // d + "\u010f\u0111\u1e0b\u1e0d\u1e0f\u1e11\u1e13", + // e + "\u00e8\u00e9\u00ea\u00eb\u0113\u0115\u0117\u0119\u011b\u0205\u0207\u0229\u1e15\u1e17\u1e19\u1e1b\u1e1d\u1eb9\u1ebb\u1ebd\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7", + // f + "\u1e1f", + // g + "\u011d\u011f\u0121\u0123\u01e5\u01e7\u01f5\u1e21", + // h + "\u0125\u0127\u021f\u1e23\u1e25\u1e27\u1e29\u1e2b\u1e96", + // i + "\u00ec\u00ed\u00ee\u00ef\u0129\u012b\u012d\u012f\u0131\u01d0\u0209\u020b\u1e2d\u1e2f\u1ec9\u1ecb", + // j + "\u0135", + // k + "\u0137\u0138\u01e9\u1e31\u1e33\u1e35", + // l + "\u013a\u013c\u013e\u0140\u0142\u1e37\u1e39\u1e3b\u1e3d", + // m + "\u1e3f\u1e41\u1e43", + // n + "\u00f1\u0144\u0146\u0148\u0149\u014b\u01f9\u1e45\u1e47\u1e49\u1e4b", + // o + "\u00f2\u00f3\u00f4\u00f5\u00f6\u014d\u014f\u0151\u01d2\u01eb\u01ed\u020d\u020e\u022b\u022d\u022f\u0231\u1e4d\u1e4f\u1e51\u1e53\u1ecd\u1ecf\u1ed1\u1ed3\u1ed5\u1ed7\u1ed9\u1edb\u1edd\u1edf\u1ee1\u1ee3", + // p + "\u1e55\u1e57", + // No q + // r + "\u0155\u0157\u0159\u0211\u0213\u1e59\u1e5b\u1e5d\u1e5f", + // s + "\u015b\u015d\u015f\u0161\u0219\u1e61\u1e63\u1e65\u1e67\u1e69", + // t + "\u0162\u0163\u0165\u0167\u021b\u1e6b\u1e6d\u1e6f\u1e71\u1e97", + // u + "\u00f9\u00fa\u00fb\u00fc\u0169\u016b\u016d\u016f\u0171\u0173\u01d4\u01d6\u01d8\u01da\u01dc\u0215\u0217\u1e73\u1e75\u1e77\u1e79\u1e7b\u1ee5\u1ee7\u1ee9\u1eeb\u1eed\u1eef\u1ef1", + // v + "\u1e7d\u1e7f", + // w + "\u0175\u1e81\u1e83\u1e85\u1e87\u1e89\u1e98", + // x + "\u1e8b\u1e8d", + // y + "\u00fd\u00ff\u0177\u0233\u1e8f\u1e99\u1ef3\u1ef5\u1ef7\u1ef9", + // z + "\u017a\u017c\u017e\u0225\u1e91\u1e93\u1e95", +]; + +const symbol = + "\u00a1\u00a2\u00a3\u00a4\u00a5\u20ac\u00a6\u00a7\u00a8\u00a9\u00aa\u00ab\u00ac\u00ae\u00af\u00b0\u00b1\u00b2\u00b3\u00b4\u00b5\u00b6\u00b7\u00b8\u00b9\u00ba\u00bb\u00bc\u00bd\u00be\u00bf\u00d7\u00f7"; + +const otherupper = + "\u00c6\u00d0\u00d8\u00de\u0132\u0152\u0186\u01c4\u01c5\u01c7\u01c8\u01ca\u01cb\u01F1\u01f2"; + +const otherlower = + "\u00e6\u00f0\u00f8\u00fe\u00df\u0133\u0153\u01c6\u01c9\u01cc\u01f3"; diff --git a/comm/suite/editor/components/dialogs/content/EdInsertChars.xhtml b/comm/suite/editor/components/dialogs/content/EdInsertChars.xhtml new file mode 100644 index 0000000000..4e6c1020fa --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdInsertChars.xhtml @@ -0,0 +1,55 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorInsertChars.dtd"> + +<dialog id="insertCharsDlg" title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload = "Startup()" + onfocus = "onFocus()" + buttonlabelaccept="&insertButton.label;" + buttonlabelcancel="&closeButton.label;" + style = "width: 20em"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdInsertChars.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <groupbox> + <hbox class="groupbox-title"> + <label class="header">&category.label;</label> + </hbox> + <radiogroup id="CatGrp" persist="category letter_index char_index"> + <radio id="AccentUpper" label="&accentUpper.label;" oncommand="ChangeCategory(this.id)"/> + <radio id="AccentLower" label="&accentLower.label;" oncommand="ChangeCategory(this.id)"/> + <radio id="Upper" label="&otherUpper.label;" oncommand="ChangeCategory(this.id)"/> + <radio id="Lower" label="&otherLower.label;" oncommand="ChangeCategory(this.id)"/> + <radio id="Symbol" label="&commonSymbols.label;" oncommand="ChangeCategory(this.id)"/> + </radiogroup> + <spacer class="spacer"/> + </groupbox> + <hbox equalsize="always"> + <vbox flex="1"> + <!-- value is set in JS from editor.properties strings --> + <label id="LatinL_Label" control="LatinL" value="&letter.label;" accesskey="&letter.accessKey;"/> + <menulist class="larger" flex="1" id="LatinL" oncommand="SelectLatinLetter()"> + <menupopup/> + </menulist> + </vbox> + <vbox flex="1"> + <label id="LatinM_Label" control="LatinM" value="&character.label;" accesskey="&character.accessKey;"/> + <menulist class="larger" flex="1" id="LatinM" oncommand="SelectLatinModifier()"> + <menupopup/> + </menulist> + </vbox> + </hbox> + <separator class="groove"/> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdInsertMath.js b/comm/suite/editor/components/dialogs/content/EdInsertMath.js new file mode 100644 index 0000000000..c99bf8edac --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdInsertMath.js @@ -0,0 +1,330 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Insert MathML dialog */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + var editor = GetCurrentEditor(); + if (!editor) { + window.close(); + return; + } + + // Create dialog object for easy access + gDialog.accept = document.documentElement.getButton("accept"); + gDialog.mode = document.getElementById("optionMode"); + gDialog.direction = document.getElementById("optionDirection"); + gDialog.input = document.getElementById("input"); + gDialog.output = document.getElementById("output"); + gDialog.tabbox = document.getElementById("tabboxInsertLaTeXCommand"); + + // Set initial focus + gDialog.input.focus(); + + // Load TeXZilla + // TeXZilla.js contains non-ASCII characters and explicitly sets + // window.TeXZilla, so we have to specify the charset parameter but don't + // need to worry about the targetObj parameter. + /* globals TeXZilla */ + Services.scriptloader.loadSubScript( + "chrome://editor/content/TeXZilla.js", + {}, + "UTF-8" + ); + + // Verify if the selection is on a <math> and initialize the dialog. + gDialog.oldMath = editor.getElementOrParentByTagName("math", null); + if (gDialog.oldMath) { + // When these attributes are absent or invalid, they default to "inline" and "ltr" respectively. + gDialog.mode.selectedIndex = + gDialog.oldMath.getAttribute("display") == "block" ? 1 : 0; + gDialog.direction.selectedIndex = + gDialog.oldMath.getAttribute("dir") == "rtl" ? 1 : 0; + gDialog.input.value = TeXZilla.getTeXSource(gDialog.oldMath); + } + + // Create the tabbox with LaTeX commands. + createCommandPanel({ + "√⅗²": [ + "{⋯}^{⋯}", + "{⋯}_{⋯}", + "{⋯}_{⋯}^{⋯}", + "\\underset{⋯}{⋯}", + "\\overset{⋯}{⋯}", + "\\underoverset{⋯}{⋯}{⋯}", + "\\left(⋯\\right)", + "\\left[⋯\\right]", + "\\frac{⋯}{⋯}", + "\\binom{⋯}{⋯}", + "\\sqrt{⋯}", + "\\sqrt[⋯]{⋯}", + "\\cos\\left({⋯}\\right)", + "\\sin\\left({⋯}\\right)", + "\\tan\\left({⋯}\\right)", + "\\exp\\left({⋯}\\right)", + "\\ln\\left({⋯}\\right)", + "\\underbrace{⋯}", + "\\underline{⋯}", + "\\overbrace{⋯}", + "\\widevec{⋯}", + "\\widetilde{⋯}", + "\\widehat{⋯}", + "\\widecheck{⋯}", + "\\widebar{⋯}", + "\\dot{⋯}", + "\\ddot{⋯}", + "\\boxed{⋯}", + "\\slash{⋯}", + ], + "(▦)": [ + "\\begin{matrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{matrix}", + "\\begin{pmatrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{pmatrix}", + "\\begin{bmatrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{bmatrix}", + "\\begin{Bmatrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{Bmatrix}", + "\\begin{vmatrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{vmatrix}", + "\\begin{Vmatrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{Vmatrix}", + "\\begin{cases} ⋯ \\\\ ⋯ \\end{cases}", + "\\begin{aligned} ⋯ &= ⋯ \\\\ ⋯ &= ⋯ \\end{aligned}", + ], + }); + createSymbolPanels([ + "∏∐∑∫∬∭⨌∮⊎⊕⊖⊗⊘⊙⋀⋁⋂⋃⌈⌉⌊⌋⎰⎱⟨⟩⟪⟫∥⫼⨀⨁⨂⨄⨅⨆ðıȷℏℑℓ℘ℜℵℶ", + "∀∃∄∅∉∊∋∌⊂⊃⊄⊅⊆⊇⊈⊈⊉⊊⊊⊋⊋⊏⊐⊑⊒⊓⊔⊥⋐⋑⋔⫅⫆⫋⫋⫌⫌…⋮⋯⋰⋱♭♮♯∂∇", + "±×÷†‡•∓∔∗∘∝∠∡∢∧∨∴∵∼∽≁≃≅≇≈≈≊≍≎≏≐≑≒≓≖≗≜≡≢≬⊚⊛⊞⊡⊢⊣⊤⊥", + "⊨⊩⊪⊫⊬⊭⊯⊲⊲⊳⊴⊵⊸⊻⋄⋅⋇⋈⋉⋊⋋⋌⋍⋎⋏⋒⋓⌅⌆⌣△▴▵▸▹▽▾▿◂◃◊○★♠♡♢♣⧫", + "≦≧≨≩≩≪≫≮≯≰≱≲≳≶≷≺≻≼≽≾≿⊀⊁⋖⋗⋘⋙⋚⋛⋞⋟⋦⋧⋨⋩⩽⩾⪅⪆⪇⪈⪉⪊⪋⪌⪕⪯⪰⪷⪸⪹⪺", + "←↑→↓↔↕↖↗↘↙↜↝↞↠↢↣↦↩↪↫↬↭↭↰↱↼↽↾↿⇀⇁⇂⇃⇄⇆⇇⇈⇉⇊⇋⇌⇐⇑⇒⇓⇕⇖⇗⇘⇙⟺", + "αβγδϵ϶εζηθϑικϰλμνξℴπϖρϱσςτυϕφχψωΓΔΘΛΞΠΣϒΦΨΩϝ℧", + "𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ", + "𝒶𝒷𝒸𝒹ℯ𝒻ℊ𝒽𝒾𝒿𝓀𝓁𝓂𝓃ℴ𝓅𝓆𝓇𝓈𝓉𝓊𝓋𝓌𝓍𝓎𝓏𝒜ℬ𝒞𝒟ℰℱ𝒢ℋℐ𝒥𝒦ℒℳ𝒩𝒪𝒫𝒬ℛ𝒮𝒯𝒰𝒱𝒲𝒳𝒴𝒵", + "𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ", + ]); + gDialog.tabbox.selectedIndex = 0; + + updateMath(); + + SetWindowLocation(); +} + +function insertLaTeXCommand(aButton) { + gDialog.input.focus(); + + // For a single math symbol, just use the insertText command. + if (aButton.label) { + gDialog.input.editor.insertText(aButton.label); + return; + } + + // Otherwise, it's a LaTeX command with at least one argument... + var latex = TeXZilla.getTeXSource(aButton.firstChild); + var selectionStart = gDialog.input.selectionStart; + var selectionEnd = gDialog.input.selectionEnd; + + // If the selection is not empty, we replace the first argument of the LaTeX + // command with the current selection. + var selection = gDialog.input.value.substring(selectionStart, selectionEnd); + if (selection != "") { + latex = latex.replace("⋯", selection); + } + + // Try and move to the next position. + var latexNewStart = latex.indexOf("⋯"), + latexNewEnd; + if (latexNewStart == -1) { + // This is a unary function and the selection was used as an argument above. + // We select the expression again so that one can choose to apply further + // command to it or just move the caret after that text. + latexNewStart = 0; + latexNewEnd = latex.length; + } else { + // Otherwise, select the dots representing the next argument. + latexNewEnd = latexNewStart + 1; + } + + // Update the input text and selection. + gDialog.input.editor.insertText(latex); + gDialog.input.setSelectionRange( + selectionStart + latexNewStart, + selectionStart + latexNewEnd + ); + + updateMath(); +} + +function createCommandPanel(aCommandPanelList) { + const columnCount = 10; + + for (var label in aCommandPanelList) { + var commands = aCommandPanelList[label]; + + // Create a <rows> element with some LaTeX commands. + var rows = document.createXULElement("rows"); + + var i = 0, + row; + for (var command of commands) { + if (i % columnCount == 0) { + // Create a new row. + row = document.createXULElement("row"); + rows.appendChild(row); + } + + // Create a new button to insert the symbol. + var button = document.createXULElement("toolbarbutton"); + button.setAttribute("class", "tabbable"); + button.appendChild(TeXZilla.toMathML(command)); + row.appendChild(button); + + i++; + } + + // Create a <columns> element with the desired number of columns. + var columns = document.createXULElement("columns"); + for (i = 0; i < columnCount; i++) { + var column = document.createXULElement("column"); + column.setAttribute("flex", "1"); + columns.appendChild(column); + } + + // Create the <grid> element with the <rows> and <columns> children. + var grid = document.createXULElement("grid"); + grid.appendChild(columns); + grid.appendChild(rows); + + // Create a new <tab> element. + var tab = document.createXULElement("tab"); + tab.setAttribute("label", label); + gDialog.tabbox.tabs.appendChild(tab); + + // Append the new tab panel. + gDialog.tabbox.tabpanels.appendChild(grid); + } +} + +function createSymbolPanels(aSymbolPanelList) { + const columnCount = 13, + tabLabelLength = 3; + + for (var symbols of aSymbolPanelList) { + // Create a <rows> element with the symbols of the i-th panel. + var rows = document.createXULElement("rows"); + var i = 0, + tabLabel = "", + row; + for (var symbol of symbols) { + if (i % columnCount == 0) { + // Create a new row. + row = document.createXULElement("row"); + rows.appendChild(row); + } + + // Build the tab label from the first symbols of this tab. + if (i < tabLabelLength) { + tabLabel += symbol; + } + + // Create a new button to insert the symbol. + var button = document.createXULElement("toolbarbutton"); + button.setAttribute("label", symbol); + button.setAttribute("class", "tabbable"); + row.appendChild(button); + + i++; + } + + // Create a <columns> element with the desired number of columns. + var columns = document.createXULElement("columns"); + for (i = 0; i < columnCount; i++) { + var column = document.createXULElement("column"); + column.setAttribute("flex", "1"); + columns.appendChild(column); + } + + // Create the <grid> element with the <rows> and <columns> children. + var grid = document.createXULElement("grid"); + grid.appendChild(columns); + grid.appendChild(rows); + + // Create a new <tab> element with the label determined above. + var tab = document.createXULElement("tab"); + tab.setAttribute("label", tabLabel); + gDialog.tabbox.tabs.appendChild(tab); + + // Append the new tab panel. + gDialog.tabbox.tabpanels.appendChild(grid); + } +} + +function onAccept(event) { + if (gDialog.output.firstChild) { + var editor = GetCurrentEditor(); + editor.beginTransaction(); + + try { + var newMath = editor.document.importNode(gDialog.output.firstChild, true); + if (gDialog.oldMath) { + // Replace the old <math> element with the new one. + editor.selectElement(gDialog.oldMath); + editor.insertElementAtSelection(newMath, true); + } else { + // Insert the new <math> element. + editor.insertElementAtSelection(newMath, false); + } + } catch (e) {} + + editor.endTransaction(); + } else { + dump("Null value -- not inserting in MathML Source dialog\n"); + event.preventDefault(); + } + SaveWindowLocation(); +} + +function updateMath() { + // Remove the preview, if any. + if (gDialog.output.firstChild) { + gDialog.output.firstChild.remove(); + } + + // Try to convert the LaTeX source into MathML using TeXZilla. + // We use the placeholder text if no input is provided. + try { + var input = gDialog.input.value || gDialog.input.placeholder; + var newMath = TeXZilla.toMathML( + input, + gDialog.mode.selectedIndex, + gDialog.direction.selectedIndex, + true + ); + gDialog.output.appendChild(document.importNode(newMath, true)); + gDialog.output.style.opacity = gDialog.input.value ? 1 : 0.5; + } catch (e) {} + // Disable the accept button if parsing fails or when the placeholder is used. + gDialog.accept.disabled = !gDialog.input.value || !gDialog.output.firstChild; +} + +function updateMode() { + if (gDialog.output.firstChild) { + gDialog.output.firstChild.setAttribute( + "display", + gDialog.mode.selectedIndex ? "block" : "inline" + ); + } +} + +function updateDirection() { + if (gDialog.output.firstChild) { + gDialog.output.firstChild.setAttribute( + "dir", + gDialog.direction.selectedIndex ? "rtl" : "ltr" + ); + } +} diff --git a/comm/suite/editor/components/dialogs/content/EdInsertMath.xhtml b/comm/suite/editor/components/dialogs/content/EdInsertMath.xhtml new file mode 100644 index 0000000000..9138d00846 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdInsertMath.xhtml @@ -0,0 +1,60 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public +- License, v. 2.0. If a copy of the MPL was not distributed with this +- file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorInsertMath.dtd"> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup();" + buttonlabelaccept="&insertButton.label;" + buttonaccesskeyaccept="&insertButton.accesskey;"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://global/content/globalOverlay.js"/> + <script src="chrome://global/content/editMenuOverlay.js"/> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdInsertMath.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <label id="srcMessage" value="&sourceEditField.label;"/> + <html:textarea id="input" rows="5" oninput="updateMath();" + placeholder="\sqrt{x_1} + \frac{π^3}{2}"/> + <vbox flex="1" style="overflow: auto; width: 30em; height: 5em;"> + <description id="output"/> + </vbox> + <tabbox id="tabboxInsertLaTeXCommand"> + <tabs/> + <tabpanels oncommand="insertLaTeXCommand(event.target);"/> + </tabbox> + <spacer class="spacer"/> + <groupbox> + <hbox class="groupbox-title"> + <label class="header">&options.label;</label> + </hbox> + <hbox> + <radiogroup id="optionMode" oncommand="updateMode();"> + <radio label="&optionInline.label;" + accesskey="&optionInline.accesskey;"/> + <radio label="&optionDisplay.label;" + accesskey="&optionDisplay.accesskey;"/> + </radiogroup> + <radiogroup id="optionDirection" oncommand="updateDirection();"> + <radio label="&optionLTR.label;" + accesskey="&optionLTR.accesskey;"/> + <radio label="&optionRTL.label;" + accesskey="&optionRTL.accesskey;"/> + </radiogroup> + </hbox> + </groupbox> + <spacer class="spacer"/> + <separator class="groove"/> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdInsertTOC.js b/comm/suite/editor/components/dialogs/content/EdInsertTOC.js new file mode 100644 index 0000000000..3ec386f7c9 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdInsertTOC.js @@ -0,0 +1,378 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +// tocHeadersArray is the array containing the pairs tag/class +// defining TOC entries +var tocHeadersArray = new Array(6); + +// a global used when building the TOC +var currentHeaderLevel = 0; + +// a global set to true if the TOC is to be readonly +var readonly = false; + +// a global set to true if user wants indexes in the TOC +var orderedList = true; + +// constants +const kMozToc = "mozToc"; +const kMozTocLength = 6; +const kMozTocIdPrefix = "mozTocId"; +const kMozTocIdPrefixLength = 8; +const kMozTocClassPrefix = "mozToc"; +const kMozTocClassPrefixLength = 6; + +document.addEventListener("dialogaccept", () => BuildTOC(true)); + +// Startup() is called when EdInsertTOC.xhtml is opened +function Startup() { + // early way out if if we have no editor + if (!GetCurrentEditor()) { + window.close(); + return; + } + + var i; + // clean the table of tag/class pairs we look for + for (i = 0; i < 6; ++i) { + tocHeadersArray[i] = ["", ""]; + } + + // reset all settings + for (i = 1; i < 7; ++i) { + var menulist = document.getElementById("header" + i + "Menulist"); + var menuitem = document.getElementById("header" + i + "none"); + var textbox = document.getElementById("header" + i + "Class"); + menulist.selectedItem = menuitem; + textbox.setAttribute("disabled", "true"); + } + + var theDocument = GetCurrentEditor().document; + + // do we already have a TOC in the document ? It should have "mozToc" ID + var toc = theDocument.getElementById(kMozToc); + + // default TOC definition, use h1-h6 for TOC entry levels 1-6 + var headers = "h1 1 h2 2 h3 3 h4 4 h5 5 h6 6"; + + var orderedListCheckbox = document.getElementById("orderedListCheckbox"); + orderedListCheckbox.checked = true; + + if (toc) { + // man, there is already a TOC here + + if (toc.getAttribute("class") == "readonly") { + // and it's readonly + var checkbox = document.getElementById("readOnlyCheckbox"); + checkbox.checked = true; + readonly = true; + } + + // let's see if it's an OL or an UL + orderedList = toc.nodeName.toLowerCase() == "ol"; + orderedListCheckbox.checked = orderedList; + + var nodeList = toc.childNodes; + // let's look at the children of the TOC ; if we find a comment beginning + // with "mozToc", it contains the TOC definition + for (i = 0; i < nodeList.length; ++i) { + if ( + nodeList.item(i).nodeType == Node.COMMENT_NODE && + nodeList.item(i).data.startsWith(kMozToc) + ) { + // yep, there is already a definition here; parse it ! + headers = nodeList + .item(i) + .data.substr( + kMozTocLength + 1, + nodeList.item(i).length - kMozTocLength - 1 + ); + break; + } + } + } + + // let's get an array filled with the (tag.class, index level) pairs + var headersArray = headers.split(" "); + + for (i = 0; i < headersArray.length; i += 2) { + var tag = headersArray[i], + className = ""; + var index = headersArray[i + 1]; + menulist = document.getElementById("header" + index + "Menulist"); + if (menulist) { + var sep = tag.indexOf("."); + if (sep != -1) { + // the tag variable contains in fact "tag.className", let's parse + // the class and get the real tag name + var tmp = tag.substr(0, sep); + className = tag.substr(sep + 1, tag.length - sep - 1); + tag = tmp; + } + + // update the dialog + menuitem = document.getElementById("header" + index + tag.toUpperCase()); + textbox = document.getElementById("header" + index + "Class"); + menulist.selectedItem = menuitem; + if (tag != "") { + textbox.removeAttribute("disabled"); + } + if (className != "") { + textbox.value = className; + } + tocHeadersArray[index - 1] = [tag, className]; + } + } +} + +function BuildTOC(update) { + // controlClass() is a node filter that accepts a node if + // (a) we don't look for a class (b) we look for a class and + // node has it + function controlClass(node, index) { + currentHeaderLevel = index + 1; + if (tocHeadersArray[index][1] == "") { + // we are not looking for a specific class, this node is ok + return NodeFilter.FILTER_ACCEPT; + } + if (node.getAttribute("class")) { + // yep, we look for a class, let's look at all the classes + // the node has + var classArray = node.getAttribute("class").split(" "); + for (var j = 0; j < classArray.length; j++) { + if (classArray[j] == tocHeadersArray[index][1]) { + // hehe, we found it... + return NodeFilter.FILTER_ACCEPT; + } + } + } + return NodeFilter.FILTER_SKIP; + } + + // the main node filter for our node iterator + // it selects the tag names as specified in the dialog + // then calls the controlClass filter above + function acceptNode(node) { + switch (node.nodeName.toLowerCase()) { + case tocHeadersArray[0][0]: + return controlClass(node, 0); + case tocHeadersArray[1][0]: + return controlClass(node, 1); + case tocHeadersArray[2][0]: + return controlClass(node, 2); + case tocHeadersArray[3][0]: + return controlClass(node, 3); + case tocHeadersArray[4][0]: + return controlClass(node, 4); + case tocHeadersArray[5][0]: + return controlClass(node, 5); + default: + return NodeFilter.FILTER_SKIP; + } + } + + var editor = GetCurrentEditor(); + var theDocument = editor.document; + // let's create a TreeWalker to look for our nodes + var treeWalker = theDocument.createTreeWalker( + theDocument.documentElement, + NodeFilter.SHOW_ELEMENT, + acceptNode, + true + ); + // we need an array to store all TOC entries we find in the document + var tocArray = []; + if (treeWalker) { + var tocSourceNode = treeWalker.nextNode(); + while (tocSourceNode) { + var headerIndex = currentHeaderLevel; + + // we have a node, we need to get all its textual contents + var textTreeWalker = theDocument.createTreeWalker( + tocSourceNode, + NodeFilter.SHOW_TEXT, + null, + true + ); + var textNode = textTreeWalker.nextNode(), + headerText = ""; + while (textNode) { + headerText += textNode.data; + textNode = textTreeWalker.nextNode(); + } + + var anchor = tocSourceNode.firstChild, + id; + // do we have a named anchor as 1st child of our node ? + if ( + anchor.nodeName.toLowerCase() == "a" && + anchor.hasAttribute("name") && + anchor.getAttribute("name").startsWith(kMozTocIdPrefix) + ) { + // yep, get its name + id = anchor.getAttribute("name"); + } else { + // no we don't and we need to create one + anchor = theDocument.createElement("a"); + tocSourceNode.insertBefore(anchor, tocSourceNode.firstChild); + // let's give it a random ID + var c = 1000000 * Math.random(); + id = kMozTocIdPrefix + Math.round(c); + anchor.setAttribute("name", id); + anchor.setAttribute( + "class", + kMozTocClassPrefix + tocSourceNode.nodeName.toUpperCase() + ); + } + // and store that new entry in our array + tocArray.push(headerIndex, headerText, id); + tocSourceNode = treeWalker.nextNode(); + } + } + + /* generate the TOC itself */ + headerIndex = 0; + var item, toc; + for (var i = 0; i < tocArray.length; i += 3) { + if (!headerIndex) { + // do we need to create an ol/ul container for the first entry ? + ++headerIndex; + toc = theDocument.getElementById(kMozToc); + if (!toc || !update) { + // we need to create a list container for the table of contents + toc = GetCurrentEditor().createElementWithDefaults( + orderedList ? "ol" : "ul" + ); + // grrr, we need to create a LI inside the list otherwise + // Composer will refuse an empty list and will remove it ! + var pit = theDocument.createElement("li"); + toc.appendChild(pit); + GetCurrentEditor().insertElementAtSelection(toc, true); + // ah, now it's inserted so let's remove the useless list item... + toc.removeChild(pit); + // we need to recognize later that this list is our TOC + toc.setAttribute("id", kMozToc); + } else if (orderedList != (toc.nodeName.toLowerCase() == "ol")) { + // we have to update an existing TOC, is the existing TOC of the + // desired type (ordered or not) ? + + // nope, we have to recreate the list + var newToc = GetCurrentEditor().createElementWithDefaults( + orderedList ? "ol" : "ul" + ); + toc.parentNode.insertBefore(newToc, toc); + // and remove the old one + toc.remove(); + toc = newToc; + toc.setAttribute("id", kMozToc); + } else { + // we can keep the list itself but let's get rid of the TOC entries + while (toc.hasChildNodes()) { + toc.lastChild.remove(); + } + } + + var commentText = "mozToc "; + for (var j = 0; j < 6; j++) { + if (tocHeadersArray[j][0] != "") { + commentText += tocHeadersArray[j][0]; + if (tocHeadersArray[j][1] != "") { + commentText += "." + tocHeadersArray[j][1]; + } + commentText += " " + (j + 1) + " "; + } + } + // important, we have to remove trailing spaces + commentText = TrimStringRight(commentText); + + // forge a comment we'll insert in the TOC ; that comment will hold + // the TOC definition for us + var ct = theDocument.createComment(commentText); + toc.appendChild(ct); + + // assign a special class to the TOC top element if the TOC is readonly + // the definition of this class is in EditorOverride.css + if (readonly) { + toc.setAttribute("class", "readonly"); + } else { + toc.removeAttribute("class"); + } + + // We need a new variable to hold the local ul/ol container + // The toplevel TOC element is not the parent element of a + // TOC entry if its depth is > 1... + var tocList = toc; + // create a list item + var tocItem = theDocument.createElement("li"); + // and an anchor in this list item + var tocAnchor = theDocument.createElement("a"); + // make it target the source of the TOC entry + tocAnchor.setAttribute("href", "#" + tocArray[i + 2]); + // and put the textual contents of the TOC entry in that anchor + var tocEntry = theDocument.createTextNode(tocArray[i + 1]); + // now, insert everything where it has to be inserted + tocAnchor.appendChild(tocEntry); + tocItem.appendChild(tocAnchor); + tocList.appendChild(tocItem); + item = tocList; + } else { + if (tocArray[i] < headerIndex) { + // if the depth of the new TOC entry is less than the depth of the + // last entry we created, find the good ul/ol ancestor + for (j = headerIndex - tocArray[i]; j > 0; --j) { + if (item != toc) { + item = item.parentNode.parentNode; + } + } + tocItem = theDocument.createElement("li"); + } else if (tocArray[i] > headerIndex) { + // to the contrary, it's deeper than the last one + // we need to create sub ul/ol's and li's + for (j = tocArray[i] - headerIndex; j > 0; --j) { + tocList = theDocument.createElement(orderedList ? "ol" : "ul"); + item.lastChild.appendChild(tocList); + tocItem = theDocument.createElement("li"); + tocList.appendChild(tocItem); + item = tocList; + } + } else { + tocItem = theDocument.createElement("li"); + } + tocAnchor = theDocument.createElement("a"); + tocAnchor.setAttribute("href", "#" + tocArray[i + 2]); + tocEntry = theDocument.createTextNode(tocArray[i + 1]); + tocAnchor.appendChild(tocEntry); + tocItem.appendChild(tocAnchor); + item.appendChild(tocItem); + headerIndex = tocArray[i]; + } + } + SaveWindowLocation(); +} + +function selectHeader(elt, index) { + var tag = elt.value; + tocHeadersArray[index - 1][0] = tag; + var textbox = document.getElementById("header" + index + "Class"); + if (tag == "") { + textbox.setAttribute("disabled", "true"); + } else { + textbox.removeAttribute("disabled"); + } +} + +function changeClass(elt, index) { + tocHeadersArray[index - 1][1] = elt.value; +} + +function ToggleReadOnlyToc(elt) { + readonly = elt.checked; +} + +function ToggleOrderedList(elt) { + orderedList = elt.checked; +} diff --git a/comm/suite/editor/components/dialogs/content/EdInsertTOC.xhtml b/comm/suite/editor/components/dialogs/content/EdInsertTOC.xhtml new file mode 100644 index 0000000000..8d82bac046 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdInsertTOC.xhtml @@ -0,0 +1,225 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorInsertTOC.dtd"> + +<dialog title="&Window.title;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup();" + oncancel="window.close(); return true;"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdInsertTOC.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + <spacer id="dummy" style="display:none"/> + <vbox flex="1"> + <groupbox> + <hbox class="groupbox-title"> + <label class="header">&buildToc.label;</label> + </hbox> + <grid> + <columns><column/><column style="min-width: 6em"/><column/></columns> + <rows> + <row align="center"> + <spacer/> + <label value="&tag.label;"/> + <label value="&class.label;"/> + </row> + <row align="center"> + <label value="&header1.label;"/> + <menulist id="header1Menulist"> + <menupopup> + <menuitem id="header1none" label="--" value="" + oncommand="selectHeader(this, 1)"/> + <menuseparator/> + <menuitem id="header1H1" label="h1" value="h1" + oncommand="selectHeader(this, 1)"/> + <menuitem id="header1H2" label="h2" value="h2" + oncommand="selectHeader(this, 1)"/> + <menuitem id="header1H3" label="h3" value="h3" + oncommand="selectHeader(this, 1)"/> + <menuitem id="header1H4" label="h4" value="h4" + oncommand="selectHeader(this, 1)"/> + <menuitem id="header1H5" label="h5" value="h5" + oncommand="selectHeader(this, 1)"/> + <menuitem id="header1H6" label="h6" value="h6" + oncommand="selectHeader(this, 1)"/> + <menuitem id="header1DIV" label="div" value="div" + oncommand="selectHeader(this, 1)"/> + <menuitem id="header1P" label="p" value="p" + oncommand="selectHeader(this, 1)"/> + </menupopup> + </menulist> + <textbox id="header1Class" size="10" + oninput="changeClass(this, 1)"/> + </row> + + <row align="center"> + <label value="&header2.label;"/> + <menulist id="header2Menulist"> + <menupopup> + <menuitem id="header2none" label="--" value="" + oncommand="selectHeader(this, 2)"/> + <menuseparator/> + <menuitem id="header2H1" label="h1" value="h1" + oncommand="selectHeader(this, 2)"/> + <menuitem id="header2H2" label="h2" value="h2" + oncommand="selectHeader(this, 2)"/> + <menuitem id="header2H3" label="h3" value="h3" + oncommand="selectHeader(this, 2)"/> + <menuitem id="header2H4" label="h4" value="h4" + oncommand="selectHeader(this, 2)"/> + <menuitem id="header2H5" label="h5" value="h5" + oncommand="selectHeader(this, 2)"/> + <menuitem id="header2H6" label="h6" value="h6" + oncommand="selectHeader(this, 2)"/> + <menuitem id="header2DIV" label="div" value="div" + oncommand="selectHeader(this, 2)"/> + <menuitem id="header2P" label="p" value="p" + oncommand="selectHeader(this, 2)"/> + </menupopup> + </menulist> + <textbox id="header2Class" size="10" + oninput="changeClass(this, 2)"/> + </row> + + <row align="center"> + <label value="&header3.label;"/> + <menulist id="header3Menulist"> + <menupopup> + <menuitem id="header3none" label="--" value="" + oncommand="selectHeader(this, 3)"/> + <menuseparator/> + <menuitem id="header3H1" label="h1" value="h1" + oncommand="selectHeader(this, 3)"/> + <menuitem id="header3H2" label="h2" value="h2" + oncommand="selectHeader(this, 3)"/> + <menuitem id="header3H3" label="h3" value="h3" + oncommand="selectHeader(this, 3)"/> + <menuitem id="header3H4" label="h4" value="h4" + oncommand="selectHeader(this, 3)"/> + <menuitem id="header3H5" label="h5" value="h5" + oncommand="selectHeader(this, 3)"/> + <menuitem id="header3H6" label="h6" value="h6" + oncommand="selectHeader(this, 3)"/> + <menuitem id="header3DIV" label="div" value="div" + oncommand="selectHeader(this, 3)"/> + <menuitem id="header3P" label="p" value="p" + oncommand="selectHeader(this, 3)"/> + </menupopup> + </menulist> + <textbox id="header3Class" size="10" + oninput="changeClass(this, 3)"/> + </row> + + <row align="center"> + <label value="&header4.label;"/> + <menulist id="header4Menulist"> + <menupopup> + <menuitem id="header4none" label="--" value="" + oncommand="selectHeader(this, 4)"/> + <menuseparator/> + <menuitem id="header4H1" label="h1" value="h1" + oncommand="selectHeader(this, 4)"/> + <menuitem id="header4H2" label="h2" value="h2" + oncommand="selectHeader(this, 4)"/> + <menuitem id="header4H3" label="h3" value="h3" + oncommand="selectHeader(this, 4)"/> + <menuitem id="header4H4" label="h4" value="h4" + oncommand="selectHeader(this, 4)"/> + <menuitem id="header4H5" label="h5" value="h5" + oncommand="selectHeader(this, 4)"/> + <menuitem id="header4H6" label="h6" value="h6" + oncommand="selectHeader(this, 4)"/> + <menuitem id="header4DIV" label="div" value="div" + oncommand="selectHeader(this, 4)"/> + <menuitem id="header4P" label="p" value="p" + oncommand="selectHeader(this, 4)"/> + </menupopup> + </menulist> + <textbox id="header4Class" size="10" + oninput="changeClass(this, 4)"/> + </row> + + <row align="center"> + <label value="&header5.label;"/> + <menulist id="header5Menulist"> + <menupopup> + <menuitem id="header5none" label="--" value="" + oncommand="selectHeader(this, 5)"/> + <menuseparator/> + <menuitem id="header5H1" label="h1" value="h1" + oncommand="selectHeader(this, 5)"/> + <menuitem id="header5H2" label="h2" value="h2" + oncommand="selectHeader(this, 5)"/> + <menuitem id="header5H3" label="h3" value="h3" + oncommand="selectHeader(this, 5)"/> + <menuitem id="header5H4" label="h4" value="h4" + oncommand="selectHeader(this, 5)"/> + <menuitem id="header5H5" label="h5" value="h5" + oncommand="selectHeader(this, 5)"/> + <menuitem id="header5H6" label="h6" value="h6" + oncommand="selectHeader(this, 5)"/> + <menuitem id="header5DIV" label="div" value="div" + oncommand="selectHeader(this, 5)"/> + <menuitem id="header5P" label="p" value="p" + oncommand="selectHeader(this, 5)"/> + </menupopup> + </menulist> + <textbox id="header5Class" size="10" + oninput="changeClass(this, 5)"/> + </row> + + <row align="center"> + <label value="&header6.label;"/> + <menulist id="header6Menulist"> + <menupopup> + <menuitem id="header6none" label="--" value="" + oncommand="selectHeader(this, 6)"/> + <menuseparator/> + <menuitem id="header6H1" label="h1" value="h1" + oncommand="selectHeader(this, 6)"/> + <menuitem id="header6H2" label="h2" value="h2" + oncommand="selectHeader(this, 6)"/> + <menuitem id="header6H3" label="h3" value="h3" + oncommand="selectHeader(this, 6)"/> + <menuitem id="header6H4" label="h4" value="h4" + oncommand="selectHeader(this, 6)"/> + <menuitem id="header6H5" label="h5" value="h5" + oncommand="selectHeader(this, 6)"/> + <menuitem id="header6H6" label="h6" value="h6" + oncommand="selectHeader(this, 6)"/> + <menuitem id="header6DIV" label="div" value="div" + oncommand="selectHeader(this, 6)"/> + <menuitem id="header6P" label="p" value="p" + oncommand="selectHeader(this, 6)"/> + </menupopup> + </menulist> + <textbox id="header6Class" size="10" + oninput="changeClass(this, 6)"/> + </row> + </rows> + </grid> + </groupbox> + <vbox> + <checkbox id="orderedListCheckbox" + label="&orderedList.label;" + oncommand="ToggleOrderedList(this)"/> + <checkbox id="readOnlyCheckbox" + label="&makeReadOnly.label;" + oncommand="ToggleReadOnlyToc(this)"/> + </vbox> + <separator class="groove"/> + </vbox> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdInsertTable.js b/comm/suite/editor/components/dialogs/content/EdInsertTable.js new file mode 100644 index 0000000000..0053f9fd94 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdInsertTable.js @@ -0,0 +1,254 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +// Cancel() is in EdDialogCommon.js + +var gTableElement = null; +var gRows; +var gColumns; +var gActiveEditor; + +// dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + gActiveEditor = GetCurrentTableEditor(); + if (!gActiveEditor) { + dump("Failed to get active editor!\n"); + window.close(); + return; + } + + try { + gTableElement = gActiveEditor.createElementWithDefaults("table"); + } catch (e) {} + + if (!gTableElement) { + dump("Failed to create a new table!\n"); + window.close(); + return; + } + gDialog.rowsInput = document.getElementById("rowsInput"); + gDialog.columnsInput = document.getElementById("columnsInput"); + gDialog.widthInput = document.getElementById("widthInput"); + gDialog.borderInput = document.getElementById("borderInput"); + gDialog.widthPixelOrPercentMenulist = document.getElementById( + "widthPixelOrPercentMenulist" + ); + gDialog.OkButton = document.documentElement.getButton("accept"); + + // Make a copy to use for AdvancedEdit + globalElement = gTableElement.cloneNode(false); + try { + if ( + Services.prefs.getBoolPref("editor.use_css") && + IsHTMLEditor() && + !(gActiveEditor.flags & Ci.nsIEditor.eEditorMailMask) + ) { + // only for Composer and not for htmlmail + globalElement.setAttribute("style", "text-align: left;"); + } + } catch (e) {} + + // Initialize all widgets with image attributes + InitDialog(); + + // Set initial number to 2 rows, 2 columns: + // Note, these are not attributes on the table, + // so don't put them in InitDialog(), + // else the user's values will be trashed when they use + // the Advanced Edit dialog + gDialog.rowsInput.value = 2; + gDialog.columnsInput.value = 2; + + // If no default value on the width, set to 100% + if (gDialog.widthInput.value.length == 0) { + gDialog.widthInput.value = "100"; + gDialog.widthPixelOrPercentMenulist.selectedIndex = 1; + } + + SetTextboxFocusById("rowsInput"); + + SetWindowLocation(); +} + +// Set dialog widgets with attribute data +// We get them from globalElement copy so this can be used +// by AdvancedEdit(), which is shared by all property dialogs +function InitDialog() { + // Get default attributes set on the created table: + // Get the width attribute of the element, stripping out "%" + // This sets contents of menu combobox list + // 2nd param = null: Use current selection to find if parent is table cell or window + gDialog.widthInput.value = InitPixelOrPercentMenulist( + globalElement, + null, + "width", + "widthPixelOrPercentMenulist", + gPercent + ); + gDialog.borderInput.value = globalElement.getAttribute("border"); +} + +function ChangeRowOrColumn(id) { + // Allow only integers + forceInteger(id); + + // Enable OK only if both rows and columns have a value > 0 + var enable = + gDialog.rowsInput.value.length > 0 && + gDialog.rowsInput.value > 0 && + gDialog.columnsInput.value.length > 0 && + gDialog.columnsInput.value > 0; + + SetElementEnabled(gDialog.OkButton, enable); + SetElementEnabledById("AdvancedEditButton1", enable); +} + +// Get and validate data from widgets. +// Set attributes on globalElement so they can be accessed by AdvancedEdit() +function ValidateData() { + gRows = ValidateNumber( + gDialog.rowsInput, + null, + 1, + gMaxRows, + null, + null, + true + ); + if (gValidationError) { + return false; + } + + gColumns = ValidateNumber( + gDialog.columnsInput, + null, + 1, + gMaxColumns, + null, + null, + true + ); + if (gValidationError) { + return false; + } + + // Set attributes: NOTE: These may be empty strings (last param = false) + ValidateNumber( + gDialog.borderInput, + null, + 0, + gMaxPixels, + globalElement, + "border", + false + ); + // TODO: Deal with "BORDER" without value issue + if (gValidationError) { + return false; + } + + ValidateNumber( + gDialog.widthInput, + gDialog.widthPixelOrPercentMenulist, + 1, + gMaxTableSize, + globalElement, + "width", + false + ); + if (gValidationError) { + return false; + } + + return true; +} + +function onAccept(event) { + if (ValidateData()) { + gActiveEditor.beginTransaction(); + try { + gActiveEditor.cloneAttributes(gTableElement, globalElement); + + // Create necessary rows and cells for the table + var tableBody = gActiveEditor.createElementWithDefaults("tbody"); + if (tableBody) { + gTableElement.appendChild(tableBody); + + // Create necessary rows and cells for the table + for (var i = 0; i < gRows; i++) { + var newRow = gActiveEditor.createElementWithDefaults("tr"); + if (newRow) { + tableBody.appendChild(newRow); + for (var j = 0; j < gColumns; j++) { + var newCell = gActiveEditor.createElementWithDefaults("td"); + if (newCell) { + newRow.appendChild(newCell); + } + } + } + } + } + // Detect when entire cells are selected: + // Get number of cells selected + var tagNameObj = { value: "" }; + var countObj = { value: 0 }; + var element = gActiveEditor.getSelectedOrParentTableElement( + tagNameObj, + countObj + ); + var deletePlaceholder = false; + + if (tagNameObj.value == "table") { + // Replace entire selected table with new table, so delete the table + gActiveEditor.deleteTable(); + } else if (tagNameObj.value == "td") { + if (countObj.value >= 1) { + if (countObj.value > 1) { + // Assume user wants to replace a block of + // contiguous cells with a table, so + // join the selected cells + gActiveEditor.joinTableCells(false); + + // Get the cell everything was merged into + element = gActiveEditor.getFirstSelectedCell(); + + // Collapse selection into just that cell + gActiveEditor.selection.collapse(element, 0); + } + + if (element) { + // Empty just the contents of the cell + gActiveEditor.deleteTableCellContents(); + + // Collapse selection to start of empty cell... + gActiveEditor.selection.collapse(element, 0); + // ...but it will contain a <br> placeholder + deletePlaceholder = true; + } + } + } + + // true means delete selection when inserting + gActiveEditor.insertElementAtSelection(gTableElement, true); + + if (deletePlaceholder && gTableElement && gTableElement.nextSibling) { + // Delete the placeholder <br> + gActiveEditor.deleteNode(gTableElement.nextSibling); + } + } catch (e) {} + + gActiveEditor.endTransaction(); + + SaveWindowLocation(); + return; + } + event.preventDefault(); +} diff --git a/comm/suite/editor/components/dialogs/content/EdInsertTable.xhtml b/comm/suite/editor/components/dialogs/content/EdInsertTable.xhtml new file mode 100644 index 0000000000..b376d5f0be --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdInsertTable.xhtml @@ -0,0 +1,82 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % edInsertTable SYSTEM "chrome://editor/locale/EditorInsertTable.dtd"> +%edInsertTable; +<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd"> +%edDialogOverlay; +]> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload = "Startup()"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdInsertTable.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + <groupbox> + <hbox class="groupbox-title"> + <label class="header">&size.label;</label> + </hbox> + <grid> + <columns> + <column flex="1"/> + <column flex="1"/> + <column flex="6"/> + </columns> + <rows> + <row align="center"> + <label control="rowsInput" class="align-right" + value="&numRowsEditField.label;" + accesskey="&numRowsEditField.accessKey;"/> + <textbox class="narrow" id="rowsInput" oninput="ChangeRowOrColumn(this.id)" /> + <spacer/> + </row> + <row align="center"> + <label control="columnsInput" class="align-right" + value="&numColumnsEditField.label;" + accesskey="&numColumnsEditField.accessKey;"/> + <textbox class="narrow" id="columnsInput" oninput="ChangeRowOrColumn(this.id)" /> + <spacer/> + </row> + <row align="center"> + <label control="widthInput" class="align-right" + value="&widthEditField.label;" + accesskey="&widthEditField.accessKey;"/> + <textbox class="narrow" id="widthInput" oninput="forceInteger(this.id)" /> + <menulist id="widthPixelOrPercentMenulist" flex="1"/> + <!-- child elements are appended by JS --> + </row> + </rows> + </grid> + <spacer class="spacer"/> + </groupbox> + <spacer class="spacer"/> + <hbox align="center"> + <label control="borderInput" class="align-right" + value="&borderEditField.label;" + accesskey="&borderEditField.accessKey;" + tooltiptext="&borderEditField.tooltip;" /> + <textbox class="narrow" id="borderInput" oninput="forceInteger(this.id)" /> + <label value="&pixels.label;"/> + </hbox> + <vbox id="AdvancedEdit"> + <hbox flex="1" style="margin-top: 0.2em" align="center"> + <!-- This will right-align the button --> + <spacer flex="1"/> + <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;" + accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/> + </hbox> + <separator id="advancedSeparator" class="groove"/> + </vbox> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdLabelProps.js b/comm/suite/editor/components/dialogs/content/EdLabelProps.js new file mode 100644 index 0000000000..ec96878ea6 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdLabelProps.js @@ -0,0 +1,118 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +var labelElement; + +// dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + var editor = GetCurrentEditor(); + if (!editor) { + dump("Failed to get active editor!\n"); + window.close(); + return; + } + + gDialog.editText = document.getElementById("EditText"); + gDialog.labelText = document.getElementById("LabelText"); + gDialog.labelFor = document.getElementById("LabelFor"); + gDialog.labelAccessKey = document.getElementById("LabelAccessKey"); + + labelElement = window.arguments[0]; + + // Make a copy to use for AdvancedEdit + globalElement = labelElement.cloneNode(false); + + InitDialog(); + + var range = editor.document.createRange(); + range.selectNode(labelElement); + gDialog.labelText.value = range.toString(); + + if (labelElement.innerHTML.includes("<")) { + gDialog.editText.checked = false; + gDialog.editText.disabled = false; + gDialog.labelText.disabled = true; + gDialog.editText.addEventListener( + "command", + () => + Services.prompt.alert( + window, + GetString("Alert"), + GetString("EditTextWarning") + ), + { capture: false, once: true } + ); + SetTextboxFocus(gDialog.labelFor); + } else { + SetTextboxFocus(gDialog.labelText); + } + + SetWindowLocation(); +} + +function InitDialog() { + gDialog.labelFor.value = globalElement.getAttribute("for"); + gDialog.labelAccessKey.value = globalElement.getAttribute("accesskey"); +} + +function RemoveLabel() { + RemoveContainer(labelElement); + SaveWindowLocation(); + window.close(); +} + +function ValidateData() { + if (gDialog.labelFor.value) { + globalElement.setAttribute("for", gDialog.labelFor.value); + } else { + globalElement.removeAttribute("for"); + } + if (gDialog.labelAccessKey.value) { + globalElement.setAttribute("accesskey", gDialog.labelAccessKey.value); + } else { + globalElement.removeAttribute("accesskey"); + } + return true; +} + +function onAccept() { + // All values are valid - copy to actual element in doc + ValidateData(); + + var editor = GetCurrentEditor(); + + editor.beginTransaction(); + + try { + if (gDialog.editText.checked) { + editor.setShouldTxnSetSelection(false); + + while (labelElement.firstChild) { + editor.deleteNode(labelElement.firstChild); + } + if (gDialog.labelText.value) { + editor.insertNode( + editor.document.createTextNode(gDialog.labelText.value), + labelElement, + 0 + ); + } + + editor.setShouldTxnSetSelection(true); + } + + editor.cloneAttributes(labelElement, globalElement); + } catch (e) {} + + editor.endTransaction(); + + SaveWindowLocation(); +} diff --git a/comm/suite/editor/components/dialogs/content/EdLabelProps.xhtml b/comm/suite/editor/components/dialogs/content/EdLabelProps.xhtml new file mode 100644 index 0000000000..21d964f1fd --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdLabelProps.xhtml @@ -0,0 +1,66 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % edLabelProperties SYSTEM "chrome://editor/locale/EditorLabelProperties.dtd"> +%edLabelProperties; +<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd"> +%edDialogOverlay; +]> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup();" + buttons="accept,cancel"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdLabelProps.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <groupbox> + <hbox class="groupbox-title"> + <label class="header" accesskey="&Settings.accesskey;">&Settings.label;</label> + </hbox> + <grid><columns><column/><column/></columns> + <rows> + <row align="center"> + <checkbox id="EditText" label="&EditLabelText.label;" accesskey="&EditLabelText.accesskey;" checked="true" disabled="true" + oncommand="gDialog.labelText.disabled = !gDialog.editText.checked;"/> + <textbox id="LabelText" accesskey="&Settings.accesskey;"/> + </row> + <row align="center"> + <label control="LabelFor" value="&LabelFor.label;" accesskey="&LabelFor.accesskey;"/> + <textbox id="LabelFor"/> + </row> + <row align="center"> + <label control="LabelAccessKey" value="&AccessKey.label;" accesskey="&AccessKey.accesskey;"/> + <hbox> + <textbox id="LabelAccessKey" class="narrow"/> + </hbox> + </row> + </rows> + </grid> + </groupbox> + + <!-- from EdDialogOverlay --> + <hbox flex="1" style="margin-top: 0.2em"> + <button id="RemoveLabel" label="&RemoveLabel.label;" accesskey="&RemoveLabel.accesskey;" oncommand="RemoveLabel();"/> + <!-- This will right-align the button --> + <spacer flex="1"/> + <button id="AdvancedEditButton" + oncommand="onAdvancedEdit();" + label="&AdvancedEditButton.label;" + accesskey="&AdvancedEditButton.accessKey;" + tooltiptext="&AdvancedEditButton.tooltip;"/> + </hbox> + <separator class="groove"/> + +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdLinkProps.js b/comm/suite/editor/components/dialogs/content/EdLinkProps.js new file mode 100644 index 0000000000..6504496a51 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdLinkProps.js @@ -0,0 +1,331 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +var gActiveEditor; +var anchorElement = null; +var imageElement = null; +var insertNew = false; +var replaceExistingLink = false; +var insertLinkAtCaret; +var needLinkText = false; +var href; +var newLinkText; +var gHNodeArray = {}; +var gHaveNamedAnchors = false; +var gHaveHeadings = false; +var gCanChangeHeadingSelected = true; +var gCanChangeAnchorSelected = true; + +// NOTE: Use "href" instead of "a" to distinguish from Named Anchor +// The returned node is has an "a" tagName +var tagName = "href"; + +// dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + gActiveEditor = GetCurrentEditor(); + if (!gActiveEditor) { + dump("Failed to get active editor!\n"); + window.close(); + return; + } + // Message was wrapped in a <label> or <div>, so actual text is a child text node + gDialog.linkTextCaption = document.getElementById("linkTextCaption"); + gDialog.linkTextMessage = document.getElementById("linkTextMessage"); + gDialog.linkTextInput = document.getElementById("linkTextInput"); + gDialog.hrefInput = document.getElementById("hrefInput"); + gDialog.makeRelativeLink = document.getElementById("MakeRelativeLink"); + gDialog.AdvancedEditSection = document.getElementById("AdvancedEdit"); + + // See if we have a single selected image + imageElement = gActiveEditor.getSelectedElement("img"); + + if (imageElement) { + // Get the parent link if it exists -- more efficient than GetSelectedElement() + anchorElement = gActiveEditor.getElementOrParentByTagName( + "href", + imageElement + ); + if (anchorElement) { + if (anchorElement.childNodes.length > 1) { + // If there are other children, then we want to break + // this image away by inserting a new link around it, + // so make a new node and copy existing attributes + anchorElement = anchorElement.cloneNode(false); + // insertNew = true; + replaceExistingLink = true; + } + } + } else { + // Get an anchor element if caret or + // entire selection is within the link. + anchorElement = gActiveEditor.getSelectedElement(tagName); + + if (anchorElement) { + // Select the entire link + gActiveEditor.selectElement(anchorElement); + } else { + // If selection starts in a link, but extends beyond it, + // the user probably wants to extend existing link to new selection, + // so check if either end of selection is within a link + // POTENTIAL PROBLEM: This prevents user from selecting text in an existing + // link and making 2 links. + // Note that this isn't a problem with images, handled above + + anchorElement = gActiveEditor.getElementOrParentByTagName( + "href", + gActiveEditor.selection.anchorNode + ); + if (!anchorElement) { + anchorElement = gActiveEditor.getElementOrParentByTagName( + "href", + gActiveEditor.selection.focusNode + ); + } + + if (anchorElement) { + // But clone it for reinserting/merging around existing + // link that only partially overlaps the selection + anchorElement = anchorElement.cloneNode(false); + // insertNew = true; + replaceExistingLink = true; + } + } + } + + if (!anchorElement) { + // No existing link -- create a new one + anchorElement = gActiveEditor.createElementWithDefaults(tagName); + insertNew = true; + // Hide message about removing existing link + // document.getElementById("RemoveLinkMsg").hidden = true; + } + if (!anchorElement) { + dump("Failed to get selected element or create a new one!\n"); + window.close(); + return; + } + + // We insert at caret only when nothing is selected + insertLinkAtCaret = gActiveEditor.selection.isCollapsed; + + var selectedText; + if (insertLinkAtCaret) { + // Groupbox caption: + gDialog.linkTextCaption.setAttribute("label", GetString("LinkText")); + + // Message above input field: + gDialog.linkTextMessage.setAttribute("value", GetString("EnterLinkText")); + gDialog.linkTextMessage.setAttribute( + "accesskey", + GetString("EnterLinkTextAccessKey") + ); + } else { + if (!imageElement) { + // We get here if selection is exactly around a link node + // Check if selection has some text - use that first + selectedText = GetSelectionAsText(); + if (!selectedText) { + // No text, look for first image in the selection + var children = anchorElement.childNodes; + if (children) { + for (var i = 0; i < children.length; i++) { + var nodeName = children.item(i).nodeName.toLowerCase(); + if (nodeName == "img") { + imageElement = children.item(i); + break; + } + } + } + } + } + // Set "caption" for link source and the source text or image URL + if (imageElement) { + gDialog.linkTextCaption.setAttribute("label", GetString("LinkImage")); + // Link source string is the source URL of image + // TODO: THIS DOESN'T HANDLE MULTIPLE SELECTED IMAGES! + gDialog.linkTextMessage.setAttribute("value", imageElement.src); + } else { + gDialog.linkTextCaption.setAttribute("label", GetString("LinkText")); + if (selectedText) { + // Use just the first 60 characters and add "..." + gDialog.linkTextMessage.setAttribute( + "value", + TruncateStringAtWordEnd( + ReplaceWhitespace(selectedText, " "), + 60, + true + ) + ); + } else { + gDialog.linkTextMessage.setAttribute( + "value", + GetString("MixedSelection") + ); + } + } + } + + // Make a copy to use for AdvancedEdit and onSaveDefault + globalElement = anchorElement.cloneNode(false); + + // Get the list of existing named anchors and headings + FillLinkMenulist(gDialog.hrefInput, gHNodeArray); + + // We only need to test for this once per dialog load + gHaveDocumentUrl = GetDocumentBaseUrl(); + + // Set data for the dialog controls + InitDialog(); + + // Search for a URI pattern in the selected text + // as candidate href + selectedText = TrimString(selectedText); + if (!gDialog.hrefInput.value && TextIsURI(selectedText)) { + gDialog.hrefInput.value = selectedText; + } + + // Set initial focus + if (insertLinkAtCaret) { + // We will be using the HREF inputbox, so text message + SetTextboxFocus(gDialog.linkTextInput); + } else { + SetTextboxFocus(gDialog.hrefInput); + + // We will not insert a new link at caret, so remove link text input field + gDialog.linkTextInput.hidden = true; + gDialog.linkTextInput = null; + } + + // This sets enable state on OK button + doEnabling(); + + SetWindowLocation(); +} + +// Set dialog widgets with attribute data +// We get them from globalElement copy so this can be used +// by AdvancedEdit(), which is shared by all property dialogs +function InitDialog() { + // Must use getAttribute, not "globalElement.href", + // or foreign chars aren't converted correctly! + gDialog.hrefInput.value = globalElement.getAttribute("href"); + + // Set "Relativize" checkbox according to current URL state + SetRelativeCheckbox(gDialog.makeRelativeLink); +} + +function doEnabling() { + // We disable Ok button when there's no href text only if inserting a new link + var enable = insertNew + ? TrimString(gDialog.hrefInput.value).length > 0 + : true; + + // anon. content, so can't use SetElementEnabledById here + var dialogNode = document.getElementById("linkDlg"); + dialogNode.getButton("accept").disabled = !enable; + + SetElementEnabledById("AdvancedEditButton1", enable); +} + +function ChangeLinkLocation() { + SetRelativeCheckbox(gDialog.makeRelativeLink); + // Set OK button enable state + doEnabling(); +} + +// Get and validate data from widgets. +// Set attributes on globalElement so they can be accessed by AdvancedEdit() +function ValidateData() { + href = TrimString(gDialog.hrefInput.value); + if (href) { + // Set the HREF directly on the editor document's anchor node + // or on the newly-created node if insertNew is true + globalElement.setAttribute("href", href); + } else if (insertNew) { + // We must have a URL to insert a new link + // NOTE: We accept an empty HREF on existing link to indicate removing the link + ShowInputErrorMessage(GetString("EmptyHREFError")); + return false; + } + if (gDialog.linkTextInput) { + // The text we will insert isn't really an attribute, + // but it makes sense to validate it + newLinkText = TrimString(gDialog.linkTextInput.value); + if (!newLinkText) { + if (href) { + newLinkText = href; + } else { + ShowInputErrorMessage(GetString("EmptyLinkTextError")); + SetTextboxFocus(gDialog.linkTextInput); + return false; + } + } + } + return true; +} + +function onAccept(event) { + if (ValidateData()) { + if (href.length > 0) { + // Copy attributes to element we are changing or inserting + gActiveEditor.cloneAttributes(anchorElement, globalElement); + + // Coalesce into one undo transaction + gActiveEditor.beginTransaction(); + + // Get text to use for a new link + if (insertLinkAtCaret) { + // Append the link text as the last child node + // of the anchor node + var textNode = gActiveEditor.document.createTextNode(newLinkText); + if (textNode) { + anchorElement.appendChild(textNode); + } + try { + gActiveEditor.insertElementAtSelection(anchorElement, false); + } catch (e) { + dump("Exception occurred in InsertElementAtSelection\n"); + return; + } + } else if (insertNew || replaceExistingLink) { + // Link source was supplied by the selection, + // so insert a link node as parent of this + // (may be text, image, or other inline content) + try { + gActiveEditor.insertLinkAroundSelection(anchorElement); + } catch (e) { + dump("Exception occurred in InsertElementAtSelection\n"); + return; + } + } + // Check if the link was to a heading + if (href in gHNodeArray) { + var anchorNode = gActiveEditor.createElementWithDefaults("a"); + if (anchorNode) { + anchorNode.name = href.substr(1); + + // Insert the anchor into the document, + // but don't let the transaction change the selection + gActiveEditor.setShouldTxnSetSelection(false); + gActiveEditor.insertNode(anchorNode, gHNodeArray[href], 0); + gActiveEditor.setShouldTxnSetSelection(true); + } + } + gActiveEditor.endTransaction(); + } else if (!insertNew) { + // We already had a link, but empty HREF means remove it + EditorRemoveTextProperty("href", ""); + } + SaveWindowLocation(); + return; + } + event.preventDefault(); +} diff --git a/comm/suite/editor/components/dialogs/content/EdLinkProps.xhtml b/comm/suite/editor/components/dialogs/content/EdLinkProps.xhtml new file mode 100644 index 0000000000..04e147db4c --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdLinkProps.xhtml @@ -0,0 +1,79 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % linkPropertiesDTD SYSTEM "chrome://editor/locale/EditorLinkProperties.dtd"> +%linkPropertiesDTD; +<!ENTITY % composeEditorOverlayDTD SYSTEM "chrome://messenger/locale/messengercompose/mailComposeEditorOverlay.dtd"> +%composeEditorOverlayDTD; +<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd"> +%edDialogOverlay; +]> + +<dialog id="linkDlg" title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload = "Startup()"> + + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdLinkProps.js"/> + <script src="chrome://editor/content/EdImageLinkLoader.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <vbox style="min-width: 20em"> + <groupbox> + <hbox class="groupbox-title"> + <label id="linkTextCaption" class="header"/> + </hbox> + <vbox> + <label id="linkTextMessage" control="linkTextInput"/> + <textbox id="linkTextInput"/> + </vbox> + </groupbox> + + <groupbox id="LinkURLBox"> + <hbox class="groupbox-title"> + <label class="header">&LinkURLBox.label;</label> + </hbox> + <vbox id="LinkLocationBox"> + <label control="hrefInput" + accesskey="&LinkURLEditField2.accessKey;" + width="1">&LinkURLEditField2.label;</label> + <textbox id="hrefInput" type="text" + class="uri-element padded" oninput="ChangeLinkLocation();"/> + <hbox align="center"> + <checkbox id="MakeRelativeLink" + for="hrefInput" + label="&makeUrlRelative.label;" + accesskey="&makeUrlRelative.accessKey;" + oncommand="MakeInputValueRelativeOrAbsolute(this);" + tooltiptext="&makeUrlRelative.tooltip;"/> + <spacer flex="1"/> + <button label="&chooseFileLinkButton.label;" accesskey="&chooseFileLinkButton.accessKey;" + oncommand="chooseLinkFile();"/> + </hbox> + </vbox> + <checkbox id="AttachSourceToMail" + hidden="true" + label="&attachLinkSource.label;" + accesskey="&attachLinkSource.accesskey;" + oncommand="DoAttachSourceCheckbox()"/> + </groupbox> + </vbox> + <vbox id="AdvancedEdit"> + <hbox flex="1" style="margin-top: 0.2em" align="center"> + <!-- This will right-align the button --> + <spacer flex="1"/> + <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;" + accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/> + </hbox> + <separator id="advancedSeparator" class="groove"/> + </vbox> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdListProps.js b/comm/suite/editor/components/dialogs/content/EdListProps.js new file mode 100644 index 0000000000..8d4b78536b --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdListProps.js @@ -0,0 +1,455 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +// Cancel() is in EdDialogCommon.js +var gBulletStyleType = ""; +var gNumberStyleType = ""; +var gListElement; +var gOriginalListType = ""; +var gListType = ""; +var gMixedListSelection = false; +var gStyleType = ""; +var gOriginalStyleType = ""; +const gOnesArray = ["", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"]; +const gTensArray = ["", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"]; +const gHundredsArray = [ + "", + "C", + "CC", + "CCC", + "CD", + "D", + "DC", + "DCC", + "DCCC", + "CM", +]; +const gThousandsArray = [ + "", + "M", + "MM", + "MMM", + "MMMM", + "MMMMM", + "MMMMMM", + "MMMMMMM", + "MMMMMMMM", + "MMMMMMMMM", +]; +const gRomanDigits = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 }; +const A = "A".charCodeAt(0); +const gArabic = "1"; +const gUpperRoman = "I"; +const gLowerRoman = "i"; +const gUpperLetters = "A"; +const gLowerLetters = "a"; +const gDecimalCSS = "decimal"; +const gUpperRomanCSS = "upper-roman"; +const gLowerRomanCSS = "lower-roman"; +const gUpperAlphaCSS = "upper-alpha"; +const gLowerAlphaCSS = "lower-alpha"; + +// dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + var editor = GetCurrentEditor(); + if (!editor) { + window.close(); + return; + } + gDialog.ListTypeList = document.getElementById("ListType"); + gDialog.BulletStyleList = document.getElementById("BulletStyle"); + gDialog.BulletStyleLabel = document.getElementById("BulletStyleLabel"); + gDialog.StartingNumberInput = document.getElementById("StartingNumber"); + gDialog.StartingNumberLabel = document.getElementById("StartingNumberLabel"); + gDialog.AdvancedEditButton = document.getElementById("AdvancedEditButton1"); + gDialog.RadioGroup = document.getElementById("RadioGroup"); + gDialog.ChangeAllRadio = document.getElementById("ChangeAll"); + gDialog.ChangeSelectedRadio = document.getElementById("ChangeSelected"); + + // Try to get an existing list(s) + var mixedObj = { value: null }; + try { + gListType = editor.getListState(mixedObj, {}, {}, {}); + + // We may have mixed list and non-list, or > 1 list type in selection + gMixedListSelection = mixedObj.value; + + // Get the list element at the anchor node + gListElement = editor.getElementOrParentByTagName("list", null); + } catch (e) {} + + // The copy to use in AdvancedEdit + if (gListElement) { + globalElement = gListElement.cloneNode(false); + } + + // Show extra options for changing entire list if we have one already. + gDialog.RadioGroup.collapsed = !gListElement; + if (gListElement) { + // Radio button index is persistent + if (gDialog.RadioGroup.getAttribute("index") == "1") { + gDialog.RadioGroup.selectedItem = gDialog.ChangeSelectedRadio; + } else { + gDialog.RadioGroup.selectedItem = gDialog.ChangeAllRadio; + } + } + + InitDialog(); + + gOriginalListType = gListType; + + gDialog.ListTypeList.focus(); + + SetWindowLocation(); +} + +function InitDialog() { + // Note that if mixed, we we pay attention + // only to the anchor node's list type + // (i.e., don't confuse user with "mixed" designation) + if (gListElement) { + gListType = gListElement.nodeName.toLowerCase(); + } else { + gListType = ""; + } + + gDialog.ListTypeList.value = gListType; + gDialog.StartingNumberInput.value = ""; + + // Last param = true means attribute value is case-sensitive + var type = globalElement + ? GetHTMLOrCSSStyleValue(globalElement, "type", "list-style-type") + : null; + + if (gListType == "ul") { + if (type) { + type = type.toLowerCase(); + gBulletStyleType = type; + gOriginalStyleType = type; + } + } else if (gListType == "ol") { + // Translate CSS property strings + switch (type.toLowerCase()) { + case gDecimalCSS: + type = gArabic; + break; + case gUpperRomanCSS: + type = gUpperRoman; + break; + case gLowerRomanCSS: + type = gLowerRoman; + break; + case gUpperAlphaCSS: + type = gUpperLetters; + break; + case gLowerAlphaCSS: + type = gLowerLetters; + break; + } + if (type) { + gNumberStyleType = type; + gOriginalStyleType = type; + } + + // Convert attribute number to appropriate letter or roman numeral + gDialog.StartingNumberInput.value = ConvertStartAttrToUserString( + globalElement.getAttribute("start"), + type + ); + } + BuildBulletStyleList(); +} + +// Convert attribute number to appropriate letter or roman numeral +function ConvertStartAttrToUserString(startAttr, type) { + switch (type) { + case gUpperRoman: + startAttr = ConvertArabicToRoman(startAttr); + break; + case gLowerRoman: + startAttr = ConvertArabicToRoman(startAttr).toLowerCase(); + break; + case gUpperLetters: + startAttr = ConvertArabicToLetters(startAttr); + break; + case gLowerLetters: + startAttr = ConvertArabicToLetters(startAttr).toLowerCase(); + break; + } + return startAttr; +} + +function BuildBulletStyleList() { + gDialog.BulletStyleList.removeAllItems(); + var label; + + if (gListType == "ul") { + gDialog.BulletStyleList.removeAttribute("disabled"); + gDialog.BulletStyleLabel.removeAttribute("disabled"); + gDialog.StartingNumberInput.setAttribute("disabled", "true"); + gDialog.StartingNumberLabel.setAttribute("disabled", "true"); + + label = GetString("BulletStyle"); + + gDialog.BulletStyleList.appendItem(GetString("Automatic"), ""); + gDialog.BulletStyleList.appendItem(GetString("SolidCircle"), "disc"); + gDialog.BulletStyleList.appendItem(GetString("OpenCircle"), "circle"); + gDialog.BulletStyleList.appendItem(GetString("SolidSquare"), "square"); + + gDialog.BulletStyleList.value = gBulletStyleType; + } else if (gListType == "ol") { + gDialog.BulletStyleList.removeAttribute("disabled"); + gDialog.BulletStyleLabel.removeAttribute("disabled"); + gDialog.StartingNumberInput.removeAttribute("disabled"); + gDialog.StartingNumberLabel.removeAttribute("disabled"); + label = GetString("NumberStyle"); + + gDialog.BulletStyleList.appendItem(GetString("Automatic"), ""); + gDialog.BulletStyleList.appendItem(GetString("Style_1"), gArabic); + gDialog.BulletStyleList.appendItem(GetString("Style_I"), gUpperRoman); + gDialog.BulletStyleList.appendItem(GetString("Style_i"), gLowerRoman); + gDialog.BulletStyleList.appendItem(GetString("Style_A"), gUpperLetters); + gDialog.BulletStyleList.appendItem(GetString("Style_a"), gLowerLetters); + + gDialog.BulletStyleList.value = gNumberStyleType; + } else { + gDialog.BulletStyleList.setAttribute("disabled", "true"); + gDialog.BulletStyleLabel.setAttribute("disabled", "true"); + gDialog.StartingNumberInput.setAttribute("disabled", "true"); + gDialog.StartingNumberLabel.setAttribute("disabled", "true"); + } + + // Disable advanced edit button if changing to "normal" + if (gListType) { + gDialog.AdvancedEditButton.removeAttribute("disabled"); + } else { + gDialog.AdvancedEditButton.setAttribute("disabled", "true"); + } + + if (label) { + gDialog.BulletStyleLabel.setAttribute("label", label); + } +} + +function SelectListType() { + // Each list type is stored in the "value" of each menuitem + var NewType = gDialog.ListTypeList.value; + + if (NewType == "ol") { + SetTextboxFocus(gDialog.StartingNumberInput); + } + + if (gListType != NewType) { + gListType = NewType; + + // Create a newlist object for Advanced Editing + try { + if (gListType) { + globalElement = GetCurrentEditor().createElementWithDefaults(gListType); + } + } catch (e) {} + + BuildBulletStyleList(); + } +} + +function SelectBulletStyle() { + // Save the selected index so when user changes + // list style, restore index to associated list + // Each bullet or number type is stored in the "value" of each menuitem + if (gListType == "ul") { + gBulletStyleType = gDialog.BulletStyleList.value; + } else if (gListType == "ol") { + var type = gDialog.BulletStyleList.value; + if (gNumberStyleType != type) { + // Convert existing input value to attr number first, + // then convert to the appropriate format for the newly-selected + gDialog.StartingNumberInput.value = ConvertStartAttrToUserString( + ConvertUserStringToStartAttr(gNumberStyleType), + type + ); + + gNumberStyleType = type; + SetTextboxFocus(gDialog.StartingNumberInput); + } + } +} + +function ValidateData() { + gBulletStyleType = gDialog.BulletStyleList.value; + // globalElement should already be of the correct type + + if (globalElement) { + var editor = GetCurrentEditor(); + if (gListType == "ul") { + if (gBulletStyleType && gDialog.ChangeAllRadio.selected) { + globalElement.setAttribute("type", gBulletStyleType); + } else { + try { + editor.removeAttributeOrEquivalent(globalElement, "type", true); + } catch (e) {} + } + } else if (gListType == "ol") { + if (gBulletStyleType) { + globalElement.setAttribute("type", gBulletStyleType); + } else { + try { + editor.removeAttributeOrEquivalent(globalElement, "type", true); + } catch (e) {} + } + + var startingNumber = ConvertUserStringToStartAttr(gBulletStyleType); + if (startingNumber) { + globalElement.setAttribute("start", startingNumber); + } else { + globalElement.removeAttribute("start"); + } + } + } + return true; +} + +function ConvertUserStringToStartAttr(type) { + var startingNumber = TrimString(gDialog.StartingNumberInput.value); + + switch (type) { + case gUpperRoman: + case gLowerRoman: + // If the input isn't an integer, assume it's a roman numeral. Convert it. + if (!Number(startingNumber)) { + startingNumber = ConvertRomanToArabic(startingNumber); + } + break; + case gUpperLetters: + case gLowerLetters: + // Get the number equivalent of the letters + if (!Number(startingNumber)) { + startingNumber = ConvertLettersToArabic(startingNumber); + } + break; + } + return startingNumber; +} + +function ConvertRomanToArabic(num) { + num = num.toUpperCase(); + if (num && !/[^MDCLXVI]/i.test(num)) { + var Arabic = 0; + var last_digit = 1000; + for (var i = 0; i < num.length; i++) { + var digit = gRomanDigits[num.charAt(i)]; + if (last_digit < digit) { + Arabic -= 2 * last_digit; + } + + last_digit = digit; + Arabic += last_digit; + } + return Arabic; + } + + return ""; +} + +function ConvertArabicToRoman(num) { + if (/^\d{1,4}$/.test(num)) { + var digits = ("000" + num).substr(-4); + return ( + gThousandsArray[digits.charAt(0)] + + gHundredsArray[digits.charAt(1)] + + gTensArray[digits.charAt(2)] + + gOnesArray[digits.charAt(3)] + ); + } + return ""; +} + +function ConvertLettersToArabic(letters) { + letters = letters.toUpperCase(); + if (!letters || /[^A-Z]/.test(letters)) { + return ""; + } + + var num = 0; + for (var i = 0; i < letters.length; i++) { + num = num * 26 + letters.charCodeAt(i) - A + 1; + } + return num; +} + +function ConvertArabicToLetters(num) { + var letters = ""; + while (num) { + num--; + letters = String.fromCharCode(A + (num % 26)) + letters; + num = Math.floor(num / 26); + } + return letters; +} + +function onAccept(event) { + if (ValidateData()) { + // Coalesce into one undo transaction + var editor = GetCurrentEditor(); + + editor.beginTransaction(); + + var changeEntireList = + gDialog.RadioGroup.selectedItem == gDialog.ChangeAllRadio; + + // Remember which radio button was selected + if (gListElement) { + gDialog.RadioGroup.setAttribute("index", changeEntireList ? "0" : "1"); + } + + var changeList; + if (gListElement && gDialog.ChangeAllRadio.selected) { + changeList = true; + } else { + changeList = + gMixedListSelection || + gListType != gOriginalListType || + gBulletStyleType != gOriginalStyleType; + } + if (changeList) { + try { + if (gListType) { + editor.makeOrChangeList( + gListType, + changeEntireList, + gBulletStyleType != gOriginalStyleType ? gBulletStyleType : null + ); + + // Get the new list created: + gListElement = editor.getElementOrParentByTagName(gListType, null); + + editor.cloneAttributes(gListElement, globalElement); + } else { + // Remove all existing lists + if (gListElement && changeEntireList) { + editor.selectElement(gListElement); + } + + editor.removeList("ol"); + editor.removeList("ul"); + editor.removeList("dl"); + } + } catch (e) {} + } + + editor.endTransaction(); + + SaveWindowLocation(); + + return; + } + event.preventDefault(); +} diff --git a/comm/suite/editor/components/dialogs/content/EdListProps.xhtml b/comm/suite/editor/components/dialogs/content/EdListProps.xhtml new file mode 100644 index 0000000000..cae9829c97 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdListProps.xhtml @@ -0,0 +1,73 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % edListProperties SYSTEM "chrome://editor/locale/EditorListProperties.dtd"> +%edListProperties; +<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd"> +%edDialogOverlay; +]> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup()"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdListProps.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <groupbox flex="1"> + <hbox class="groupbox-title"> + <label class="header">&ListType.label;</label> + </hbox> + <menulist id="ListType" oncommand="SelectListType()"> + <menupopup> + <menuitem label="&none.value;"/> + <menuitem value="ul" label="&bulletList.value;"/> + <menuitem value="ol" label="&numberList.value;"/> + <menuitem value="dl" label="&definitionList.value;"/> + </menupopup> + </menulist> + </groupbox> + <spacer class="spacer"/> + + <!-- message text and list items are set in JS + text value should be identical to string with id=BulletStyle in editor.properties + --> + <groupbox flex="1"> + <hbox class="groupbox-title"> + <label id="BulletStyleLabel" class="header">&bulletStyle.label;</label> + </hbox> + <menulist class="MinWidth10em" id="BulletStyle" oncommand="SelectBulletStyle()"> + <menupopup/> + </menulist> + <spacer class="spacer"/> + <hbox> + <label id="StartingNumberLabel" control="StartingNumber" + value="&startingNumber.label;" accesskey="&startingNumber.accessKey;"/> + <textbox class="narrow" id="StartingNumber"/> + <spacer/> + </hbox> + </groupbox> + <radiogroup id="RadioGroup" index="0" persist="index"> + <radio id="ChangeAll" label="&changeEntireListRadio.label;" accesskey="&changeEntireListRadio.accessKey;"/> + <radio id="ChangeSelected" label="&changeSelectedRadio.label;" accesskey="&changeSelectedRadio.accessKey;"/> + </radiogroup> + <vbox id="AdvancedEdit"> + <hbox flex="1" style="margin-top: 0.2em" align="center"> + <!-- This will right-align the button --> + <spacer flex="1"/> + <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;" + accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/> + </hbox> + <separator id="advancedSeparator" class="groove"/> + </vbox> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.js b/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.js new file mode 100644 index 0000000000..0433e58872 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.js @@ -0,0 +1,159 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +var gInsertNew = true; +var gAnchorElement = null; +var gOriginalName = ""; +const kTagName = "anchor"; + +// dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + var editor = GetCurrentEditor(); + if (!editor) { + window.close(); + return; + } + + gDialog.OkButton = document.documentElement.getButton("accept"); + gDialog.NameInput = document.getElementById("nameInput"); + + // Get a single selected element of the desired type + gAnchorElement = editor.getSelectedElement(kTagName); + + if (gAnchorElement) { + // We found an element and don't need to insert one + gInsertNew = false; + + // Make a copy to use for AdvancedEdit + globalElement = gAnchorElement.cloneNode(false); + gOriginalName = ConvertToCDATAString(gAnchorElement.name); + } else { + gInsertNew = true; + // We don't have an element selected, + // so create one with default attributes + gAnchorElement = editor.createElementWithDefaults(kTagName); + if (gAnchorElement) { + // Use the current selection as suggested name + var name = GetSelectionAsText(); + // Get 40 characters of the selected text and don't add "...", + // replace whitespace with "_" and strip non-word characters + name = ConvertToCDATAString(TruncateStringAtWordEnd(name, 40, false)); + // Be sure the name is unique to the document + if (AnchorNameExists(name)) { + name += "_"; + } + + // Make a copy to use for AdvancedEdit + globalElement = gAnchorElement.cloneNode(false); + globalElement.setAttribute("name", name); + } + } + if (!gAnchorElement) { + dump("Failed to get selected element or create a new one!\n"); + window.close(); + return; + } + + InitDialog(); + + DoEnabling(); + SetTextboxFocus(gDialog.NameInput); + SetWindowLocation(); +} + +function InitDialog() { + gDialog.NameInput.value = globalElement.getAttribute("name"); +} + +function ChangeName() { + if (gDialog.NameInput.value.length > 0) { + // Replace spaces with "_" and strip other non-URL characters + // Note: we could use ConvertAndEscape, but then we'd + // have to UnEscapeAndConvert beforehand - too messy! + gDialog.NameInput.value = ConvertToCDATAString(gDialog.NameInput.value); + } + DoEnabling(); +} + +function DoEnabling() { + var enable = gDialog.NameInput.value.length > 0; + SetElementEnabled(gDialog.OkButton, enable); + SetElementEnabledById("AdvancedEditButton1", enable); +} + +function AnchorNameExists(name) { + var anchorList; + try { + anchorList = GetCurrentEditor().document.anchors; + } catch (e) {} + + if (anchorList) { + for (var i = 0; i < anchorList.length; i++) { + if (anchorList[i].name == name) { + return true; + } + } + } + return false; +} + +// Get and validate data from widgets. +// Set attributes on globalElement so they can be accessed by AdvancedEdit() +function ValidateData() { + var name = TrimString(gDialog.NameInput.value); + if (!name) { + ShowInputErrorMessage(GetString("MissingAnchorNameError")); + SetTextboxFocus(gDialog.NameInput); + return false; + } + // Replace spaces with "_" and strip other characters + // Note: we could use ConvertAndEscape, but then we'd + // have to UnConverAndEscape beforehand - too messy! + name = ConvertToCDATAString(name); + + if (gOriginalName != name && AnchorNameExists(name)) { + ShowInputErrorMessage( + GetString("DuplicateAnchorNameError").replace(/%name%/, name) + ); + SetTextboxFocus(gDialog.NameInput); + return false; + } + globalElement.name = name; + + return true; +} + +function onAccept(event) { + if (ValidateData()) { + if (gOriginalName != globalElement.name) { + var editor = GetCurrentEditor(); + editor.beginTransaction(); + + try { + // "false" = don't delete selected text when inserting + if (gInsertNew) { + // We must insert element before copying CSS style attribute, + // but we must set the name else it won't insert at all + gAnchorElement.name = globalElement.name; + editor.insertElementAtSelection(gAnchorElement, false); + } + + // Copy attributes to element we are changing or inserting + editor.cloneAttributes(gAnchorElement, globalElement); + } catch (e) {} + + editor.endTransaction(); + } + SaveWindowLocation(); + return; + } + event.preventDefault(); +} diff --git a/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.xhtml b/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.xhtml new file mode 100644 index 0000000000..3a38256222 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.xhtml @@ -0,0 +1,43 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % edNamedAnchorProperties SYSTEM "chrome://editor/locale/EdNamedAnchorProperties.dtd"> +%edNamedAnchorProperties; +<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd"> +%edDialogOverlay; +]> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup()"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdNamedAnchorProps.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <label control="nameInput" + value="&anchorNameEditField.label;" + accesskey="&anchorNameEditField.accessKey;"/> + <textbox class="MinWidth20em" id="nameInput" oninput="ChangeName()" + tooltiptext="&nameInput.tooltip;"/> + <spacer class="spacer"/> + <vbox id="AdvancedEdit"> + <hbox flex="1" style="margin-top: 0.2em" align="center"> + <!-- This will right-align the button --> + <spacer flex="1"/> + <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;" + accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/> + </hbox> + <separator id="advancedSeparator" class="groove"/> + </vbox> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdPageProps.js b/comm/suite/editor/components/dialogs/content/EdPageProps.js new file mode 100644 index 0000000000..568eff66ec --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdPageProps.js @@ -0,0 +1,159 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +var gNewTitle = ""; +var gAuthor = ""; +var gDescription = ""; +var gAuthorElement; +var gDescriptionElement; +var gInsertNewAuthor = false; +var gInsertNewDescription = false; +var gTitleWasEdited = false; +var gAuthorWasEdited = false; +var gDescWasEdited = false; + +// Cancel() is in EdDialogCommon.js +// dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + var editor = GetCurrentEditor(); + if (!editor) { + window.close(); + return; + } + + gDialog.PageLocation = document.getElementById("PageLocation"); + gDialog.PageModDate = document.getElementById("PageModDate"); + gDialog.TitleInput = document.getElementById("TitleInput"); + gDialog.AuthorInput = document.getElementById("AuthorInput"); + gDialog.DescriptionInput = document.getElementById("DescriptionInput"); + + // Default string for new page is set from DTD string in XUL, + // so set only if not new doc URL + var location = GetDocumentUrl(); + var lastmodString = GetString("Unknown"); + + if (!IsUrlAboutBlank(location)) { + // NEVER show username and password in clear text + gDialog.PageLocation.setAttribute("value", StripPassword(location)); + + // Get last-modified file date+time + // TODO: Convert this to local time? + var lastmod; + try { + lastmod = editor.document.lastModified; // get string of last modified date + } catch (e) {} + // Convert modified string to date (0 = unknown date or January 1, 1970 GMT) + if (Date.parse(lastmod)) { + try { + const dateTimeFormatter = new Services.intl.DateTimeFormat(undefined, { + dateStyle: "long", + timeStyle: "short", + }); + + var lastModDate = new Date(); + lastModDate.setTime(Date.parse(lastmod)); + lastmodString = dateTimeFormatter.format(lastModDate); + } catch (e) {} + } + } + gDialog.PageModDate.value = lastmodString; + + gAuthorElement = GetMetaElementByAttribute("name", "author"); + if (!gAuthorElement) { + gAuthorElement = CreateMetaElementWithAttribute("name", "author"); + if (!gAuthorElement) { + window.close(); + return; + } + gInsertNewAuthor = true; + } + + gDescriptionElement = GetMetaElementByAttribute("name", "description"); + if (!gDescriptionElement) { + gDescriptionElement = CreateMetaElementWithAttribute("name", "description"); + if (!gDescriptionElement) { + window.close(); + } + + gInsertNewDescription = true; + } + + InitDialog(); + + SetTextboxFocus(gDialog.TitleInput); + + SetWindowLocation(); +} + +function InitDialog() { + gDialog.TitleInput.value = GetDocumentTitle(); + + var gAuthor = TrimString(gAuthorElement.getAttribute("content")); + if (!gAuthor) { + // Fill in with value from editor prefs + gAuthor = Services.prefs.getCharPref("editor.author"); + } + gDialog.AuthorInput.value = gAuthor; + gDialog.DescriptionInput.value = gDescriptionElement.getAttribute("content"); +} + +function TextboxChanged(ID) { + switch (ID) { + case "TitleInput": + gTitleWasEdited = true; + break; + case "AuthorInput": + gAuthorWasEdited = true; + break; + case "DescriptionInput": + gDescWasEdited = true; + break; + } +} + +function ValidateData() { + gNewTitle = TrimString(gDialog.TitleInput.value); + gAuthor = TrimString(gDialog.AuthorInput.value); + gDescription = TrimString(gDialog.DescriptionInput.value); + return true; +} + +function onAccept(event) { + if (ValidateData()) { + var editor = GetCurrentEditor(); + editor.beginTransaction(); + + // Set title contents even if string is empty + // because TITLE is a required HTML element + if (gTitleWasEdited) { + SetDocumentTitle(gNewTitle); + } + + if (gAuthorWasEdited) { + SetMetaElementContent(gAuthorElement, gAuthor, gInsertNewAuthor, false); + } + + if (gDescWasEdited) { + SetMetaElementContent( + gDescriptionElement, + gDescription, + gInsertNewDescription, + false + ); + } + + editor.endTransaction(); + + SaveWindowLocation(); + return; // do close the window + } + event.preventDefault(); +} diff --git a/comm/suite/editor/components/dialogs/content/EdPageProps.xhtml b/comm/suite/editor/components/dialogs/content/EdPageProps.xhtml new file mode 100644 index 0000000000..4891baa04a --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdPageProps.xhtml @@ -0,0 +1,50 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorPageProperties.dtd"> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup();"> + + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdPageProps.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + <grid> + <columns><column flex="1"/><column flex="2"/></columns> + <rows> + <row> + <label value="&location.label;"/> + <label value="&locationNewPage.label;" id="PageLocation"/> + </row> + <row> + <label value="&lastModified.label;"/> + <label id="PageModDate"/> + </row> + <spacer class="spacer"/> + <row align="center"> + <label value="&titleInput.label;" accesskey="&titleInput.accessKey;" control="TitleInput"/> + <textbox class="MinWidth20em" id="TitleInput" oninput="TextboxChanged(this.id)"/> + </row> + <row align="center"> + <label value="&authorInput.label;" accesskey="&authorInput.accessKey;" control="AuthorInput"/> + <textbox class="MinWidth20em" id="AuthorInput" oninput="TextboxChanged(this.id)"/> + </row> + <row align="center"> + <label value="&descriptionInput.label;" accesskey="&descriptionInput.accessKey;" control="DescriptionInput"/> + <textbox class="MinWidth20em" id="DescriptionInput" oninput="TextboxChanged(this.id)"/> + </row> + </rows> + </grid> + <spacer class="bigspacer"/> + <label value="&EditHEADSource1.label;"/> + <description class="wrap" flex="1">&EditHEADSource2.label;</description> + <separator class="groove"/> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdReplace.js b/comm/suite/editor/components/dialogs/content/EdReplace.js new file mode 100644 index 0000000000..c0daea29de --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdReplace.js @@ -0,0 +1,382 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +var gReplaceDialog; // Quick access to document/form elements. +var gFindInst; // nsIWebBrowserFind that we're going to use +var gFindService; // Global service which remembers find params +var gEditor; // the editor we're using + +document.addEventListener("dialogaccept", event => { + onFindNext(); + event.preventDefault(); +}); + +function initDialogObject() { + // Create gReplaceDialog object and initialize. + gReplaceDialog = {}; + gReplaceDialog.findInput = document.getElementById("dialog.findInput"); + gReplaceDialog.replaceInput = document.getElementById("dialog.replaceInput"); + gReplaceDialog.caseSensitive = document.getElementById( + "dialog.caseSensitive" + ); + gReplaceDialog.wrap = document.getElementById("dialog.wrap"); + gReplaceDialog.searchBackwards = document.getElementById( + "dialog.searchBackwards" + ); + gReplaceDialog.findNext = document.getElementById("findNext"); + gReplaceDialog.replace = document.getElementById("replace"); + gReplaceDialog.replaceAndFind = document.getElementById("replaceAndFind"); + gReplaceDialog.replaceAll = document.getElementById("replaceAll"); +} + +function loadDialog() { + // Set initial dialog field contents. + // Set initial dialog field contents. Use the gFindInst attributes first, + // this is necessary for window.find() + gReplaceDialog.findInput.value = gFindInst.searchString + ? gFindInst.searchString + : gFindService.searchString; + gReplaceDialog.replaceInput.value = gFindService.replaceString; + gReplaceDialog.caseSensitive.checked = gFindInst.matchCase + ? gFindInst.matchCase + : gFindService.matchCase; + gReplaceDialog.wrap.checked = gFindInst.wrapFind + ? gFindInst.wrapFind + : gFindService.wrapFind; + gReplaceDialog.searchBackwards.checked = gFindInst.findBackwards + ? gFindInst.findBackwards + : gFindService.findBackwards; + + doEnabling(); +} + +function onLoad() { + // Get the xul <editor> element: + var editorElement = window.arguments[0]; + + // If we don't get the editor, then we won't allow replacing. + gEditor = editorElement.getEditor(editorElement.contentWindow); + if (!gEditor) { + window.close(); + return; + } + + // Get the nsIWebBrowserFind service: + gFindInst = editorElement.webBrowserFind; + + try { + // get the find service, which stores global find state + gFindService = Cc["@mozilla.org/find/find_service;1"].getService( + Ci.nsIFindService + ); + } catch (e) { + dump("No find service!\n"); + gFindService = 0; + } + + // Init gReplaceDialog. + initDialogObject(); + + // Change "OK" to "Find". + // dialog.find.label = document.getElementById("fBLT").getAttribute("label"); + + // Fill dialog. + loadDialog(); + + if (gReplaceDialog.findInput.value) { + gReplaceDialog.findInput.select(); + } else { + gReplaceDialog.findInput.focus(); + } +} + +function saveFindData() { + // Set data attributes per user input. + if (gFindService) { + gFindService.searchString = gReplaceDialog.findInput.value; + gFindService.matchCase = gReplaceDialog.caseSensitive.checked; + gFindService.wrapFind = gReplaceDialog.wrap.checked; + gFindService.findBackwards = gReplaceDialog.searchBackwards.checked; + } +} + +function setUpFindInst() { + gFindInst.searchString = gReplaceDialog.findInput.value; + gFindInst.matchCase = gReplaceDialog.caseSensitive.checked; + gFindInst.wrapFind = gReplaceDialog.wrap.checked; + gFindInst.findBackwards = gReplaceDialog.searchBackwards.checked; +} + +function onFindNext() { + // Transfer dialog contents to the find service. + saveFindData(); + // set up the find instance + setUpFindInst(); + + // Search. + var result = gFindInst.findNext(); + + if (!result) { + var bundle = document.getElementById("findBundle"); + Services.prompt.alert( + window, + GetString("Alert"), + bundle.getString("notFoundWarning") + ); + SetTextboxFocus(gReplaceDialog.findInput); + gReplaceDialog.findInput.select(); + gReplaceDialog.findInput.focus(); + return false; + } + return true; +} + +function onReplace() { + if (!gEditor) { + return false; + } + + // Does the current selection match the find string? + var selection = gEditor.selection; + + var selStr = selection.toString(); + var specStr = gReplaceDialog.findInput.value; + if (!gReplaceDialog.caseSensitive.checked) { + selStr = selStr.toLowerCase(); + specStr = specStr.toLowerCase(); + } + // Unfortunately, because of whitespace we can't just check + // whether (selStr == specStr), but have to loop ourselves. + // N chars of whitespace in specStr can match any M >= N in selStr. + var matches = true; + var specLen = specStr.length; + var selLen = selStr.length; + if (selLen < specLen) { + matches = false; + } else { + var specArray = specStr.match(/\S+|\s+/g); + var selArray = selStr.match(/\S+|\s+/g); + if (specArray.length != selArray.length) { + matches = false; + } else { + for (var i = 0; i < selArray.length; i++) { + if (selArray[i] != specArray[i]) { + if (/\S/.test(selArray[i][0]) || /\S/.test(specArray[i][0])) { + // not a space chunk -- match fails + matches = false; + break; + } else if (selArray[i].length < specArray[i].length) { + // if it's a space chunk then we only care that sel be + // at least as long as spec + matches = false; + break; + } + } + } + } + } + + // If the current selection doesn't match the pattern, + // then we want to find the next match, but not do the replace. + // That's what most other apps seem to do. + // So here, just return. + if (!matches) { + return false; + } + + // Transfer dialog contents to the find service. + saveFindData(); + + // For reverse finds, need to remember the caret position + // before current selection + var newRange; + if (gReplaceDialog.searchBackwards.checked && selection.rangeCount > 0) { + newRange = selection.getRangeAt(0).cloneRange(); + newRange.collapse(true); + } + + // nsPlaintextEditor::InsertText fails if the string is empty, + // so make that a special case: + var replStr = gReplaceDialog.replaceInput.value; + if (replStr == "") { + gEditor.deleteSelection(gEditor.eNone, gEditor.eStrip); + } else { + gEditor.insertText(replStr); + } + + // For reverse finds, need to move caret just before the replaced text + if (gReplaceDialog.searchBackwards.checked && newRange) { + gEditor.selection.removeAllRanges(); + gEditor.selection.addRange(newRange); + } + + return true; +} + +function onReplaceAll() { + if (!gEditor) { + return; + } + + var findStr = gReplaceDialog.findInput.value; + var repStr = gReplaceDialog.replaceInput.value; + + // Transfer dialog contents to the find service. + saveFindData(); + + var finder = Cc["@mozilla.org/embedcomp/rangefind;1"] + .createInstance() + .QueryInterface(Ci.nsIFind); + + finder.caseSensitive = gReplaceDialog.caseSensitive.checked; + finder.findBackwards = gReplaceDialog.searchBackwards.checked; + + // We want the whole operation to be undoable in one swell foop, + // so start a transaction: + gEditor.beginTransaction(); + + // and to make sure we close the transaction, guard against exceptions: + try { + // Make a range containing the current selection, + // so we don't go past it when we wrap. + var selection = gEditor.selection; + var selecRange; + if (selection.rangeCount > 0) { + selecRange = selection.getRangeAt(0); + } + var origRange = selecRange.cloneRange(); + + // We'll need a range for the whole document: + var wholeDocRange = gEditor.document.createRange(); + var rootNode = gEditor.rootElement; + wholeDocRange.selectNodeContents(rootNode); + + // And start and end points: + var endPt = gEditor.document.createRange(); + + if (gReplaceDialog.searchBackwards.checked) { + endPt.setStart(wholeDocRange.startContainer, wholeDocRange.startOffset); + endPt.setEnd(wholeDocRange.startContainer, wholeDocRange.startOffset); + } else { + endPt.setStart(wholeDocRange.endContainer, wholeDocRange.endOffset); + endPt.setEnd(wholeDocRange.endContainer, wholeDocRange.endOffset); + } + + // Find and replace from here to end (start) of document: + var foundRange; + var searchRange = wholeDocRange.cloneRange(); + while ( + (foundRange = finder.Find(findStr, searchRange, selecRange, endPt)) != + null + ) { + gEditor.selection.removeAllRanges(); + gEditor.selection.addRange(foundRange); + + // The editor will leave the caret at the end of the replaced text. + // For reverse finds, we need it at the beginning, + // so save the next position now. + if (gReplaceDialog.searchBackwards.checked) { + selecRange = foundRange.cloneRange(); + selecRange.setEnd(selecRange.startContainer, selecRange.startOffset); + } + + // nsPlaintextEditor::InsertText fails if the string is empty, + // so make that a special case: + if (repStr == "") { + gEditor.deleteSelection(gEditor.eNone, gEditor.eStrip); + } else { + gEditor.insertText(repStr); + } + + // If we're going forward, we didn't save selecRange before, so do it now: + if (!gReplaceDialog.searchBackwards.checked) { + selection = gEditor.selection; + if (selection.rangeCount <= 0) { + gEditor.endTransaction(); + return; + } + selecRange = selection.getRangeAt(0).cloneRange(); + } + } + + // If no wrapping, then we're done + if (!gReplaceDialog.wrap.checked) { + gEditor.endTransaction(); + return; + } + + // If wrapping, find from start/end of document back to start point. + if (gReplaceDialog.searchBackwards.checked) { + // Collapse origRange to end + origRange.setStart(origRange.endContainer, origRange.endOffset); + // Set current position to document end + selecRange.setEnd(wholeDocRange.endContainer, wholeDocRange.endOffset); + selecRange.setStart(wholeDocRange.endContainer, wholeDocRange.endOffset); + } else { + // Collapse origRange to start + origRange.setEnd(origRange.startContainer, origRange.startOffset); + // Set current position to document start + selecRange.setStart( + wholeDocRange.startContainer, + wholeDocRange.startOffset + ); + selecRange.setEnd( + wholeDocRange.startContainer, + wholeDocRange.startOffset + ); + } + + while ( + (foundRange = finder.Find( + findStr, + wholeDocRange, + selecRange, + origRange + )) != null + ) { + gEditor.selection.removeAllRanges(); + gEditor.selection.addRange(foundRange); + + // Save insert point for backward case + if (gReplaceDialog.searchBackwards.checked) { + selecRange = foundRange.cloneRange(); + selecRange.setEnd(selecRange.startContainer, selecRange.startOffset); + } + + // nsPlaintextEditor::InsertText fails if the string is empty, + // so make that a special case: + if (repStr == "") { + gEditor.deleteSelection(gEditor.eNone, gEditor.eStrip); + } else { + gEditor.insertText(repStr); + } + + // Get insert point for forward case + if (!gReplaceDialog.searchBackwards.checked) { + selection = gEditor.selection; + if (selection.rangeCount <= 0) { + gEditor.endTransaction(); + return; + } + selecRange = selection.getRangeAt(0); + } + } + } catch (e) {} + + gEditor.endTransaction(); +} + +function doEnabling() { + var findStr = gReplaceDialog.findInput.value; + gReplaceDialog.enabled = findStr; + gReplaceDialog.findNext.disabled = !findStr; + gReplaceDialog.replace.disabled = !findStr; + gReplaceDialog.replaceAndFind.disabled = !findStr; + gReplaceDialog.replaceAll.disabled = !findStr; +} diff --git a/comm/suite/editor/components/dialogs/content/EdReplace.xhtml b/comm/suite/editor/components/dialogs/content/EdReplace.xhtml new file mode 100644 index 0000000000..54a9d81ba3 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdReplace.xhtml @@ -0,0 +1,65 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorReplace.dtd"> + +<dialog id="replaceDlg" title="&replaceDialog.title;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + persist="screenX screenY" + buttons="cancel" + onload="onLoad()"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdReplace.js"/> + <stringbundle id="findBundle" src="chrome://global/locale/finddialog.properties"/> + + <hbox> + <vbox> + <spacer class="spacer"/> + <grid align="start"> + <columns><column/><column/></columns> + <rows> + <row align="center"> + <label value="&findField.label;" accesskey="&findField.accesskey;" control="dialog.findInput"/> + <textbox id="dialog.findInput" oninput="doEnabling();"/> + </row> + <row align="center"> + <label value="&replaceField.label;" accesskey="&replaceField.accesskey;" control="dialog.replaceInput"/> + <textbox id="dialog.replaceInput" oninput="doEnabling();"/> + </row> + <row align="start"> + <spacer/> + <vbox align="start"> + <spacer class="bigspacer"/> + <checkbox id="dialog.caseSensitive" label="&caseSensitiveCheckbox.label;" + accesskey="&caseSensitiveCheckbox.accesskey;"/> + <checkbox id="dialog.wrap" label="&wrapCheckbox.label;" + accesskey="&wrapCheckbox.accesskey;"/> + <checkbox id="dialog.searchBackwards" label="&backwardsCheckbox.label;" + accesskey="&backwardsCheckbox.accesskey;"/> + </vbox> + </row> + </rows> + </grid> + </vbox> + <vbox> + <button id="findNext" label="&findNextButton.label;" accesskey="&findNextButton.accesskey;" + oncommand="onFindNext();" default="true"/> + <button id="replace" label="&replaceButton.label;" accesskey="&replaceButton.accesskey;" + oncommand="onReplace();"/> + <button id="replaceAndFind" label="&replaceAndFindButton.label;" + accesskey="&replaceAndFindButton.accesskey;" oncommand="onReplace(); onFindNext();"/> + <button id="replaceAll" label="&replaceAllButton.label;" + accesskey="&replaceAllButton.accesskey;" oncommand="onReplaceAll();"/> + <button dlgtype="cancel" label="&closeButton.label;" accesskey="&closeButton.accesskey;"/> + </vbox> + </hbox> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdSelectProps.js b/comm/suite/editor/components/dialogs/content/EdSelectProps.js new file mode 100644 index 0000000000..c03fd73a67 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdSelectProps.js @@ -0,0 +1,770 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +// Global variables + +var hasValue; +var oldValue; +var insertNew; +var itemArray; +var theTree; +var treeSelection; +var selectElement; +var currentItem = null; +var selectedOption = null; +var selectedOptionCount = 0; + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +// Utility functions + +function getParentIndex(index) { + switch (itemArray[index].level) { + case 0: + return -1; + case 1: + return 0; + } + // eslint-disable-next-line curly + while (itemArray[--index].level > 1); + return index; +} + +function UpdateSelectMultiple() { + if (selectedOptionCount > 1) { + gDialog.selectMultiple.checked = true; + gDialog.selectMultiple.disabled = true; + } else { + gDialog.selectMultiple.disabled = false; + } +} + +/* wrapper objects: + * readonly attribute Node element; // DOM node (select/optgroup/option) + * readonly attribute int level; // tree depth + * readonly attribute boolean container; // can contain options + * string getCellText(string col); // tree view helper + * string cycleCell(int currentIndex); // tree view helper + * void onFocus(); // load data into deck + * void onBlur(); // save data from deck + * boolean canDestroy(boolean prompt); // NB prompt not used + * void destroy(); // post remove callback + * void moveUp(); + * boolean canMoveDown(); + * void moveDown(); + * void appendOption(newElement, currentIndex); + */ + +// OPTION element wrapper object + +// Create a wrapper for the given element at the given level +function optionObject(option, level) { + // select an added option (when loading from document) + if (option.hasAttribute("selected")) { + selectedOptionCount++; + } + this.level = level; + this.element = option; +} + +optionObject.prototype.container = false; + +optionObject.prototype.getCellText = function(column) { + if (column.id == "SelectSelCol") { + return ""; + } + if (column.id == "SelectValCol" && this.element.hasAttribute("value")) { + return this.element.getAttribute("value"); + } + return this.element.text; +}; + +optionObject.prototype.cycleCell = function(index) { + if (this.element.hasAttribute("selected")) { + this.element.removeAttribute("selected"); + selectedOptionCount--; + selectedOption = null; + } else { + // Different handling for multiselect lists + if (gDialog.selectMultiple.checked || !selectedOption) { + selectedOptionCount++; + } else if (selectedOption) { + selectedOption.removeAttribute("selected"); + let column = theTree.columns.SelectSelCol; + theTree.invalidateColumn(column); + selectedOption = null; + } + this.element.setAttribute("selected", ""); + selectedOption = this.element; + let column = theTree.columns.SelectSelCol; + theTree.invalidateCell(index, column); + } + if (currentItem == this) { + // Also update the deck + gDialog.optionSelected.setAttribute( + "checked", + this.element.hasAttribute("selected") + ); + } + UpdateSelectMultiple(); +}; + +optionObject.prototype.onFocus = function() { + gDialog.optionText.value = this.element.text; + hasValue = this.element.hasAttribute("value"); + oldValue = this.element.value; + gDialog.optionHasValue.checked = hasValue; + gDialog.optionValue.value = hasValue ? this.element.value : this.element.text; + gDialog.optionSelected.checked = this.element.hasAttribute("selected"); + gDialog.optionDisabled.checked = this.element.hasAttribute("disabled"); + gDialog.selectDeck.setAttribute("selectedIndex", "2"); +}; + +optionObject.prototype.onBlur = function() { + this.element.text = gDialog.optionText.value; + if (gDialog.optionHasValue.checked) { + this.element.value = gDialog.optionValue.value; + } else { + this.element.removeAttribute("value"); + } + if (gDialog.optionSelected.checked) { + this.element.setAttribute("selected", ""); + } else { + this.element.removeAttribute("selected"); + } + if (gDialog.optionDisabled.checked) { + this.element.setAttribute("disabled", ""); + } else { + this.element.removeAttribute("disabled"); + } +}; + +optionObject.prototype.canDestroy = function(prompt) { + return true; + /* return !prompt || + ConfirmWithTitle(GetString("DeleteOption"), + GetString("DeleteOptionMsg"), + GetString("DeleteOption"));*/ +}; + +optionObject.prototype.destroy = function() { + // Deselect a removed option + if (this.element.hasAttribute("selected")) { + selectedOptionCount--; + selectedOption = null; + UpdateSelectMultiple(); + } +}; + +/* 4 cases: + * a) optgroup -> optgroup + * ... ... + * option option + * b) optgroup -> option + * option optgroup + * ... ... + * c) option + * option + * d) option + * option + */ + +optionObject.prototype.moveUp = function() { + var index = treeSelection.currentIndex; + if ( + itemArray[index].level < + itemArray[index - 1].level + itemArray[index - 1].container + ) { + // we need to repaint the tree's lines + theTree.invalidateRange(getParentIndex(index), index); + // a) option is just after an optgroup, so it becomes the last child + itemArray[index].level = 2; + theTree.view.selectionChanged(); + } else { + // otherwise new option level is now the same as the previous item + itemArray[index].level = itemArray[index - 1].level; + // swap the option with the previous item + itemArray.splice(index, 0, itemArray.splice(--index, 1)[0]); + } + selectTreeIndex(index, true); +}; + +optionObject.prototype.canMoveDown = function() { + // move down is not allowed on the last option if its level is 1 + return this.level > 1 || itemArray.length - treeSelection.currentIndex > 1; +}; + +optionObject.prototype.moveDown = function() { + var index = treeSelection.currentIndex; + if ( + index + 1 == itemArray.length || + itemArray[index].level > itemArray[index + 1].level + ) { + // we need to repaint the tree's lines + theTree.invalidateRange(getParentIndex(index), index); + // a) option is last child of an optgroup, so it moves just after + itemArray[index].level = 1; + theTree.view.selectionChanged(); + } else { + // level increases if the option was preceding an optgroup + itemArray[index].level += itemArray[index + 1].container; + // swap the option with the next item + itemArray.splice(index, 0, itemArray.splice(++index, 1)[0]); + } + selectTreeIndex(index, true); +}; + +optionObject.prototype.appendOption = function(child, parent) { + // special case quick check + if (this.level == 1) { + return gDialog.appendOption(child, 0); + } + + // append the option to the parent element + parent = getParentIndex(parent); + return itemArray[parent].appendOption(child, parent); +}; + +// OPTGROUP element wrapper object + +function optgroupObject(optgroup) { + this.element = optgroup; +} + +optgroupObject.prototype.level = 1; + +optgroupObject.prototype.container = true; + +optgroupObject.prototype.getCellText = function(column) { + return column.id == "SelectTextCol" ? this.element.label : ""; +}; + +optgroupObject.prototype.cycleCell = function(index) {}; + +optgroupObject.prototype.onFocus = function() { + gDialog.optgroupLabel.value = this.element.label; + gDialog.optgroupDisabled.checked = this.element.disabled; + gDialog.selectDeck.setAttribute("selectedIndex", "1"); +}; + +optgroupObject.prototype.onBlur = function() { + this.element.label = gDialog.optgroupLabel.value; + this.element.disabled = gDialog.optgroupDisabled.checked; +}; + +optgroupObject.prototype.canDestroy = function(prompt) { + // Only removing empty option groups for now + return ( + gDialog.nextChild(treeSelection.currentIndex) - + treeSelection.currentIndex == + 1 + ); + /* && (!prompt || + ConfirmWithTitle(GetString("DeleteOptGroup"), + GetString("DeleteOptGroupMsg"), + GetString("DeleteOptGroup"))); +*/ +}; + +optgroupObject.prototype.destroy = function() {}; + +optgroupObject.prototype.moveUp = function() { + // Find the index of the previous and next elements at the same level + var index = treeSelection.currentIndex; + var i = index; + // eslint-disable-next-line curly + while (itemArray[--index].level > 1); + var j = gDialog.nextChild(i); + // Cut out the element, cut the array in two, then join together + var movedItems = itemArray.splice(i, j - i); + var endItems = itemArray.splice(index); + itemArray = itemArray.concat(movedItems).concat(endItems); + // Repaint the lot + theTree.invalidateRange(index, j); + selectTreeIndex(index, true); +}; + +optgroupObject.prototype.canMoveDown = function() { + return gDialog.lastChild() > treeSelection.currentIndex; +}; + +optgroupObject.prototype.moveDown = function() { + // Find the index of the next two elements at the same level + var index = treeSelection.currentIndex; + var i = gDialog.nextChild(index); + var j = gDialog.nextChild(i); + // Cut out the element, cut the array in two, then join together + var movedItems = itemArray.splice(i, j - 1); + var endItems = itemArray.splice(index); + itemArray = itemArray.concat(movedItems).concat(endItems); + // Repaint the lot + theTree.invalidateRange(index, j); + index += j - i; + selectTreeIndex(index, true); +}; + +optgroupObject.prototype.appendOption = function(child, parent) { + var index = gDialog.nextChild(parent); + // XXX need to repaint the lines, tree won't do this + var primaryCol = theTree.columns.getPrimaryColumn(); + theTree.invalidateCell(index - 1, primaryCol); + // insert the wrapped object as the last child + itemArray.splice(index, 0, new optionObject(child, 2)); + theTree.rowCountChanged(index, 1); + selectTreeIndex(index, false); +}; + +// dialog initialization code + +function Startup() { + var editor = GetCurrentEditor(); + if (!editor) { + dump("Failed to get active editor!\n"); + window.close(); + return; + } + + // Get a single selected select element + const kTagName = "select"; + try { + selectElement = editor.getSelectedElement(kTagName); + } catch (e) {} + + if (selectElement) { + // We found an element and don't need to insert one + insertNew = false; + } else { + insertNew = true; + + // We don't have an element selected, + // so create one with default attributes + try { + selectElement = editor.createElementWithDefaults(kTagName); + } catch (e) {} + + if (!selectElement) { + dump("Failed to get selected element or create a new one!\n"); + window.close(); + return; + } + } + + // SELECT element wrapper object + gDialog = { + // useful elements + accept: document.documentElement.getButton("accept"), + selectDeck: document.getElementById("SelectDeck"), + selectName: document.getElementById("SelectName"), + selectSize: document.getElementById("SelectSize"), + selectMultiple: document.getElementById("SelectMultiple"), + selectDisabled: document.getElementById("SelectDisabled"), + selectTabIndex: document.getElementById("SelectTabIndex"), + optgroupLabel: document.getElementById("OptGroupLabel"), + optgroupDisabled: document.getElementById("OptGroupDisabled"), + optionText: document.getElementById("OptionText"), + optionHasValue: document.getElementById("OptionHasValue"), + optionValue: document.getElementById("OptionValue"), + optionSelected: document.getElementById("OptionSelected"), + optionDisabled: document.getElementById("OptionDisabled"), + removeButton: document.getElementById("RemoveButton"), + previousButton: document.getElementById("PreviousButton"), + nextButton: document.getElementById("NextButton"), + tree: document.getElementById("SelectTree"), + // wrapper methods (except MoveUp and MoveDown) + element: selectElement.cloneNode(false), + level: 0, + container: true, + getCellText(column) { + return column.id == "SelectTextCol" + ? this.element.getAttribute("name") + : ""; + }, + cycleCell(index) {}, + onFocus() { + gDialog.selectName.value = this.element.getAttribute("name"); + gDialog.selectSize.value = this.element.getAttribute("size"); + gDialog.selectMultiple.checked = this.element.hasAttribute("multiple"); + gDialog.selectDisabled.checked = this.element.hasAttribute("disabled"); + gDialog.selectTabIndex.value = this.element.getAttribute("tabindex"); + this.selectDeck.setAttribute("selectedIndex", "0"); + onNameInput(); + }, + onBlur() { + this.element.setAttribute("name", gDialog.selectName.value); + if (gDialog.selectSize.value) { + this.element.setAttribute("size", gDialog.selectSize.value); + } else { + this.element.removeAttribute("size"); + } + if (gDialog.selectMultiple.checked) { + this.element.setAttribute("multiple", ""); + } else { + this.element.removeAttribute("multiple"); + } + if (gDialog.selectDisabled.checked) { + this.element.setAttribute("disabled", ""); + } else { + this.element.removeAttribute("disabled"); + } + if (gDialog.selectTabIndex.value) { + this.element.setAttribute("tabindex", gDialog.selectTabIndex.value); + } else { + this.element.removeAttribute("tabindex"); + } + }, + appendOption(child, parent) { + var index = itemArray.length; + // XXX need to repaint the lines, tree won't do this + theTree.invalidateRange(this.lastChild(), index); + // append the wrapped object + itemArray.push(new optionObject(child, 1)); + theTree.rowCountChanged(index, 1); + selectTreeIndex(index, false); + }, + canDestroy(prompt) { + return false; + }, + canMoveDown() { + return false; + }, + // helper methods + // Find the index of the next immediate child of the select + nextChild(index) { + // eslint-disable-next-line curly + while (++index < itemArray.length && itemArray[index].level > 1); + return index; + }, + // Find the index of the last immediate child of the select + lastChild() { + var index = itemArray.length; + // eslint-disable-next-line curly + while (itemArray[--index].level > 1); + return index; + }, + }; + // Start with the <select> wrapper + itemArray = [gDialog]; + + // We modify the actual option and optgroup elements so clone them first + for (var child = selectElement.firstChild; child; child = child.nextSibling) { + if (child.tagName == "OPTION") { + itemArray.push(new optionObject(child.cloneNode(true), 1)); + } else if (child.tagName == "OPTGROUP") { + itemArray.push(new optgroupObject(child.cloneNode(false))); + for ( + var grandchild = child.firstChild; + grandchild; + grandchild = grandchild.nextSibling + ) { + if (grandchild.tagName == "OPTION") { + itemArray.push(new optionObject(grandchild.cloneNode(true), 2)); + } + } + } + } + + UpdateSelectMultiple(); + + // Define a custom view for the tree + theTree = gDialog.tree; + theTree.view = { + QueryInterface: ChromeUtils.generateQI([ + "nsITreeView", + "nsISupportsWeakReference", + ]), + // useful for debugging + get wrappedJSObject() { + return this; + }, + get rowCount() { + return itemArray.length; + }, + get selection() { + return treeSelection; + }, + set selection(selection) { + return (treeSelection = selection); + }, + getRowProperties(index) { + return ""; + }, + // could have used a wrapper for this + getCellProperties(index, column) { + if (column.id == "SelectSelCol" && !itemArray[index].container) { + return "checked-" + itemArray[index].element.hasAttribute("selected"); + } + return ""; + }, + getColumnProperties(column) { + return ""; + }, + // get info from wrapper + isContainer(index) { + return itemArray[index].container; + }, + isContainerOpen(index) { + return true; + }, + isContainerEmpty(index) { + return true; + }, + isSeparator(index) { + return false; + }, + isSorted() { + return false; + }, + // d&d not implemented yet! + canDrop(index, orientation) { + return false; + }, + drop(index, orientation) { + alert("drop:" + index + "," + orientation); + }, + // same as the global helper + getParentIndex, + // tree needs to know when to paint lines + hasNextSibling(index, after) { + if (!index) { + return false; + } + var level = itemArray[index].level; + while (++after < itemArray.length) { + switch (level - itemArray[after].level) { + case 1: + return false; + case 0: + return true; + } + } + return false; + }, + getLevel(index) { + return itemArray[index].level; + }, + getImageSrc(index, column) {}, + getProgressMode(index, column) {}, + getCellValue(index, column) {}, + getCellText(index, column) { + return itemArray[index].getCellText(column); + }, + setTree(tree) { + this.tree = tree; + }, + toggleOpenState(index) {}, + cycleHeader(col) {}, + selectionChanged() { + // Save current values and update buttons and deck + if (currentItem) { + currentItem.onBlur(); + } + var currentIndex = treeSelection.currentIndex; + currentItem = itemArray[currentIndex]; + gDialog.removeButton.disabled = !currentItem.canDestroy(); + gDialog.previousButton.disabled = currentIndex < 2; + gDialog.nextButton.disabled = !currentItem.canMoveDown(); + // For Advanced Edit + globalElement = currentItem.element; + currentItem.onFocus(); + }, + cycleCell(index, column) { + itemArray[index].cycleCell(index); + }, + isEditable(index, column) { + return false; + }, + }; + treeSelection.select(0); + currentItem = gDialog; + // onNameInput(); + + SetTextboxFocus(gDialog.selectName); + + SetWindowLocation(); +} + +// Called from Advanced Edit +function InitDialog() { + currentItem.onFocus(); +} + +// Called from Advanced Edit +function ValidateData() { + currentItem.onBlur(); + return true; +} + +function onAccept() { + // All values are valid - copy to actual element in doc or + // element created to insert + ValidateData(); + + var editor = GetCurrentEditor(); + + // Coalesce into one undo transaction + editor.beginTransaction(); + + try { + editor.cloneAttributes(selectElement, gDialog.element); + + if (insertNew) { + // 'true' means delete the selection before inserting + editor.insertElementAtSelection(selectElement, true); + } + + editor.setShouldTxnSetSelection(false); + + while (selectElement.lastChild) { + editor.deleteNode(selectElement.lastChild); + } + + var offset = 0; + for (var i = 1; i < itemArray.length; i++) { + if (itemArray[i].level > 1) { + selectElement.lastChild.appendChild(itemArray[i].element); + } else { + editor.insertNode(itemArray[i].element, selectElement, offset++); + } + } + + editor.setShouldTxnSetSelection(true); + } finally { + editor.endTransaction(); + } + + SaveWindowLocation(); +} + +// Button actions +function AddOption() { + currentItem.appendOption( + GetCurrentEditor().createElementWithDefaults("option"), + treeSelection.currentIndex + ); + SetTextboxFocus(gDialog.optionText); +} + +function AddOptGroup() { + var optgroupElement = GetCurrentEditor().createElementWithDefaults( + "optgroup" + ); + var index = itemArray.length; + // XXX need to repaint the lines, tree won't do this + theTree.invalidateRange(gDialog.lastChild(), index); + // append the wrapped object + itemArray.push(new optgroupObject(optgroupElement)); + theTree.rowCountChanged(index, 1); + selectTreeIndex(index, false); + SetTextboxFocus(gDialog.optgroupLabel); +} + +function RemoveElement() { + if (currentItem.canDestroy(true)) { + // Only removing empty option groups for now + var index = treeSelection.currentIndex; + var level = itemArray[index].level; + // Perform necessary cleanup and remove the wrapper + itemArray[index].destroy(); + itemArray.splice(index, 1); + --index; + // XXX need to repaint the lines, tree won't do this + if (level == 1) { + var last = gDialog.lastChild(); + if (index > last) { + theTree.invalidateRange(last, index); + } + } + selectTreeIndex(index, true); + theTree.rowCountChanged(++index, -1); + } +} + +// Event handler +function onTreeKeyUp(event) { + if (event.keyCode == event.DOM_VK_SPACE) { + currentItem.cycleCell(); + } +} + +function onNameInput() { + var disabled = !gDialog.selectName.value; + if (gDialog.accept.disabled != disabled) { + gDialog.accept.disabled = disabled; + } + gDialog.element.setAttribute("name", gDialog.selectName.value); + // repaint the tree + var primaryCol = theTree.columns.getPrimaryColumn(); + theTree.invalidateCell(treeSelection.currentIndex, primaryCol); +} + +function onLabelInput() { + currentItem.element.setAttribute("label", gDialog.optgroupLabel.value); + // repaint the tree + var primaryCol = theTree.columns.getPrimaryColumn(); + theTree.invalidateCell(treeSelection.currentIndex, primaryCol); +} + +function onTextInput() { + currentItem.element.text = gDialog.optionText.value; + // repaint the tree + if (hasValue) { + var primaryCol = theTree.columns.getPrimaryColumn(); + theTree.invalidateCell(treeSelection.currentIndex, primaryCol); + } else { + gDialog.optionValue.value = gDialog.optionText.value; + theTree.invalidateRow(treeSelection.currentIndex); + } +} + +function onValueInput() { + gDialog.optionHasValue.checked = hasValue = true; + oldValue = gDialog.optionValue.value; + currentItem.element.setAttribute("value", oldValue); + // repaint the tree + var column = theTree.columns.SelectValCol; + theTree.invalidateCell(treeSelection.currentIndex, column); +} + +function onHasValueClick() { + hasValue = gDialog.optionHasValue.checked; + if (hasValue) { + gDialog.optionValue.value = oldValue; + currentItem.element.setAttribute("value", oldValue); + } else { + oldValue = gDialog.optionValue.value; + gDialog.optionValue.value = gDialog.optionText.value; + currentItem.element.removeAttribute("value"); + } + // repaint the tree + var column = theTree.columns.SelectValCol; + theTree.invalidateCell(treeSelection.currentIndex, column); +} + +function onSelectMultipleClick() { + // Recalculate the unique selected option if we need it and have lost it + if ( + !gDialog.selectMultiple.checked && + selectedOptionCount == 1 && + !selectedOption + ) { + // eslint-disable-next-line curly + for ( + var i = 1; + !(selectedOption = itemArray[i].element).hasAttribute("selected"); + i++ + ); + } +} + +function selectTreeIndex(index, focus) { + treeSelection.select(index); + theTree.ensureRowIsVisible(index); + if (focus) { + gDialog.tree.focus(); + } +} diff --git a/comm/suite/editor/components/dialogs/content/EdSelectProps.xhtml b/comm/suite/editor/components/dialogs/content/EdSelectProps.xhtml new file mode 100644 index 0000000000..ff91b02e28 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdSelectProps.xhtml @@ -0,0 +1,143 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % edSelectProperties SYSTEM "chrome://editor/locale/EditorSelectProperties.dtd"> +%edSelectProperties; +<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd"> +%edDialogOverlay; +]> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup();" + buttons="accept,cancel"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdSelectProps.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <!-- Setting rows="7" on tree isn't working, equalsize vbox sets tree height. --> + <vbox equalsize="always"> + <tree id="SelectTree" onselect="treeBoxObject.view.selectionChanged();" onkeyup="onTreeKeyUp(event);"> + <treecols id="SelectCols"> + <treecol id="SelectTextCol" flex="3" label="&TextHeader.label;" primary="true"/> + <splitter class="tree-splitter"/> + <treecol id="SelectValCol" flex="2" label="&ValueHeader.label;"/> + <treecol id="SelectSelCol" label="&SelectedHeader.label;" cycler="true"/> + </treecols> + + <treechildren id="SelectTreeChildren"/> + </tree> + + <hbox flex="1"> + <deck flex="1" id="SelectDeck" index="0"> + <groupbox flex="1"> + <hbox class="groupbox-title"> + <label class="header">&Select.label;</label> + </hbox> + <grid flex="1"><columns><column flex="1"/><column/></columns> + <rows> + <row align="center"> + <label control="SelectName" value="&SelectName.label;" accesskey="&SelectName.accesskey;"/> + <textbox id="SelectName" flex="1" oninput="onNameInput();"/> + </row> + <row align="center"> + <label control="SelectSize" value="&SelectSize.label;" accesskey="&SelectSize.accesskey;"/> + <hbox> + <textbox id="SelectSize" class="narrow" oninput="forceInteger(this.id);"/> + </hbox> + </row> + <row> + <spacer/> + <checkbox id="SelectMultiple" flex="1" label="&SelectMultiple.label;" accesskey="&SelectMultiple.accesskey;" oncommand="onSelectMultipleClick();"/> + </row> + <row> + <spacer/> + <checkbox id="SelectDisabled" flex="1" label="&SelectDisabled.label;" accesskey="&SelectDisabled.accesskey;"/> + </row> + <row align="center"> + <label control="SelectTabIndex" value="&SelectTabIndex.label;" accesskey="&SelectTabIndex.accesskey;"/> + <hbox> + <textbox id="SelectTabIndex" class="narrow" oninput="forceInteger(this.id);"/> + </hbox> + </row> + </rows> + </grid> + </groupbox> + + <groupbox flex="1"> + <hbox class="groupbox-title"> + <label class="header">&OptGroup.label;</label> + </hbox> + <grid flex="1"><columns><column flex="1"/><column/></columns> + <rows> + <row align="center"> + <label control="OptGroupLabel" value="&OptGroupLabel.label;" accesskey="&OptGroupLabel.accesskey;"/> + <textbox id="OptGroupLabel" oninput="onLabelInput();"/> + </row> + <row> + <spacer/> + <checkbox id="OptGroupDisabled" label="&OptGroupDisabled.label;" accesskey="&OptGroupDisabled.accesskey;"/> + </row> + </rows> + </grid> + </groupbox> + + <groupbox flex="1"> + <hbox class="groupbox-title"> + <label class="header">&Option.label;</label> + </hbox> + <grid flex="1"><columns><column flex="1"/><column/></columns> + <rows> + <row align="center"> + <label control="OptionText" value="&OptionText.label;" accesskey="&OptionText.accesskey;"/> + <textbox id="OptionText" oninput="onTextInput();"/> + </row> + <row align="center"> + <checkbox id="OptionHasValue" label="&OptionValue.label;" accesskey="&OptionValue.accesskey;" oncommand="onHasValueClick();"/> + <textbox id="OptionValue" oninput="onValueInput();"/> + </row> + <row> + <spacer/> + <checkbox id="OptionSelected" label="&OptionSelected.label;" accesskey="&OptionSelected.accesskey;" oncommand="currentItem.cycleCell();"/> + </row> + <row> + <spacer/> + <checkbox id="OptionDisabled" label="&OptionDisabled.label;" accesskey="&OptionDisabled.accesskey;"/> + </row> + </rows> + </grid> + </groupbox> + </deck> + + <vbox> + <button label="&AddOption.label;" accesskey="&AddOption.accesskey;" oncommand="AddOption();"/> + <button label="&AddOptGroup.label;" accesskey="&AddOptGroup.accesskey;" oncommand="AddOptGroup();"/> + <button id="RemoveButton" label="&RemoveElement.label;" accesskey="&RemoveElement.accesskey;" + oncommand="RemoveElement();" disabled="true"/> + <button id="PreviousButton" label="&MoveElementUp.label;" accesskey="&MoveElementUp.accesskey;" + oncommand="currentItem.moveUp();" disabled="true" type="row"/> + <button id="NextButton" label="&MoveElementDown.label;" accesskey="&MoveElementDown.accesskey;" + oncommand="currentItem.moveDown();" disabled="true" type="row"/> + <spacer flex="1"/> + <button id="AdvancedEditButton" + oncommand="onAdvancedEdit();" + label="&AdvancedEditButton.label;" + accesskey="&AdvancedEditButton.accessKey;" + tooltiptext="&AdvancedEditButton.tooltip;"/> + </vbox> + </hbox> + </vbox> + + <separator class="groove"/> + +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdSnapToGrid.js b/comm/suite/editor/components/dialogs/content/EdSnapToGrid.js new file mode 100644 index 0000000000..ed546d3540 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdSnapToGrid.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var gEditor; + +// dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() +{ + gEditor = GetCurrentEditor(); + if (!gEditor) + { + window.close(); + return; + } + + gEditor instanceof Ci.nsIHTMLAbsPosEditor; + + gDialog.enableSnapToGrid = document.getElementById("enableSnapToGrid"); + gDialog.sizeInput = document.getElementById("size"); + gDialog.sizeLabel = document.getElementById("sizeLabel"); + gDialog.unitLabel = document.getElementById("unitLabel"); + + // Initialize control values based on existing attributes + InitDialog() + + // SET FOCUS TO FIRST CONTROL + SetTextboxFocus(gDialog.sizeInput); + + // Resize window + window.sizeToContent(); + + SetWindowLocation(); +} + +// Set dialog widgets with attribute data +// We get them from globalElement copy so this can be used +// by AdvancedEdit(), which is shared by all property dialogs +function InitDialog() +{ + gDialog.enableSnapToGrid.checked = gEditor.snapToGridEnabled; + toggleSnapToGrid(); + + gDialog.sizeInput.value = gEditor.gridSize; +} + +function onAccept() +{ + gEditor.snapToGridEnabled = gDialog.enableSnapToGrid.checked; + gEditor.gridSize = gDialog.sizeInput.value; +} + +function toggleSnapToGrid() +{ + SetElementEnabledById("size", gDialog.enableSnapToGrid.checked) + SetElementEnabledById("sizeLabel", gDialog.enableSnapToGrid.checked) + SetElementEnabledById("unitLabel", gDialog.enableSnapToGrid.checked) +} diff --git a/comm/suite/editor/components/dialogs/content/EdSnapToGrid.xhtml b/comm/suite/editor/components/dialogs/content/EdSnapToGrid.xhtml new file mode 100644 index 0000000000..9ae6643975 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdSnapToGrid.xhtml @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorSnapToGrid.dtd"> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup()"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <!--- Element-specific methods --> + <script src="chrome://editor/content/EdSnapToGrid.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <checkbox id="enableSnapToGrid" + label="&enableSnapToGrid.label;" + accesskey="&enableSnapToGrid.accessKey;" + oncommand="toggleSnapToGrid();"/> + + <spacer class="spacer"/> + + <grid> + <columns><column/><column/><column /></columns> + <rows> + <row align="center"> + <label value="&sizeEditField.label;" + id="sizeLabel" + control="size" + accesskey="&sizeEditField.accessKey;"/> + <textbox class="narrow" id="size" oninput="forceInteger('size')"/> + <label id="unitLabel" + value="&pixelsLabel.value;" /> + </row> + </rows> + </grid> + + <spacer class="spacer"/> + <separator class="groove"/> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdSpellCheck.js b/comm/suite/editor/components/dialogs/content/EdSpellCheck.js new file mode 100644 index 0000000000..ddb23726cd --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdSpellCheck.js @@ -0,0 +1,495 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../../../mail/base/content/utilityOverlay.js */ +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +var { InlineSpellChecker } = ChromeUtils.import( + "resource://gre/modules/InlineSpellChecker.jsm" +); + +var gMisspelledWord; +var gSpellChecker = null; +var gAllowSelectWord = true; +var gPreviousReplaceWord = ""; +var gFirstTime = true; +var gLastSelectedLang = null; +var gDictCount = 0; + +document.addEventListener("dialogaccept", doDefault); +document.addEventListener("dialogcancel", CancelSpellCheck); + +function Startup() { + var editor = GetCurrentEditor(); + if (!editor) { + window.close(); + return; + } + + // Get the spellChecker shell + gSpellChecker = Cu.createSpellChecker(); + if (!gSpellChecker) { + dump("SpellChecker not found!!!\n"); + window.close(); + return; + } + + // Start the spell checker module. + try { + var skipBlockQuotes = window.arguments[1]; + var enableSelectionChecking = window.arguments[2]; + + gSpellChecker.setFilterType( + skipBlockQuotes + ? Ci.nsIEditorSpellCheck.FILTERTYPE_MAIL + : Ci.nsIEditorSpellCheck.FILTERTYPE_NORMAL + ); + gSpellChecker.InitSpellChecker( + editor, + enableSelectionChecking, + spellCheckStarted + ); + } catch (ex) { + dump("*** Exception error: InitSpellChecker\n"); + window.close(); + } +} + +function spellCheckStarted() { + gDialog.MisspelledWordLabel = document.getElementById("MisspelledWordLabel"); + gDialog.MisspelledWord = document.getElementById("MisspelledWord"); + gDialog.ReplaceButton = document.getElementById("Replace"); + gDialog.IgnoreButton = document.getElementById("Ignore"); + gDialog.StopButton = document.getElementById("Stop"); + gDialog.CloseButton = document.getElementById("Close"); + gDialog.ReplaceWordInput = document.getElementById("ReplaceWordInput"); + gDialog.SuggestedList = document.getElementById("SuggestedList"); + gDialog.LanguageMenulist = document.getElementById("LanguageMenulist"); + + // Fill in the language menulist and sync it up + // with the spellchecker's current language. + + var curLang; + + try { + curLang = gSpellChecker.GetCurrentDictionary(); + } catch (ex) { + curLang = ""; + } + + InitLanguageMenu(curLang); + + // Get the first misspelled word and setup all UI + NextWord(); + + // When startup param is true, setup different UI when spell checking + // just before sending mail message + if (window.arguments[0]) { + // If no misspelled words found, simply close dialog and send message + if (!gMisspelledWord) { + onClose(); + return; + } + + // Hide "Close" button and use "Send" instead + gDialog.CloseButton.hidden = true; + gDialog.CloseButton = document.getElementById("Send"); + gDialog.CloseButton.hidden = false; + } else { + // Normal spell checking - hide the "Stop" button + // (Note that this button is the "Cancel" button for + // Esc keybinding and related window close actions) + gDialog.StopButton.hidden = true; + } + + // Clear flag that determines message when + // no misspelled word is found + // (different message when used for the first time) + gFirstTime = false; + + window.sizeToContent(); +} + +function InitLanguageMenu(aCurLang) { + // Get the list of dictionaries from + // the spellchecker. + + var dictList; + try { + dictList = gSpellChecker.GetDictionaryList(); + } catch (ex) { + dump("Failed to get DictionaryList!\n"); + return; + } + + // If we're not just starting up and dictionary count + // hasn't changed then no need to update the menu. + if (gDictCount == dictList.length) { + return; + } + + // Store current dictionary count. + gDictCount = dictList.length; + + var inlineSpellChecker = new InlineSpellChecker(); + var sortedList = inlineSpellChecker.sortDictionaryList(dictList); + + // Remove any languages from the list. + var languageMenuPopup = gDialog.LanguageMenulist.menupopup; + while (languageMenuPopup.firstChild.localName != "menuseparator") { + languageMenuPopup.firstChild.remove(); + } + + var defaultItem = null; + + for (var i = 0; i < gDictCount; i++) { + let item = document.createXULElement("menuitem"); + item.setAttribute("label", sortedList[i].displayName); + item.setAttribute("value", sortedList[i].localeCode); + let beforeItem = gDialog.LanguageMenulist.getItemAtIndex(i); + languageMenuPopup.insertBefore(item, beforeItem); + + if (aCurLang && sortedList[i].localeCode == aCurLang) { + defaultItem = item; + } + } + + // Now make sure the correct item in the menu list is selected. + if (defaultItem) { + gDialog.LanguageMenulist.selectedItem = defaultItem; + gLastSelectedLang = defaultItem; + } +} + +function DoEnabling() { + if (!gMisspelledWord) { + // No more misspelled words + gDialog.MisspelledWord.setAttribute( + "value", + GetString(gFirstTime ? "NoMisspelledWord" : "CheckSpellingDone") + ); + + gDialog.ReplaceButton.removeAttribute("default"); + gDialog.IgnoreButton.removeAttribute("default"); + + gDialog.CloseButton.setAttribute("default", "true"); + // Shouldn't have to do this if "default" is true? + gDialog.CloseButton.focus(); + + SetElementEnabledById("MisspelledWordLabel", false); + SetElementEnabledById("ReplaceWordLabel", false); + SetElementEnabledById("ReplaceWordInput", false); + SetElementEnabledById("CheckWord", false); + SetElementEnabledById("SuggestedListLabel", false); + SetElementEnabledById("SuggestedList", false); + SetElementEnabledById("Ignore", false); + SetElementEnabledById("IgnoreAll", false); + SetElementEnabledById("Replace", false); + SetElementEnabledById("ReplaceAll", false); + SetElementEnabledById("AddToDictionary", false); + } else { + SetElementEnabledById("MisspelledWordLabel", true); + SetElementEnabledById("ReplaceWordLabel", true); + SetElementEnabledById("ReplaceWordInput", true); + SetElementEnabledById("CheckWord", true); + SetElementEnabledById("SuggestedListLabel", true); + SetElementEnabledById("SuggestedList", true); + SetElementEnabledById("Ignore", true); + SetElementEnabledById("IgnoreAll", true); + SetElementEnabledById("AddToDictionary", true); + + gDialog.CloseButton.removeAttribute("default"); + SetReplaceEnable(); + } +} + +function NextWord() { + gMisspelledWord = gSpellChecker.GetNextMisspelledWord(); + SetWidgetsForMisspelledWord(); +} + +function SetWidgetsForMisspelledWord() { + gDialog.MisspelledWord.setAttribute("value", gMisspelledWord); + + // Initial replace word is misspelled word + gDialog.ReplaceWordInput.value = gMisspelledWord; + gPreviousReplaceWord = gMisspelledWord; + + // This sets gDialog.ReplaceWordInput to first suggested word in list + FillSuggestedList(gMisspelledWord); + + DoEnabling(); + + if (gMisspelledWord) { + SetTextboxFocus(gDialog.ReplaceWordInput); + } +} + +function CheckWord() { + var word = gDialog.ReplaceWordInput.value; + if (word) { + if (gSpellChecker.CheckCurrentWord(word)) { + FillSuggestedList(word); + SetReplaceEnable(); + } else { + ClearListbox(gDialog.SuggestedList); + var item = gDialog.SuggestedList.appendItem( + GetString("CorrectSpelling"), + "" + ); + if (item) { + item.setAttribute("disabled", "true"); + } + // Suppress being able to select the message text + gAllowSelectWord = false; + } + } +} + +function SelectSuggestedWord() { + if (gAllowSelectWord) { + if (gDialog.SuggestedList.selectedItem) { + var selValue = gDialog.SuggestedList.selectedItem.label; + gDialog.ReplaceWordInput.value = selValue; + gPreviousReplaceWord = selValue; + } else { + gDialog.ReplaceWordInput.value = gPreviousReplaceWord; + } + SetReplaceEnable(); + } +} + +function ChangeReplaceWord() { + // Calling this triggers SelectSuggestedWord(), + // so temporarily suppress the effect of that + var saveAllow = gAllowSelectWord; + gAllowSelectWord = false; + + // Select matching word in list + var newSelectedItem; + var replaceWord = TrimString(gDialog.ReplaceWordInput.value); + if (replaceWord) { + for (var i = 0; i < gDialog.SuggestedList.getRowCount(); i++) { + var item = gDialog.SuggestedList.getItemAtIndex(i); + if (item.label == replaceWord) { + newSelectedItem = item; + break; + } + } + } + gDialog.SuggestedList.selectedItem = newSelectedItem; + + gAllowSelectWord = saveAllow; + + // Remember the new word + gPreviousReplaceWord = gDialog.ReplaceWordInput.value; + + SetReplaceEnable(); +} + +function Ignore() { + NextWord(); +} + +function IgnoreAll() { + if (gMisspelledWord) { + gSpellChecker.IgnoreWordAllOccurrences(gMisspelledWord); + } + NextWord(); +} + +function Replace(newWord) { + if (!newWord) { + return; + } + + if (gMisspelledWord && gMisspelledWord != newWord) { + var editor = GetCurrentEditor(); + editor.beginTransaction(); + try { + gSpellChecker.ReplaceWord(gMisspelledWord, newWord, false); + } catch (e) {} + editor.endTransaction(); + } + NextWord(); +} + +function ReplaceAll() { + var newWord = gDialog.ReplaceWordInput.value; + if (gMisspelledWord && gMisspelledWord != newWord) { + var editor = GetCurrentEditor(); + editor.beginTransaction(); + try { + gSpellChecker.ReplaceWord(gMisspelledWord, newWord, true); + } catch (e) {} + editor.endTransaction(); + } + NextWord(); +} + +function AddToDictionary() { + if (gMisspelledWord) { + gSpellChecker.AddWordToDictionary(gMisspelledWord); + } + NextWord(); +} + +function EditDictionary() { + window.openDialog( + "chrome://editor/content/EdDictionary.xhtml", + "_blank", + "chrome,close,titlebar,modal", + "", + gMisspelledWord + ); +} + +function SelectLanguage() { + var item = gDialog.LanguageMenulist.selectedItem; + if (item.value != "more-cmd") { + gSpellChecker.SetCurrentDictionary(item.value); + // For compose windows we need to set the "lang" attribute so the + // core editor uses the correct dictionary for the inline spell check. + if (window.arguments[1]) { + if ("ComposeChangeLanguage" in window.opener) { + // We came here from a compose window. + window.opener.ComposeChangeLanguage(item.value); + } else { + window.opener.document.documentElement.setAttribute("lang", item.value); + } + } + gLastSelectedLang = item; + } else { + openDictionaryList(); + + if (gLastSelectedLang) { + gDialog.LanguageMenulist.selectedItem = gLastSelectedLang; + } + } +} + +function Recheck() { + var recheckLanguage; + + function finishRecheck() { + gSpellChecker.SetCurrentDictionary(recheckLanguage); + gMisspelledWord = gSpellChecker.GetNextMisspelledWord(); + SetWidgetsForMisspelledWord(); + } + + // TODO: Should we bother to add a "Recheck" method to interface? + try { + recheckLanguage = gSpellChecker.GetCurrentDictionary(); + gSpellChecker.UninitSpellChecker(); + // Clear the ignore all list. + Cc["@mozilla.org/spellchecker/personaldictionary;1"] + .getService(Ci.mozIPersonalDictionary) + .endSession(); + gSpellChecker.InitSpellChecker(GetCurrentEditor(), false, finishRecheck); + } catch (ex) { + Cu.reportError(ex); + } +} + +function FillSuggestedList(misspelledWord) { + var list = gDialog.SuggestedList; + + // Clear the current contents of the list + gAllowSelectWord = false; + ClearListbox(list); + var item; + + if (misspelledWord.length > 0) { + // Get suggested words until an empty string is returned + var count = 0; + do { + var word = gSpellChecker.GetSuggestedWord(); + if (word.length > 0) { + list.appendItem(word, ""); + count++; + } + } while (word.length > 0); + + if (count == 0) { + // No suggestions - show a message but don't let user select it + item = list.appendItem(GetString("NoSuggestedWords")); + if (item) { + item.setAttribute("disabled", "true"); + } + gAllowSelectWord = false; + } else { + gAllowSelectWord = true; + // Initialize with first suggested list by selecting it + gDialog.SuggestedList.selectedIndex = 0; + } + } else { + item = list.appendItem("", ""); + if (item) { + item.setAttribute("disabled", "true"); + } + } +} + +function SetReplaceEnable() { + // Enable "Change..." buttons only if new word is different than misspelled + var newWord = gDialog.ReplaceWordInput.value; + var enable = newWord.length > 0 && newWord != gMisspelledWord; + SetElementEnabledById("Replace", enable); + SetElementEnabledById("ReplaceAll", enable); + if (enable) { + gDialog.ReplaceButton.setAttribute("default", "true"); + gDialog.IgnoreButton.removeAttribute("default"); + } else { + gDialog.IgnoreButton.setAttribute("default", "true"); + gDialog.ReplaceButton.removeAttribute("default"); + } +} + +function doDefault(event) { + if (gDialog.ReplaceButton.getAttribute("default") == "true") { + Replace(gDialog.ReplaceWordInput.value); + } else if (gDialog.IgnoreButton.getAttribute("default") == "true") { + Ignore(); + } else if (gDialog.CloseButton.getAttribute("default") == "true") { + onClose(); + } + + event.preventDefault(); +} + +function ExitSpellChecker() { + if (gSpellChecker) { + try { + gSpellChecker.UninitSpellChecker(); + // now check the document over again with the new dictionary + // if we have an inline spellchecker + if ( + "InlineSpellCheckerUI" in window.opener && + window.opener.InlineSpellCheckerUI.enabled + ) { + window.opener.InlineSpellCheckerUI.mInlineSpellChecker.spellCheckRange( + null + ); + } + } finally { + gSpellChecker = null; + } + } +} + +function CancelSpellCheck() { + ExitSpellChecker(); + + // Signal to calling window that we canceled + window.opener.cancelSendMessage = true; +} + +function onClose() { + ExitSpellChecker(); + + window.opener.cancelSendMessage = false; + window.close(); +} diff --git a/comm/suite/editor/components/dialogs/content/EdSpellCheck.xhtml b/comm/suite/editor/components/dialogs/content/EdSpellCheck.xhtml new file mode 100644 index 0000000000..4cb9f49198 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdSpellCheck.xhtml @@ -0,0 +1,113 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorSpellCheck.dtd"> + +<!-- dialog containing a control requiring initial setup --> +<dialog id="spellCheckDlg" buttons="cancel" title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + persist="screenX screenY" + onload="Startup()"> + + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://communicator/content/utilityOverlay.js"/> + <script src="chrome://editor/content/EdSpellCheck.js"/> + <script src="chrome://global/content/contentAreaUtils.js"/> + + <stringbundle id="languageBundle" src="chrome://global/locale/languageNames.properties"/> + <stringbundle id="regionBundle" src="chrome://global/locale/regionNames.properties"/> + + <grid> + <columns> + <column class="spell-check"/> + <column class="spell-check" flex="1"/> + <column class="spell-check"/> + </columns> + <rows> + <row align="center"> + <label id="MisspelledWordLabel" value="&misspelledWord.label;"/> + <label class="bold" id="MisspelledWord" crop="end"/> + <button class="spell-check" label="&recheckButton2.label;" oncommand="Recheck();" + accesskey="&recheckButton2.accessKey;"/> + </row> + <row align="center"> + <label id="ReplaceWordLabel" value="&wordEditField.label;" + control="ReplaceWordInput" + accesskey="&wordEditField.accessKey;"/> + <textbox id="ReplaceWordInput" oninput="ChangeReplaceWord()" flex="1"/> + <button id="CheckWord" oncommand="CheckWord()" label="&checkwordButton.label;" + accesskey="&checkwordButton.accessKey;"/> + </row> + </rows> + </grid> + <label id="SuggestedListLabel" value="&suggestions.label;" + control="SuggestedList" + accesskey="&suggestions.accessKey;"/> + <grid flex="1"> + <columns><column flex="1"/><column/></columns> + <rows> + <row flex="1"> + <!-- BUG! setting class="MinWidth20em" on tree doesn't work (width=0) --> + <richlistbox id="SuggestedList" + class="theme-listbox" + onselect="SelectSuggestedWord()" + ondblclick="if (gAllowSelectWord) { Replace(event.target.value); }"/> + <vbox> + <grid> + <columns><column class="spell-check" flex="1"/><column class="spell-check" flex="1"/></columns> + <rows> + <row> + <button id="Replace" label="&replaceButton.label;" + oncommand="Replace(gDialog.ReplaceWordInput.value);" + accesskey="&replaceButton.accessKey;"/> + <button id="Ignore" oncommand="Ignore();" label="&ignoreButton.label;" + accesskey="&ignoreButton.accessKey;"/> + </row> + <row> + <button id="ReplaceAll" oncommand="ReplaceAll();" label="&replaceAllButton.label;" + accesskey="&replaceAllButton.accessKey;"/> + <button id="IgnoreAll" oncommand="IgnoreAll();" label="&ignoreAllButton.label;" + accesskey="&ignoreAllButton.accessKey;"/> + </row> + </rows> + </grid> + <separator/> + <label value="&userDictionary.label;"/> + <hbox align="start"> + <button class="spell-check" id="AddToDictionary" oncommand="AddToDictionary()" label="&addToUserDictionaryButton.label;" + accesskey="&addToUserDictionaryButton.accessKey;"/> + <button class="spell-check" id="EditDictionary" oncommand="EditDictionary()" label="&editUserDictionaryButton.label;" + accesskey="&editUserDictionaryButton.accessKey;"/> + </hbox> + </vbox> + </row> + <label value ="&languagePopup.label;" + control="LanguageMenulist" + accesskey="&languagePopup.accessKey;"/> + <row> + <menulist id="LanguageMenulist" oncommand="SelectLanguage()"> + <menupopup onpopupshowing="InitLanguageMenu(gDialog.LanguageMenulist.selectedItem.value);"> + <!-- dynamic content populated by JS --> + <menuseparator/> + <menuitem value="more-cmd" label="&moreDictionaries.label;"/> + </menupopup> + </menulist> + <hbox flex="1"> + <button class="spell-check" dlgtype="cancel" id="Stop" label="&stopButton.label;" oncommand="CancelSpellCheck();" + accesskey="&stopButton.accessKey;"/> + <spacer flex="1"/> + <button class="spell-check" id="Close" label="&closeButton.label;" oncommand="onClose();" + accesskey="&closeButton.accessKey;"/> + <button class="spell-check" id="Send" label="&sendButton.label;" oncommand="onClose();" + accesskey="&sendButton.accessKey;" hidden="true"/> + </hbox> + </row> + </rows> + </grid> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdTableProps.js b/comm/suite/editor/components/dialogs/content/EdTableProps.js new file mode 100644 index 0000000000..e78a89bc41 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdTableProps.js @@ -0,0 +1,1439 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +// Cancel() is in EdDialogCommon.js + +var gTableElement; +var gCellElement; +var gTableCaptionElement; +var globalCellElement; +var globalTableElement; +var gValidateTab; +const defHAlign = "left"; +const centerStr = "center"; // Index=1 +const rightStr = "right"; // 2 +const justifyStr = "justify"; // 3 +const charStr = "char"; // 4 +const defVAlign = "middle"; +const topStr = "top"; +const bottomStr = "bottom"; +const bgcolor = "bgcolor"; +var gTableColor; +var gCellColor; + +const cssBackgroundColorStr = "background-color"; + +var gRowCount = 1; +var gColCount = 1; +var gLastRowIndex; +var gLastColIndex; +var gNewRowCount; +var gNewColCount; +var gCurRowIndex; +var gCurColIndex; +var gCurColSpan; +var gSelectedCellsType = 1; +const SELECT_CELL = 1; +const SELECT_ROW = 2; +const SELECT_COLUMN = 3; +const RESET_SELECTION = 0; +var gCellData = { + value: null, + startRowIndex: 0, + startColIndex: 0, + rowSpan: 0, + colSpan: 0, + actualRowSpan: 0, + actualColSpan: 0, + isSelected: false, +}; +var gAdvancedEditUsed; +var gAlignWasChar = false; + +/* +From C++: + 0 TABLESELECTION_TABLE + 1 TABLESELECTION_CELL There are 1 or more cells selected + but complete rows or columns are not selected + 2 TABLESELECTION_ROW All cells are in 1 or more rows + and in each row, all cells selected + Note: This is the value if all rows (thus all cells) are selected + 3 TABLESELECTION_COLUMN All cells are in 1 or more columns +*/ + +var gSelectedCellCount = 0; +var gApplyUsed = false; +var gSelection; +var gCellDataChanged = false; +var gCanDelete = false; +var gUseCSS = true; +var gActiveEditor; + +// dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogextra1", Apply); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + gActiveEditor = GetCurrentTableEditor(); + if (!gActiveEditor) { + window.close(); + return; + } + + try { + gSelection = gActiveEditor.selection; + } catch (e) {} + if (!gSelection) { + return; + } + + // Get dialog widgets - Table Panel + gDialog.TableRowsInput = document.getElementById("TableRowsInput"); + gDialog.TableColumnsInput = document.getElementById("TableColumnsInput"); + gDialog.TableWidthInput = document.getElementById("TableWidthInput"); + gDialog.TableWidthUnits = document.getElementById("TableWidthUnits"); + gDialog.TableHeightInput = document.getElementById("TableHeightInput"); + gDialog.TableHeightUnits = document.getElementById("TableHeightUnits"); + try { + if ( + !Services.prefs.getBoolPref("editor.use_css") || + gActiveEditor.flags & 1 + ) { + gUseCSS = false; + var tableHeightLabel = document.getElementById("TableHeightLabel"); + tableHeightLabel.remove(); + gDialog.TableHeightInput.remove(); + gDialog.TableHeightUnits.remove(); + } + } catch (e) {} + gDialog.BorderWidthInput = document.getElementById("BorderWidthInput"); + gDialog.SpacingInput = document.getElementById("SpacingInput"); + gDialog.PaddingInput = document.getElementById("PaddingInput"); + gDialog.TableAlignList = document.getElementById("TableAlignList"); + gDialog.TableCaptionList = document.getElementById("TableCaptionList"); + gDialog.TableInheritColor = document.getElementById("TableInheritColor"); + gDialog.TabBox = document.getElementById("TabBox"); + + // Cell Panel + gDialog.SelectionList = document.getElementById("SelectionList"); + gDialog.PreviousButton = document.getElementById("PreviousButton"); + gDialog.NextButton = document.getElementById("NextButton"); + // Currently, we always apply changes and load new attributes when changing selection + // (Let's keep this for possible future use) + // gDialog.ApplyBeforeMove = document.getElementById("ApplyBeforeMove"); + // gDialog.KeepCurrentData = document.getElementById("KeepCurrentData"); + + gDialog.CellHeightInput = document.getElementById("CellHeightInput"); + gDialog.CellHeightUnits = document.getElementById("CellHeightUnits"); + gDialog.CellWidthInput = document.getElementById("CellWidthInput"); + gDialog.CellWidthUnits = document.getElementById("CellWidthUnits"); + gDialog.CellHAlignList = document.getElementById("CellHAlignList"); + gDialog.CellVAlignList = document.getElementById("CellVAlignList"); + gDialog.CellInheritColor = document.getElementById("CellInheritColor"); + gDialog.CellStyleList = document.getElementById("CellStyleList"); + gDialog.TextWrapList = document.getElementById("TextWrapList"); + + // In cell panel, user must tell us which attributes to apply via checkboxes, + // else we would apply values from one cell to ALL in selection + // and that's probably not what they expect! + gDialog.CellHeightCheckbox = document.getElementById("CellHeightCheckbox"); + gDialog.CellWidthCheckbox = document.getElementById("CellWidthCheckbox"); + gDialog.CellHAlignCheckbox = document.getElementById("CellHAlignCheckbox"); + gDialog.CellVAlignCheckbox = document.getElementById("CellVAlignCheckbox"); + gDialog.CellStyleCheckbox = document.getElementById("CellStyleCheckbox"); + gDialog.TextWrapCheckbox = document.getElementById("TextWrapCheckbox"); + gDialog.CellColorCheckbox = document.getElementById("CellColorCheckbox"); + gDialog.TableTab = document.getElementById("TableTab"); + gDialog.CellTab = document.getElementById("CellTab"); + gDialog.AdvancedEditCell = document.getElementById("AdvancedEditButton2"); + // Save "normal" tooltip message for Advanced Edit button + gDialog.AdvancedEditCellToolTipText = gDialog.AdvancedEditCell.getAttribute( + "tooltiptext" + ); + + try { + gTableElement = gActiveEditor.getElementOrParentByTagName("table", null); + } catch (e) {} + if (!gTableElement) { + dump("Failed to get table element!\n"); + window.close(); + return; + } + globalTableElement = gTableElement.cloneNode(false); + + var tagNameObj = { value: "" }; + var countObj = { value: 0 }; + var tableOrCellElement; + try { + tableOrCellElement = gActiveEditor.getSelectedOrParentTableElement( + tagNameObj, + countObj + ); + } catch (e) {} + + if (tagNameObj.value == "td") { + // We are in a cell + gSelectedCellCount = countObj.value; + gCellElement = tableOrCellElement; + globalCellElement = gCellElement.cloneNode(false); + + // Tells us whether cell, row, or column is selected + try { + gSelectedCellsType = gActiveEditor.getSelectedCellsType(gTableElement); + } catch (e) {} + + // Ignore types except Cell, Row, and Column + if ( + gSelectedCellsType < SELECT_CELL || + gSelectedCellsType > SELECT_COLUMN + ) { + gSelectedCellsType = SELECT_CELL; + } + + // Be sure at least 1 cell is selected. + // (If the count is 0, then we were inside the cell.) + if (gSelectedCellCount == 0) { + DoCellSelection(); + } + + // Get location in the cell map + var rowIndexObj = { value: 0 }; + var colIndexObj = { value: 0 }; + try { + gActiveEditor.getCellIndexes(gCellElement, rowIndexObj, colIndexObj); + } catch (e) {} + gCurRowIndex = rowIndexObj.value; + gCurColIndex = colIndexObj.value; + + // We save the current colspan to quickly + // move selection from from cell to cell + if (GetCellData(gCurRowIndex, gCurColIndex)) { + gCurColSpan = gCellData.colSpan; + } + + // Starting TabPanel name is passed in + if (window.arguments[1] == "CellPanel") { + gDialog.TabBox.selectedTab = gDialog.CellTab; + } + } + + if (gDialog.TabBox.selectedTab == gDialog.TableTab) { + // We may call this with table selected, but no cell, + // so disable the Cell Properties tab + if (!gCellElement) { + // XXX: Disabling of tabs is currently broken, so for + // now we'll just remove the tab completely. + // gDialog.CellTab.disabled = true; + gDialog.CellTab.remove(); + } + } + + // Note: we must use gTableElement, not globalTableElement for these, + // thus we should not put this in InitDialog. + // Instead, monitor desired counts with separate globals + var rowCountObj = { value: 0 }; + var colCountObj = { value: 0 }; + try { + gActiveEditor.getTableSize(gTableElement, rowCountObj, colCountObj); + } catch (e) {} + + gRowCount = rowCountObj.value; + gLastRowIndex = gRowCount - 1; + gColCount = colCountObj.value; + gLastColIndex = gColCount - 1; + + // Set appropriate icons and enable state for the Previous/Next buttons + SetSelectionButtons(); + + // If only one cell in table, disable change-selection widgets + if (gRowCount == 1 && gColCount == 1) { + gDialog.SelectionList.setAttribute("disabled", "true"); + } + + // User can change these via textboxes + gNewRowCount = gRowCount; + gNewColCount = gColCount; + + // This flag is used to control whether set check state + // on "set attribute" checkboxes + // (Advanced Edit dialog use calls InitDialog when done) + gAdvancedEditUsed = false; + InitDialog(); + gAdvancedEditUsed = true; + + // If first initializing, we really aren't changing anything + gCellDataChanged = false; + + SetWindowLocation(); +} + +function InitDialog() { + // Get Table attributes + gDialog.TableRowsInput.value = gRowCount; + gDialog.TableColumnsInput.value = gColCount; + gDialog.TableWidthInput.value = InitPixelOrPercentMenulist( + globalTableElement, + gTableElement, + "width", + "TableWidthUnits", + gPercent + ); + if (gUseCSS) { + gDialog.TableHeightInput.value = InitPixelOrPercentMenulist( + globalTableElement, + gTableElement, + "height", + "TableHeightUnits", + gPercent + ); + } + gDialog.BorderWidthInput.value = globalTableElement.border; + gDialog.SpacingInput.value = globalTableElement.cellSpacing; + gDialog.PaddingInput.value = globalTableElement.cellPadding; + + var marginLeft = GetHTMLOrCSSStyleValue( + globalTableElement, + "align", + "margin-left" + ); + var marginRight = GetHTMLOrCSSStyleValue( + globalTableElement, + "align", + "margin-right" + ); + var halign = marginLeft.toLowerCase() + " " + marginRight.toLowerCase(); + if (halign == "center center" || halign == "auto auto") { + gDialog.TableAlignList.value = "center"; + } else if (halign == "right right" || halign == "auto 0px") { + gDialog.TableAlignList.value = "right"; + } else { + // Default is left. + gDialog.TableAlignList.value = "left"; + } + + // Be sure to get caption from table in doc, not the copied "globalTableElement" + gTableCaptionElement = gTableElement.caption; + if (gTableCaptionElement) { + var align = GetHTMLOrCSSStyleValue( + gTableCaptionElement, + "align", + "caption-side" + ); + if (align != "bottom" && align != "left" && align != "right") { + align = "top"; + } + gDialog.TableCaptionList.value = align; + } + + gTableColor = GetHTMLOrCSSStyleValue( + globalTableElement, + bgcolor, + cssBackgroundColorStr + ); + gTableColor = ConvertRGBColorIntoHEXColor(gTableColor); + SetColor("tableBackgroundCW", gTableColor); + + InitCellPanel(); +} + +function InitCellPanel() { + // Get cell attributes + if (globalCellElement) { + // This assumes order of items is Cell, Row, Column + gDialog.SelectionList.value = gSelectedCellsType; + + var previousValue = gDialog.CellHeightInput.value; + gDialog.CellHeightInput.value = InitPixelOrPercentMenulist( + globalCellElement, + gCellElement, + "height", + "CellHeightUnits", + gPixel + ); + gDialog.CellHeightCheckbox.checked = + gAdvancedEditUsed && previousValue != gDialog.CellHeightInput.value; + + previousValue = gDialog.CellWidthInput.value; + gDialog.CellWidthInput.value = InitPixelOrPercentMenulist( + globalCellElement, + gCellElement, + "width", + "CellWidthUnits", + gPixel + ); + gDialog.CellWidthCheckbox.checked = + gAdvancedEditUsed && previousValue != gDialog.CellWidthInput.value; + + var previousIndex = gDialog.CellVAlignList.selectedIndex; + var valign = GetHTMLOrCSSStyleValue( + globalCellElement, + "valign", + "vertical-align" + ).toLowerCase(); + if (valign == topStr || valign == bottomStr) { + gDialog.CellVAlignList.value = valign; + } else { + // Default is middle. + gDialog.CellVAlignList.value = defVAlign; + } + + gDialog.CellVAlignCheckbox.checked = + gAdvancedEditUsed && + previousIndex != gDialog.CellVAlignList.selectedIndex; + + previousIndex = gDialog.CellHAlignList.selectedIndex; + + gAlignWasChar = false; + + var halign = GetHTMLOrCSSStyleValue( + globalCellElement, + "align", + "text-align" + ).toLowerCase(); + switch (halign) { + case centerStr: + case rightStr: + case justifyStr: + gDialog.CellHAlignList.value = halign; + break; + case charStr: + // We don't support UI for this because layout doesn't work: bug 2212. + // Remember that's what they had so we don't change it + // unless they change the alignment by using the menulist + gAlignWasChar = true; + // Fall through to use show default alignment in menu + default: + // Default depends on cell type (TH is "center", TD is "left") + gDialog.CellHAlignList.value = + globalCellElement.nodeName.toLowerCase() == "th" ? "center" : "left"; + break; + } + + gDialog.CellHAlignCheckbox.checked = + gAdvancedEditUsed && + previousIndex != gDialog.CellHAlignList.selectedIndex; + + previousIndex = gDialog.CellStyleList.selectedIndex; + gDialog.CellStyleList.value = globalCellElement.nodeName.toLowerCase(); + gDialog.CellStyleCheckbox.checked = + gAdvancedEditUsed && previousIndex != gDialog.CellStyleList.selectedIndex; + + previousIndex = gDialog.TextWrapList.selectedIndex; + if ( + GetHTMLOrCSSStyleValue(globalCellElement, "nowrap", "white-space") == + "nowrap" + ) { + gDialog.TextWrapList.value = "nowrap"; + } else { + gDialog.TextWrapList.value = "wrap"; + } + gDialog.TextWrapCheckbox.checked = + gAdvancedEditUsed && previousIndex != gDialog.TextWrapList.selectedIndex; + + previousValue = gCellColor; + gCellColor = GetHTMLOrCSSStyleValue( + globalCellElement, + bgcolor, + cssBackgroundColorStr + ); + gCellColor = ConvertRGBColorIntoHEXColor(gCellColor); + SetColor("cellBackgroundCW", gCellColor); + gDialog.CellColorCheckbox.checked = + gAdvancedEditUsed && previousValue != gCellColor; + + // We want to set this true in case changes came + // from Advanced Edit dialog session (must assume something changed) + gCellDataChanged = true; + } +} + +function GetCellData(rowIndex, colIndex) { + // Get actual rowspan and colspan + var startRowIndexObj = { value: 0 }; + var startColIndexObj = { value: 0 }; + var rowSpanObj = { value: 0 }; + var colSpanObj = { value: 0 }; + var actualRowSpanObj = { value: 0 }; + var actualColSpanObj = { value: 0 }; + var isSelectedObj = { value: false }; + + try { + gActiveEditor.getCellDataAt( + gTableElement, + rowIndex, + colIndex, + gCellData, + startRowIndexObj, + startColIndexObj, + rowSpanObj, + colSpanObj, + actualRowSpanObj, + actualColSpanObj, + isSelectedObj + ); + // We didn't find a cell + if (!gCellData.value) { + return false; + } + } catch (ex) { + return false; + } + + gCellData.startRowIndex = startRowIndexObj.value; + gCellData.startColIndex = startColIndexObj.value; + gCellData.rowSpan = rowSpanObj.value; + gCellData.colSpan = colSpanObj.value; + gCellData.actualRowSpan = actualRowSpanObj.value; + gCellData.actualColSpan = actualColSpanObj.value; + gCellData.isSelected = isSelectedObj.value; + return true; +} + +function SelectCellHAlign() { + SetCheckbox("CellHAlignCheckbox"); + // Once user changes the alignment, + // we lose their original "CharAt" alignment" + gAlignWasChar = false; +} + +function GetColorAndUpdate(ColorWellID) { + var colorWell = document.getElementById(ColorWellID); + if (!colorWell) { + return; + } + + var colorObj = { + Type: "", + TableColor: 0, + CellColor: 0, + NoDefault: false, + Cancel: false, + BackgroundColor: 0, + }; + + switch (ColorWellID) { + case "tableBackgroundCW": + colorObj.Type = "Table"; + colorObj.TableColor = gTableColor; + break; + case "cellBackgroundCW": + colorObj.Type = "Cell"; + colorObj.CellColor = gCellColor; + break; + } + window.openDialog( + "chrome://editor/content/EdColorPicker.xhtml", + "_blank", + "chrome,close,titlebar,modal", + "", + colorObj + ); + + // User canceled the dialog + if (colorObj.Cancel) { + return; + } + + switch (ColorWellID) { + case "tableBackgroundCW": + gTableColor = colorObj.BackgroundColor; + SetColor(ColorWellID, gTableColor); + break; + case "cellBackgroundCW": + gCellColor = colorObj.BackgroundColor; + SetColor(ColorWellID, gCellColor); + SetCheckbox("CellColorCheckbox"); + break; + } +} + +function SetColor(ColorWellID, color) { + // Save the color + if (ColorWellID == "cellBackgroundCW") { + if (color) { + try { + gActiveEditor.setAttributeOrEquivalent( + globalCellElement, + bgcolor, + color, + true + ); + } catch (e) {} + gDialog.CellInheritColor.collapsed = true; + } else { + try { + gActiveEditor.removeAttributeOrEquivalent( + globalCellElement, + bgcolor, + true + ); + } catch (e) {} + // Reveal addition message explaining "default" color + gDialog.CellInheritColor.collapsed = false; + } + } else { + if (color) { + try { + gActiveEditor.setAttributeOrEquivalent( + globalTableElement, + bgcolor, + color, + true + ); + } catch (e) {} + gDialog.TableInheritColor.collapsed = true; + } else { + try { + gActiveEditor.removeAttributeOrEquivalent( + globalTableElement, + bgcolor, + true + ); + } catch (e) {} + gDialog.TableInheritColor.collapsed = false; + } + SetCheckbox("CellColorCheckbox"); + } + + setColorWell(ColorWellID, color); +} + +function ChangeSelectionToFirstCell() { + if (!GetCellData(0, 0)) { + dump("Can't find first cell in table!\n"); + return; + } + gCellElement = gCellData.value; + globalCellElement = gCellElement; + + gCurRowIndex = 0; + gCurColIndex = 0; + ChangeSelection(RESET_SELECTION); +} + +function ChangeSelection(newType) { + newType = Number(newType); + + if (gSelectedCellsType == newType) { + return; + } + + if (newType == RESET_SELECTION) { + // Restore selection to existing focus cell + gSelection.collapse(gCellElement, 0); + } else { + gSelectedCellsType = newType; + } + + // Keep the same focus gCellElement, just change the type + DoCellSelection(); + SetSelectionButtons(); + + // Note: globalCellElement should still be a clone of gCellElement +} + +function MoveSelection(forward) { + var newRowIndex = gCurRowIndex; + var newColIndex = gCurColIndex; + var inRow = false; + + if (gSelectedCellsType == SELECT_ROW) { + newRowIndex += forward ? 1 : -1; + + // Wrap around if before first or after last row + if (newRowIndex < 0) { + newRowIndex = gLastRowIndex; + } else if (newRowIndex > gLastRowIndex) { + newRowIndex = 0; + } + inRow = true; + + // Use first cell in row for focus cell + newColIndex = 0; + } else { + // Cell or column: + if (!forward) { + newColIndex--; + } + + if (gSelectedCellsType == SELECT_CELL) { + // Skip to next cell + if (forward) { + newColIndex += gCurColSpan; + } + } else { + // SELECT_COLUMN + // Use first cell in column for focus cell + newRowIndex = 0; + + // Don't skip by colspan, + // but find first cell in next cellmap column + if (forward) { + newColIndex++; + } + } + + if (newColIndex < 0) { + // Request is before the first cell in column + + // Wrap to last cell in column + newColIndex = gLastColIndex; + + if (gSelectedCellsType == SELECT_CELL) { + // If moving by cell, also wrap to previous... + if (newRowIndex > 0) { + newRowIndex -= 1; + } else { + // ...or the last row. + newRowIndex = gLastRowIndex; + } + + inRow = true; + } + } else if (newColIndex > gLastColIndex) { + // Request is after the last cell in column + + // Wrap to first cell in column + newColIndex = 0; + + if (gSelectedCellsType == SELECT_CELL) { + // If moving by cell, also wrap to next... + if (newRowIndex < gLastRowIndex) { + newRowIndex++; + } else { + // ...or the first row. + newRowIndex = 0; + } + + inRow = true; + } + } + } + + // Get the cell at the new location + do { + if (!GetCellData(newRowIndex, newColIndex)) { + dump("MoveSelection: CELL NOT FOUND\n"); + return; + } + if (inRow) { + if (gCellData.startRowIndex == newRowIndex) { + break; + } else { + // Cell spans from a row above, look for the next cell in row. + newRowIndex += gCellData.actualRowSpan; + } + } else if (gCellData.startColIndex == newColIndex) { + break; + } else { + // Cell spans from a Col above, look for the next cell in column + newColIndex += gCellData.actualColSpan; + } + } while (true); + + // Save data for current selection before changing + if (gCellDataChanged) { + // && gDialog.ApplyBeforeMove.checked) + if (!ValidateCellData()) { + return; + } + + gActiveEditor.beginTransaction(); + // Apply changes to all selected cells + ApplyCellAttributes(); + gActiveEditor.endTransaction(); + + SetCloseButton(); + } + + // Set cell and other data for new selection + gCellElement = gCellData.value; + + // Save globals for new current cell + gCurRowIndex = gCellData.startRowIndex; + gCurColIndex = gCellData.startColIndex; + gCurColSpan = gCellData.actualColSpan; + + // Copy for new global cell + globalCellElement = gCellElement.cloneNode(false); + + // Change the selection + DoCellSelection(); + + // Scroll page so new selection is visible + // Using SELECTION_ANCHOR_REGION makes the upper-left corner of first selected cell + // the point to bring into view. + try { + var selectionController = gActiveEditor.selectionController; + selectionController.scrollSelectionIntoView( + selectionController.SELECTION_NORMAL, + selectionController.SELECTION_ANCHOR_REGION, + true + ); + } catch (e) {} + + // Reinitialize dialog using new cell + // if (!gDialog.KeepCurrentData.checked) + // Setting this false unchecks all "set attributes" checkboxes + gAdvancedEditUsed = false; + InitCellPanel(); + gAdvancedEditUsed = true; +} + +function DoCellSelection() { + // Collapse selection into to the focus cell + // so editor uses that as start cell + gSelection.collapse(gCellElement, 0); + + var tagNameObj = { value: "" }; + var countObj = { value: 0 }; + try { + switch (gSelectedCellsType) { + case SELECT_CELL: + gActiveEditor.selectTableCell(); + break; + case SELECT_ROW: + gActiveEditor.selectTableRow(); + break; + default: + gActiveEditor.selectTableColumn(); + break; + } + // Get number of cells selected + gActiveEditor.getSelectedOrParentTableElement(tagNameObj, countObj); + } catch (e) {} + + if (tagNameObj.value == "td") { + gSelectedCellCount = countObj.value; + } else { + gSelectedCellCount = 0; + } + + // Currently, we can only allow advanced editing on ONE cell element at a time + // else we ignore CSS, JS, and HTML attributes not already in dialog + SetElementEnabled(gDialog.AdvancedEditCell, gSelectedCellCount == 1); + + gDialog.AdvancedEditCell.setAttribute( + "tooltiptext", + gSelectedCellCount > 1 + ? GetString("AdvancedEditForCellMsg") + : gDialog.AdvancedEditCellToolTipText + ); +} + +function SetSelectionButtons() { + if (gSelectedCellsType == SELECT_ROW) { + // Trigger CSS to set images of up and down arrows + gDialog.PreviousButton.setAttribute("type", "row"); + gDialog.NextButton.setAttribute("type", "row"); + } else { + // or images of left and right arrows + gDialog.PreviousButton.setAttribute("type", "col"); + gDialog.NextButton.setAttribute("type", "col"); + } + DisableSelectionButtons( + (gSelectedCellsType == SELECT_ROW && gRowCount == 1) || + (gSelectedCellsType == SELECT_COLUMN && gColCount == 1) || + (gRowCount == 1 && gColCount == 1) + ); +} + +function DisableSelectionButtons(disable) { + gDialog.PreviousButton.setAttribute("disabled", disable ? "true" : "false"); + gDialog.NextButton.setAttribute("disabled", disable ? "true" : "false"); +} + +function SwitchToValidatePanel() { + if (gDialog.TabBox.selectedTab != gValidateTab) { + gDialog.TabBox.selectedTab = gValidateTab; + } +} + +function SetAlign(listID, defaultValue, element, attName) { + var value = document.getElementById(listID).value; + if (value == defaultValue) { + try { + gActiveEditor.removeAttributeOrEquivalent(element, attName, true); + } catch (e) {} + } else { + try { + gActiveEditor.setAttributeOrEquivalent(element, attName, value, true); + } catch (e) {} + } +} + +function ValidateTableData() { + gValidateTab = gDialog.TableTab; + gNewRowCount = Number( + ValidateNumber(gDialog.TableRowsInput, null, 1, gMaxRows, null, true, true) + ); + if (gValidationError) { + return false; + } + + gNewColCount = Number( + ValidateNumber( + gDialog.TableColumnsInput, + null, + 1, + gMaxColumns, + null, + true, + true + ) + ); + if (gValidationError) { + return false; + } + + // If user is deleting any cells, get confirmation + // (This is a global to the dialog and we ask only once per dialog session) + if (!gCanDelete && (gNewRowCount < gRowCount || gNewColCount < gColCount)) { + if ( + ConfirmWithTitle( + GetString("DeleteTableTitle"), + GetString("DeleteTableMsg"), + GetString("DeleteCells") + ) + ) { + gCanDelete = true; + } else { + SetTextboxFocus( + gNewRowCount < gRowCount + ? gDialog.TableRowsInput + : gDialog.TableColumnsInput + ); + return false; + } + } + + ValidateNumber( + gDialog.TableWidthInput, + gDialog.TableWidthUnits, + 1, + gMaxTableSize, + globalTableElement, + "width" + ); + if (gValidationError) { + return false; + } + + if (gUseCSS) { + ValidateNumber( + gDialog.TableHeightInput, + gDialog.TableHeightUnits, + 1, + gMaxTableSize, + globalTableElement, + "height" + ); + if (gValidationError) { + return false; + } + } + + ValidateNumber( + gDialog.BorderWidthInput, + null, + 0, + gMaxPixels, + globalTableElement, + "border" + ); + // TODO: Deal with "BORDER" without value issue + if (gValidationError) { + return false; + } + + ValidateNumber( + gDialog.SpacingInput, + null, + 0, + gMaxPixels, + globalTableElement, + "cellspacing" + ); + if (gValidationError) { + return false; + } + + ValidateNumber( + gDialog.PaddingInput, + null, + 0, + gMaxPixels, + globalTableElement, + "cellpadding" + ); + if (gValidationError) { + return false; + } + + SetAlign("TableAlignList", defHAlign, globalTableElement, "align"); + + // Color is set on globalCellElement immediately + return true; +} + +function ValidateCellData() { + gValidateTab = gDialog.CellTab; + + if (gDialog.CellHeightCheckbox.checked) { + ValidateNumber( + gDialog.CellHeightInput, + gDialog.CellHeightUnits, + 1, + gMaxTableSize, + globalCellElement, + "height" + ); + if (gValidationError) { + return false; + } + } + + if (gDialog.CellWidthCheckbox.checked) { + ValidateNumber( + gDialog.CellWidthInput, + gDialog.CellWidthUnits, + 1, + gMaxTableSize, + globalCellElement, + "width" + ); + if (gValidationError) { + return false; + } + } + + if (gDialog.CellHAlignCheckbox.checked) { + var hAlign = gDialog.CellHAlignList.value; + + // Horizontal alignment is complicated by "char" type + // We don't change current values if user didn't edit alignment + if (!gAlignWasChar) { + globalCellElement.removeAttribute(charStr); + + // Always set "align" attribute, + // so the default "left" is effective in a cell + // when parent row has align set. + globalCellElement.setAttribute("align", hAlign); + } + } + + if (gDialog.CellVAlignCheckbox.checked) { + // Always set valign (no default in 2nd param) so + // the default "middle" is effective in a cell + // when parent row has valign set. + SetAlign("CellVAlignList", "", globalCellElement, "valign"); + } + + if (gDialog.TextWrapCheckbox.checked) { + if (gDialog.TextWrapList.value == "nowrap") { + try { + gActiveEditor.setAttributeOrEquivalent( + globalCellElement, + "nowrap", + "nowrap", + true + ); + } catch (e) {} + } else { + try { + gActiveEditor.removeAttributeOrEquivalent( + globalCellElement, + "nowrap", + true + ); + } catch (e) {} + } + } + + return true; +} + +function ValidateData() { + var result; + + // Validate current panel first + if (gDialog.TabBox.selectedTab == gDialog.TableTab) { + result = ValidateTableData(); + if (result) { + result = ValidateCellData(); + } + } else { + result = ValidateCellData(); + if (result) { + result = ValidateTableData(); + } + } + if (!result) { + return false; + } + + // Set global element for AdvancedEdit + if (gDialog.TabBox.selectedTab == gDialog.TableTab) { + globalElement = globalTableElement; + } else { + globalElement = globalCellElement; + } + + return true; +} + +function ChangeCellTextbox(textboxID) { + // Filter input for just integers + forceInteger(textboxID); + + if (gDialog.TabBox.selectedTab == gDialog.CellTab) { + gCellDataChanged = true; + } +} + +// Call this when a textbox or menulist is changed +// so the checkbox is automatically set +function SetCheckbox(checkboxID) { + if (checkboxID && checkboxID.length > 0) { + // Set associated checkbox + document.getElementById(checkboxID).checked = true; + } + gCellDataChanged = true; +} + +function ChangeIntTextbox(textboxID, checkboxID) { + // Filter input for just integers + forceInteger(textboxID); + + // Set associated checkbox + SetCheckbox(checkboxID); +} + +function CloneAttribute(destElement, srcElement, attr) { + var value = srcElement.getAttribute(attr); + // Use editor methods since we are always + // modifying a table in the document and + // we need transaction system for undo + try { + if (!value || value.length == 0) { + gActiveEditor.removeAttributeOrEquivalent(destElement, attr, false); + } else { + gActiveEditor.setAttributeOrEquivalent(destElement, attr, value, false); + } + } catch (e) {} +} + +/* eslint-disable complexity */ +function ApplyTableAttributes() { + var newAlign = gDialog.TableCaptionList.value; + if (!newAlign) { + newAlign = ""; + } + + if (gTableCaptionElement) { + // Get current alignment + var align = GetHTMLOrCSSStyleValue( + gTableCaptionElement, + "align", + "caption-side" + ).toLowerCase(); + // This is the default + if (!align) { + align = "top"; + } + + if (newAlign == "") { + // Remove existing caption + try { + gActiveEditor.deleteNode(gTableCaptionElement); + } catch (e) {} + gTableCaptionElement = null; + } else if (newAlign != align) { + try { + if (newAlign == "top") { + // This is default, so don't explicitly set it + gActiveEditor.removeAttributeOrEquivalent( + gTableCaptionElement, + "align", + false + ); + } else { + gActiveEditor.setAttributeOrEquivalent( + gTableCaptionElement, + "align", + newAlign, + false + ); + } + } catch (e) {} + } + } else if (newAlign != "") { + // Create and insert a caption: + try { + gTableCaptionElement = gActiveEditor.createElementWithDefaults("caption"); + } catch (e) {} + if (gTableCaptionElement) { + if (newAlign != "top") { + gTableCaptionElement.setAttribute("align", newAlign); + } + + // Insert it into the table - caption is always inserted as first child + try { + gActiveEditor.insertNode(gTableCaptionElement, gTableElement, 0); + } catch (e) {} + + // Put selection back where it was + ChangeSelection(RESET_SELECTION); + } + } + + var countDelta; + var foundCell; + var i; + + if (gNewRowCount != gRowCount) { + countDelta = gNewRowCount - gRowCount; + if (gNewRowCount > gRowCount) { + // Append new rows + // Find first cell in last row + if (GetCellData(gLastRowIndex, 0)) { + try { + // Move selection to the last cell + gSelection.collapse(gCellData.value, 0); + // Insert new rows after it + gActiveEditor.insertTableRow(countDelta, true); + gRowCount = gNewRowCount; + gLastRowIndex = gRowCount - 1; + // Put selection back where it was + ChangeSelection(RESET_SELECTION); + } catch (ex) { + dump("FAILED TO FIND FIRST CELL IN LAST ROW\n"); + } + } + } else if (gCanDelete) { + // Delete rows + // Find first cell starting in first row we delete + var firstDeleteRow = gRowCount + countDelta; + foundCell = false; + for (i = 0; i <= gLastColIndex; i++) { + if (!GetCellData(firstDeleteRow, i)) { + // We failed to find a cell. + break; + } + + if (gCellData.startRowIndex == firstDeleteRow) { + foundCell = true; + break; + } + } + if (foundCell) { + try { + // Move selection to the cell we found + gSelection.collapse(gCellData.value, 0); + gActiveEditor.deleteTableRow(-countDelta); + gRowCount = gNewRowCount; + gLastRowIndex = gRowCount - 1; + if (gCurRowIndex > gLastRowIndex) { + // We are deleting our selection + // move it to start of table + ChangeSelectionToFirstCell(); + } else { + // Put selection back where it was. + ChangeSelection(RESET_SELECTION); + } + } catch (ex) { + dump("FAILED TO FIND FIRST CELL IN LAST ROW\n"); + } + } + } + } + + if (gNewColCount != gColCount) { + countDelta = gNewColCount - gColCount; + + if (gNewColCount > gColCount) { + // Append new columns + // Find last cell in first column + if (GetCellData(0, gLastColIndex)) { + try { + // Move selection to the last cell + gSelection.collapse(gCellData.value, 0); + gActiveEditor.insertTableColumn(countDelta, true); + gColCount = gNewColCount; + gLastColIndex = gColCount - 1; + // Restore selection + ChangeSelection(RESET_SELECTION); + } catch (ex) { + dump("FAILED TO FIND FIRST CELL IN LAST COLUMN\n"); + } + } + } else if (gCanDelete) { + // Delete columns + var firstDeleteCol = gColCount + countDelta; + foundCell = false; + for (i = 0; i <= gLastRowIndex; i++) { + // Find first cell starting in first column we delete + if (!GetCellData(i, firstDeleteCol)) { + // We failed to find a cell. + break; + } + + if (gCellData.startColIndex == firstDeleteCol) { + foundCell = true; + break; + } + } + if (foundCell) { + try { + // Move selection to the cell we found + gSelection.collapse(gCellData.value, 0); + gActiveEditor.deleteTableColumn(-countDelta); + gColCount = gNewColCount; + gLastColIndex = gColCount - 1; + if (gCurColIndex > gLastColIndex) { + ChangeSelectionToFirstCell(); + } else { + ChangeSelection(RESET_SELECTION); + } + } catch (ex) { + dump("FAILED TO FIND FIRST CELL IN LAST ROW\n"); + } + } + } + } + + // Clone all remaining attributes to pick up + // anything changed by Advanced Edit Dialog + try { + gActiveEditor.cloneAttributes(gTableElement, globalTableElement); + } catch (e) {} +} +/* eslint-enable complexity */ + +function ApplyCellAttributes() { + var rangeObj = { value: null }; + var selectedCell; + try { + selectedCell = gActiveEditor.getFirstSelectedCell(rangeObj); + } catch (e) {} + + if (!selectedCell) { + return; + } + + if (gSelectedCellCount == 1) { + // When only one cell is selected, simply clone entire element, + // thus CSS and JS from Advanced edit is copied + try { + gActiveEditor.cloneAttributes(selectedCell, globalCellElement); + } catch (e) {} + + if (gDialog.CellStyleCheckbox.checked) { + var currentStyleIndex = + selectedCell.nodeName.toLowerCase() == "th" ? 1 : 0; + if (gDialog.CellStyleList.selectedIndex != currentStyleIndex) { + // Switch cell types + // (replaces with new cell and copies attributes and contents) + try { + selectedCell = gActiveEditor.switchTableCellHeaderType(selectedCell); + } catch (e) {} + } + } + } else { + // Apply changes to all selected cells + // XXX THIS DOESN'T COPY ADVANCED EDIT CHANGES! + try { + while (selectedCell) { + ApplyAttributesToOneCell(selectedCell); + selectedCell = gActiveEditor.getNextSelectedCell(rangeObj); + } + } catch (e) {} + } + gCellDataChanged = false; +} + +function ApplyAttributesToOneCell(destElement) { + if (gDialog.CellHeightCheckbox.checked) { + CloneAttribute(destElement, globalCellElement, "height"); + } + + if (gDialog.CellWidthCheckbox.checked) { + CloneAttribute(destElement, globalCellElement, "width"); + } + + if (gDialog.CellHAlignCheckbox.checked) { + CloneAttribute(destElement, globalCellElement, "align"); + CloneAttribute(destElement, globalCellElement, charStr); + } + + if (gDialog.CellVAlignCheckbox.checked) { + CloneAttribute(destElement, globalCellElement, "valign"); + } + + if (gDialog.TextWrapCheckbox.checked) { + CloneAttribute(destElement, globalCellElement, "nowrap"); + } + + if (gDialog.CellStyleCheckbox.checked) { + var newStyleIndex = gDialog.CellStyleList.selectedIndex; + var currentStyleIndex = destElement.nodeName.toLowerCase() == "th" ? 1 : 0; + + if (newStyleIndex != currentStyleIndex) { + // Switch cell types + // (replaces with new cell and copies attributes and contents) + try { + destElement = gActiveEditor.switchTableCellHeaderType(destElement); + } catch (e) {} + } + } + + if (gDialog.CellColorCheckbox.checked) { + CloneAttribute(destElement, globalCellElement, "bgcolor"); + } +} + +function SetCloseButton() { + // Change text on "Cancel" button after Apply is used + if (!gApplyUsed) { + document.documentElement.setAttribute( + "buttonlabelcancel", + document.documentElement.getAttribute("buttonlabelclose") + ); + gApplyUsed = true; + } +} + +function Apply() { + if (ValidateData()) { + gActiveEditor.beginTransaction(); + + ApplyTableAttributes(); + + // We may have just a table, so check for cell element + if (globalCellElement) { + ApplyCellAttributes(); + } + + gActiveEditor.endTransaction(); + + SetCloseButton(); + return true; + } + return false; +} + +function onAccept(event) { + // Do same as Apply and close window if ValidateData succeeded + var retVal = Apply(); + if (retVal) { + SaveWindowLocation(); + } else { + event.preventDefault(); + } +} diff --git a/comm/suite/editor/components/dialogs/content/EdTableProps.xhtml b/comm/suite/editor/components/dialogs/content/EdTableProps.xhtml new file mode 100644 index 0000000000..30979acb4d --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdTableProps.xhtml @@ -0,0 +1,287 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % edTableProperties SYSTEM "chrome://editor/locale/EditorTableProperties.dtd"> +%edTableProperties; +<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd"> +%edDialogOverlay; +]> + +<dialog title="&tableWindow.title;" + id="tableDlg" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup()" + buttons="accept,extra1,cancel" + buttonlabelclose="&closeButton.label;" + buttonlabelextra1="&applyButton.label;" + buttonaccesskeyextra1="&applyButton.accesskey;"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdTableProps.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <tabbox id="TabBox"> + <tabs flex="1"> + <tab id="TableTab" label="&tableTab.label;"/> + <tab id="CellTab" label="&cellTab.label;"/> + </tabs> + <tabpanels> + + <!-- TABLE PANEL --> + <vbox> + <groupbox orient="horizontal"> + <hbox class="groupbox-title"> + <label class="header">&size.label;</label> + </hbox> + <grid> + <columns><column/><column/><column/><column/><column/></columns> + <rows> + <row align="center"> + <label value="&tableRows.label;" accesskey="&tableRows.accessKey;" control="TableRowsInput"/> + <textbox class="narrow" id="TableRowsInput" oninput="forceInteger(this.id);"/> + <spring class="bigspacer"/> + <label value="&tableHeight.label;" accesskey="&tableHeight.accessKey;" + id="TableHeightLabel" control="TableHeightInput"/> + <textbox class="narrow" id="TableHeightInput" oninput="forceInteger(this.id);"/> + <menulist id="TableHeightUnits"/> + </row> + <row align="center"> + <label value="&tableColumns.label;" accesskey="&tableColumns.accessKey;" control="TableColumnsInput"/> + <textbox class="narrow" id="TableColumnsInput" oninput="forceInteger(this.id);"/> + <spring class="bigspacer"/> + <label value="&tableWidth.label;" accesskey="&tableWidth.accessKey;" control="TableWidthInput"/> + <textbox class="narrow" id="TableWidthInput" oninput="forceInteger(this.id);"/> + <menulist id="TableWidthUnits"/> + </row> + </rows> + <!-- KEEP GRID LAYOUT here since we will be adding back support for table HEIGHT via CSS --> + </grid> + </groupbox> + <groupbox> + <hbox class="groupbox-title"> + <label class="header">&tableBorderSpacing.label;</label> + </hbox> + <grid> + <columns><column/><column/><column/></columns> + <rows> + <row align="center"> + <label control="BorderWidthInput" + value="&tableBorderWidth.label;" + accesskey="&tableBorderWidth.accessKey;"/> + <textbox class="narrow" id="BorderWidthInput" oninput="forceInteger(this.id);"/> + <label align="left" value="&pixels.label;"/> + </row> + <row align="center"> + <label control="SpacingInput" + value="&tableSpacing.label;" + accesskey="&tableSpacing.accessKey;"/> + <textbox class="narrow" id="SpacingInput" oninput="forceInteger(this.id);"/> + <label value="&tablePxBetwCells.label;"/> + </row> + <row align="center"> + <label control="PaddingInput" + value="&tablePadding.label;" + accesskey="&tablePadding.accessKey;"/> + <textbox class="narrow" id="PaddingInput" oninput="forceInteger(this.id);"/> + <label value="&tablePxBetwBrdrCellContent.label;"/> + </row> + </rows> + </grid> + </groupbox> + <!-- Table Alignment and Caption --> + <hbox flex="1" align="center"> + <label control="TableAlignList" + value="&tableAlignment.label;" + accesskey="&tableAlignment.accessKey;"/> + <menulist id="TableAlignList"> + <menupopup> + <menuitem label="&AlignLeft.label;" value="left"/> + <menuitem label="&AlignCenter.label;" value="center"/> + <menuitem label="&AlignRight.label;" value="right"/> + </menupopup> + </menulist> + <spacer class="spacer"/> + <label control="TableCaptionList" + value="&tableCaption.label;" + accesskey="&tableCaption.accessKey;"/> + <menulist id="TableCaptionList"> + <menupopup> + <menuitem label="&tableCaptionNone.label;" value=""/> + <menuitem label="&tableCaptionAbove.label;" value="top"/> + <menuitem label="&tableCaptionBelow.label;" value="bottom"/> + <menuitem label="&tableCaptionLeft.label;" value="left"/> + <menuitem label="&tableCaptionRight.label;" value="right"/> + </menupopup> + </menulist> + </hbox> + <separator class="groove"/> + <hbox align="center"> + <label value="&backgroundColor.label;"/> + <button id="tableBackground" class="color-button" oncommand="GetColorAndUpdate('tableBackgroundCW');"> + <spacer id="tableBackgroundCW" class="color-well"/> + </button> + <spacer class="spacer"/> + <label id="TableInheritColor" value="&tableInheritColor.label;" collapsed="true"/> + </hbox> + <separator class="groove"/> + <hbox flex="1" align="center"> + <spacer flex="1"/> + <button id="AdvancedEditButton" + oncommand="onAdvancedEdit();" + label="&AdvancedEditButton.label;" + accesskey="&AdvancedEditButton.accessKey;" + tooltiptext="&AdvancedEditButton.tooltip;"/> + </hbox> + <spacer flex="1"/> + </vbox><!-- Table Panel --> + + <!-- CELL PANEL --> + <vbox> + <groupbox orient="horizontal" align="center"> + <hbox class="groupbox-title"> + <label class="header">&cellSelection.label;</label> + </hbox> + <vbox> + <menulist id="SelectionList" oncommand="ChangeSelection(event.target.value)" flex="1"> + <menupopup> + <!-- JS code assumes order is Cell, Row, Column --> + <menuitem label="&cellSelectCell.label;" value="1"/> + <menuitem label="&cellSelectRow.label;" value="2"/> + <menuitem label="&cellSelectColumn.label;" value="3"/> + </menupopup> + </menulist> + <hbox flex="1"> + <button id="PreviousButton" + oncommand="MoveSelection(0)" + flex="1" + align="center"> + <image/> + <label value="&cellSelectPrevious.label;" + accesskey="&cellSelectPrevious.accessKey;" + control="PreviousButton"/> + </button> + <button id="NextButton" + oncommand="MoveSelection(1)" + class="align-right" + flex="1" + align="center"> + <image/> + <label value="&cellSelectNext.label;" + accesskey="&cellSelectNext.accessKey;" + control="NextButton"/> + </button> + </hbox> + </vbox> + <spacer class="bigspacer"/> + <description class="wrap" flex="1">&applyBeforeChange.label;</description> + </groupbox> + <hbox align="center"> + <!-- cell size groupbox --> + <groupbox> + <hbox class="groupbox-title"> + <label class="header">&size.label;</label> + </hbox> + <grid> + <columns><column/><column/><column flex="1"/></columns> + <rows> + <row align="center"> + <checkbox id="CellHeightCheckbox" label="&tableHeight.label;" accesskey="&tableHeight.accessKey;"/> + <textbox class="narrow" id="CellHeightInput" + oninput="ChangeIntTextbox(this.id, 'CellHeightCheckbox');"/> + <menulist id="CellHeightUnits" oncommand="SetCheckbox('CellHeightCheckbox');"/> + </row> + <row align="center"> + <checkbox id="CellWidthCheckbox" label="&tableWidth.label;" accesskey="&tableWidth.accessKey;"/> + <textbox class="narrow" id="CellWidthInput" + oninput="ChangeIntTextbox(this.id, 'CellWidthCheckbox');"/> + <menulist id="CellWidthUnits" oncommand="SetCheckbox('CellWidthCheckbox');"/> + </row> + </rows> + </grid> + <spacer class="bigspacer"/> + </groupbox> + <!-- Alignment --> + <groupbox> + <hbox class="groupbox-title"> + <label class="header">&cellContentAlignment.label;</label> + </hbox> + <grid> + <columns><column/><column flex="1"/><column/></columns> + <rows> + <row align="center"> + <checkbox id="CellVAlignCheckbox" label="&cellVertical.label;" accesskey="&cellVertical.accessKey;"/> + <menulist id="CellVAlignList" oncommand="SetCheckbox('CellVAlignCheckbox');"> + <menupopup> + <menuitem label="&cellAlignTop.label;" value="top"/> + <menuitem label="&cellAlignMiddle.label;" value="middle"/> + <menuitem label="&cellAlignBottom.label;" value="bottom"/> + </menupopup> + </menulist> + </row> + <row align="center"> + <checkbox id="CellHAlignCheckbox" label="&cellHorizontal.label;" accesskey="&cellHorizontal.accessKey;"/> + <menulist id="CellHAlignList" oncommand="SelectCellHAlign()"> + <menupopup> + <menuitem label="&AlignLeft.label;" value="left"/> + <menuitem label="&AlignCenter.label;" value="center"/> + <menuitem label="&AlignRight.label;" value="right"/> + <menuitem label="&cellAlignJustify.label;" value="justify"/> + </menupopup> + </menulist> + </row> + </rows> + </grid> + </groupbox> + </hbox> + <spacer class="spacer"/> + <hbox align="center"> + <checkbox id="CellStyleCheckbox" label="&cellStyle.label;" accesskey="&cellStyle.accessKey;"/> + <menulist id="CellStyleList" oncommand="SetCheckbox('CellStyleCheckbox');"> + <menupopup> + <menuitem label="&cellNormal.label;" value="td"/> + <menuitem label="&cellHeader.label;" value="th"/> + </menupopup> + </menulist> + <spacer class="bigspacer"/> + <checkbox id="TextWrapCheckbox" label="&cellTextWrap.label;" accesskey="&cellTextWrap.accessKey;"/> + <menulist id="TextWrapList" oncommand="SetCheckbox('TextWrapCheckbox');"> + <menupopup> + <menuitem label="&cellWrap.label;" value="wrap"/> + <menuitem label="&cellNoWrap.label;" value="nowrap"/> + </menupopup> + </menulist> + </hbox> + <separator class="groove"/> + <hbox align="center"> + <checkbox id="CellColorCheckbox" label="&backgroundColor.label;" accesskey="&backgroundColor.accessKey;"/> + <button class="color-button" oncommand="GetColorAndUpdate('cellBackgroundCW');"> + <spacer id="cellBackgroundCW" class="color-well"/> + </button> + <spacer class="spacer"/> + <label id="CellInheritColor" value="&cellInheritColor.label;" collapsed="true"/> + </hbox> + <separator class="groove"/> + <hbox align="center"> + <description class="wrap" flex="1" style="width: 1em">&cellUseCheckboxHelp.label;</description> + <button id="AdvancedEditButton2" + oncommand="onAdvancedEdit()" + label="&AdvancedEditButton.label;" + accesskey="&AdvancedEditButton.accessKey;" + tooltiptext="&AdvancedEditButton.tooltip;"/> + </hbox> + <spacer flex="1"/> + </vbox><!-- Cell Panel --> + </tabpanels> + </tabbox> + <spacer class="spacer"/> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EdTextAreaProps.js b/comm/suite/editor/components/dialogs/content/EdTextAreaProps.js new file mode 100644 index 0000000000..da33ab60c9 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdTextAreaProps.js @@ -0,0 +1,171 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../composer/content/editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +var insertNew; +var textareaElement; + +// dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() { + var editor = GetCurrentEditor(); + if (!editor) { + dump("Failed to get active editor!\n"); + window.close(); + return; + } + + gDialog = { + accept: document.documentElement.getButton("accept"), + textareaName: document.getElementById("TextAreaName"), + textareaRows: document.getElementById("TextAreaRows"), + textareaCols: document.getElementById("TextAreaCols"), + textareaWrap: document.getElementById("TextAreaWrap"), + textareaReadOnly: document.getElementById("TextAreaReadOnly"), + textareaDisabled: document.getElementById("TextAreaDisabled"), + textareaTabIndex: document.getElementById("TextAreaTabIndex"), + textareaAccessKey: document.getElementById("TextAreaAccessKey"), + textareaValue: document.getElementById("TextAreaValue"), + MoreSection: document.getElementById("MoreSection"), + MoreFewerButton: document.getElementById("MoreFewerButton"), + }; + + // Get a single selected text area element + const kTagName = "textarea"; + try { + textareaElement = editor.getSelectedElement(kTagName); + } catch (e) {} + + if (textareaElement) { + // We found an element and don't need to insert one + insertNew = false; + + gDialog.textareaValue.value = textareaElement.value; + } else { + insertNew = true; + + // We don't have an element selected, + // so create one with default attributes + try { + textareaElement = editor.createElementWithDefaults(kTagName); + } catch (e) {} + + if (!textareaElement) { + dump("Failed to get selected element or create a new one!\n"); + window.close(); + return; + } + gDialog.textareaValue.value = GetSelectionAsText(); + } + + // Make a copy to use for AdvancedEdit + globalElement = textareaElement.cloneNode(false); + + InitDialog(); + + InitMoreFewer(); + + SetTextboxFocus(gDialog.textareaName); + + SetWindowLocation(); +} + +function InitDialog() { + gDialog.textareaName.value = globalElement.getAttribute("name"); + gDialog.textareaRows.value = globalElement.getAttribute("rows"); + gDialog.textareaCols.value = globalElement.getAttribute("cols"); + gDialog.textareaWrap.value = GetHTMLOrCSSStyleValue( + globalElement, + "wrap", + "white-space" + ); + gDialog.textareaReadOnly.checked = globalElement.hasAttribute("readonly"); + gDialog.textareaDisabled.checked = globalElement.hasAttribute("disabled"); + gDialog.textareaTabIndex.value = globalElement.getAttribute("tabindex"); + gDialog.textareaAccessKey.value = globalElement.getAttribute("accesskey"); + onInput(); +} + +function onInput() { + var disabled = + !gDialog.textareaName.value || + !gDialog.textareaRows.value || + !gDialog.textareaCols.value; + if (gDialog.accept.disabled != disabled) { + gDialog.accept.disabled = disabled; + } +} + +function ValidateData() { + var attributes = { + name: gDialog.textareaName.value, + rows: gDialog.textareaRows.value, + cols: gDialog.textareaCols.value, + wrap: gDialog.textareaWrap.value, + tabindex: gDialog.textareaTabIndex.value, + accesskey: gDialog.textareaAccessKey.value, + }; + var flags = { + readonly: gDialog.textareaReadOnly.checked, + disabled: gDialog.textareaDisabled.checked, + }; + for (var a in attributes) { + if (attributes[a]) { + globalElement.setAttribute(a, attributes[a]); + } else { + globalElement.removeAttribute(a); + } + } + for (var f in flags) { + if (flags[f]) { + globalElement.setAttribute(f, ""); + } else { + globalElement.removeAttribute(f); + } + } + return true; +} + +function onAccept() { + // All values are valid - copy to actual element in doc or + // element created to insert + ValidateData(); + + var editor = GetCurrentEditor(); + + editor.beginTransaction(); + + try { + editor.cloneAttributes(textareaElement, globalElement); + + if (insertNew) { + editor.insertElementAtSelection(textareaElement, true); + } + + // undoably set value + var initialText = gDialog.textareaValue.value; + if (initialText != textareaElement.value) { + editor.setShouldTxnSetSelection(false); + + while (textareaElement.hasChildNodes()) { + editor.deleteNode(textareaElement.lastChild); + } + if (initialText) { + var textNode = editor.document.createTextNode(initialText); + editor.insertNode(textNode, textareaElement, 0); + } + + editor.setShouldTxnSetSelection(true); + } + } finally { + editor.endTransaction(); + } + + SaveWindowLocation(); +} diff --git a/comm/suite/editor/components/dialogs/content/EdTextAreaProps.xhtml b/comm/suite/editor/components/dialogs/content/EdTextAreaProps.xhtml new file mode 100644 index 0000000000..9ab91664e8 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EdTextAreaProps.xhtml @@ -0,0 +1,115 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % edTextAreaProperties SYSTEM "chrome://editor/locale/EditorTextAreaProperties.dtd"> +%edTextAreaProperties; +<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd"> +%edDialogOverlay; +]> + +<dialog title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup();" + buttons="accept,cancel"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EdTextAreaProps.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <groupbox> + <hbox class="groupbox-title"> + <label class="header">&Settings.label;</label> + </hbox> + <grid><columns><column/><column/></columns> + <rows> + <row align="center"> + <label control="TextAreaName" value="&TextAreaName.label;" accesskey="&TextAreaName.accessKey;"/> + <textbox id="TextAreaName" oninput="onInput();"/> + </row> + <row align="center"> + <label control="TextAreaRows" value="&TextAreaRows.label;" accesskey="&TextAreaRows.accessKey;"/> + <hbox> + <textbox id="TextAreaRows" class="narrow" oninput="forceInteger(this.id); onInput();"/> + </hbox> + </row> + <row align="center"> + <label control="TextAreaCols" value="&TextAreaCols.label;" accesskey="&TextAreaCols.accessKey;"/> + <hbox> + <textbox id="TextAreaCols" class="narrow" oninput="forceInteger(this.id); onInput();"/> + </hbox> + </row> + </rows> + </grid> + <hbox> + <button id="MoreFewerButton" oncommand="onMoreFewer();" persist="more"/> + </hbox> + <grid id="MoreSection"><columns><column/><column/></columns> + <rows> + <row align="center"> + <label control="TextAreaWrap" value="&TextAreaWrap.label;" accesskey="&TextAreaWrap.accessKey;"/> + <menulist id="TextAreaWrap"> + <menupopup> + <menuitem label="&WrapDefault.value;"/> + <menuitem label="&WrapOff.value;" value="off"/> + <menuseparator/> + <menuitem label="&WrapSoft.value;" value="soft"/> + <menuitem label="&WrapHard.value;" value="hard"/> + <menuseparator/> + <menuitem label="&WrapPhysical.value;" value="physical"/> + <menuitem label="&WrapVirtual.value;" value="virtual"/> + <menuseparator/> + <menuitem label="normal" value="normal"/> + <menuitem label="nowrap" value="nowrap"/> + <menuitem label="pre" value="pre"/> + </menupopup> + </menulist> + </row> + <row> + <spacer/> + <checkbox id="TextAreaReadOnly" label="&TextAreaReadOnly.label;" accesskey="&TextAreaReadOnly.accessKey;"/> + </row> + <row> + <spacer/> + <checkbox id="TextAreaDisabled" label="&TextAreaDisabled.label;" accesskey="&TextAreaDisabled.accessKey;"/> + </row> + <row align="center"> + <label control="TextAreaTabIndex" value="&TextAreaTabIndex.label;" accesskey="&TextAreaTabIndex.accessKey;"/> + <hbox> + <textbox id="TextAreaTabIndex" class="narrow" oninput="forceInteger(this.id);"/> + </hbox> + </row> + <row align="center"> + <label control="TextAreaAccessKey" value="&TextAreaAccessKey.label;" accesskey="&TextAreaAccessKey.accessKey;"/> + <hbox> + <textbox id="TextAreaAccessKey" class="narrow" maxlength="1"/> + </hbox> + </row> + <row> + <label control="TextAreaValue" value="&InitialText.label;" accesskey="&InitialText.accessKey;"/> + </row> + <html:textarea id="TextAreaValue" flex="1" rows="5"/> + </rows> + </grid> + </groupbox> + + <vbox id="AdvancedEdit"> + <hbox flex="1" style="margin-top: 0.2em" align="center"> + <!-- This will right-align the button --> + <spacer flex="1"/> + <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;" + accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/> + </hbox> + <separator id="advancedSeparator" class="groove"/> + </vbox> + +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EditConflict.js b/comm/suite/editor/components/dialogs/content/EditConflict.js new file mode 100644 index 0000000000..28611796cd --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EditConflict.js @@ -0,0 +1,42 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// dialog initialization code + +document.addEventListener("dialogcancel", onClose); + +function Startup() +{ + if (!GetCurrentEditor()) + { + window.close(); + return; + } + + SetWindowLocation(); +} + +function KeepCurrentPage() +{ + // Simply close dialog and don't change current page + //TODO: Should we force saving of the current page? + SaveWindowLocation(); + return true; +} + +function UseOtherPage() +{ + // Reload the URL -- that will get other editor's contents + window.opener.setTimeout(window.opener.EditorLoadUrl, 0, GetDocumentUrl()); + SaveWindowLocation(); + return true; +} + +function PreventCancel() +{ + SaveWindowLocation(); + + // Don't let Esc key close the dialog! + return false; +} diff --git a/comm/suite/editor/components/dialogs/content/EditConflict.xhtml b/comm/suite/editor/components/dialogs/content/EditConflict.xhtml new file mode 100644 index 0000000000..fce32792d2 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EditConflict.xhtml @@ -0,0 +1,40 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditConflict.dtd"> + +<dialog buttons="cancel" title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup()"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EditConflict.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <label value ="&conflictWarning.label;"/> + <spacer class="bigspacer"/> + <label value ="&conflictResolve.label;"/> + <spacer class="bigspacer"/> + <hbox flex="1"> + <spacer class="bigspacer"/> + <button label="&keepCurrentPageButton.label;" + oncommand="KeepCurrentPage()"/> + <spacer class="bigspacer"/> + </hbox> + <hbox flex="1"> + <spacer class="bigspacer"/> + <button dlgtype="cancel" + label="&useOtherPageButton.label;" + oncommand="UseOtherPage()"/> + <spacer class="bigspacer"/> + </hbox> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EditorPublish.js b/comm/suite/editor/components/dialogs/content/EditorPublish.js new file mode 100644 index 0000000000..6947a439ee --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EditorPublish.js @@ -0,0 +1,558 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var gPublishSiteData; +var gReturnData; +var gDefaultSiteIndex = -1; +var gDefaultSiteName; +var gPreviousDefaultDir; +var gPreviousTitle; +var gSettingsChanged = false; +var gInitialSiteName; +var gInitialSiteIndex = -1; +var gPasswordManagerOn = true; + +// Dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() +{ + window.opener.ok = false; + + // Element to edit is passed in + gInitialSiteName = window.arguments[1]; + gReturnData = window.arguments[2]; + if (!gReturnData || !GetCurrentEditor()) + { + dump("Publish: No editor or return data object not supplied\n"); + window.close(); + return; + } + + gDialog.TabBox = document.getElementById("TabBox"); + gDialog.PublishTab = document.getElementById("PublishTab"); + gDialog.SettingsTab = document.getElementById("SettingsTab"); + + // Publish panel + gDialog.PageTitleInput = document.getElementById("PageTitleInput"); + gDialog.FilenameInput = document.getElementById("FilenameInput"); + gDialog.SiteList = document.getElementById("SiteList"); + gDialog.DocDirList = document.getElementById("DocDirList"); + gDialog.OtherDirCheckbox = document.getElementById("OtherDirCheckbox"); + gDialog.OtherDirRadiogroup = document.getElementById("OtherDirRadiogroup"); + gDialog.SameLocationRadio = document.getElementById("SameLocationRadio"); + gDialog.UseSubdirRadio = document.getElementById("UseSubdirRadio"); + gDialog.OtherDirList = document.getElementById("OtherDirList"); + + // Settings Panel + gDialog.SiteNameInput = document.getElementById("SiteNameInput"); + gDialog.PublishUrlInput = document.getElementById("PublishUrlInput"); + gDialog.BrowseUrlInput = document.getElementById("BrowseUrlInput"); + gDialog.UsernameInput = document.getElementById("UsernameInput"); + gDialog.PasswordInput = document.getElementById("PasswordInput"); + gDialog.SavePassword = document.getElementById("SavePassword"); + + gPasswordManagerOn = Services.prefs.getBoolPref("signon.rememberSignons"); + gDialog.SavePassword.disabled = !gPasswordManagerOn; + + gPublishSiteData = GetPublishSiteData(); + gDefaultSiteName = GetDefaultPublishSiteName(); + + var addNewSite = false; + if (gPublishSiteData) + { + FillSiteList(); + } + else + { + // No current site data, start a new item in the Settings panel + AddNewSite(); + addNewSite = true; + } + + var docUrl = GetDocumentUrl(); + var scheme = GetScheme(docUrl); + var filename = ""; + + if (scheme) + { + filename = GetFilename(docUrl); + + if (scheme != "file") + { + var siteFound = false; + + // Editing a remote URL. + // Attempt to find doc URL in Site Data + if (gPublishSiteData) + { + var dirObj = {}; + var siteIndex = FindSiteIndexAndDocDir(gPublishSiteData, docUrl, dirObj); + + // Select this site only if the same as user's intended site, or there wasn't one + if (siteIndex != -1 && (gInitialSiteIndex == -1 || siteIndex == gInitialSiteIndex)) + { + siteFound = true; + + // Select the site we found + gDialog.SiteList.selectedIndex = siteIndex; + var docDir = dirObj.value; + + // Use the directory within site in the editable menulist + gPublishSiteData[siteIndex].docDir = docDir; + + //XXX HOW DO WE DECIDE WHAT "OTHER" DIR TO USE? + //gPublishSiteData[siteIndex].otherDir = docDir; + } + } + if (!siteFound) + { + // Not found in site database + // Setup for a new site and use data from a remote URL + if (!addNewSite) + AddNewSite(); + + addNewSite = true; + + var publishData = CreatePublishDataFromUrl(docUrl); + if (publishData) + { + filename = publishData.filename; + gDialog.SiteNameInput.value = publishData.siteName; + gDialog.PublishUrlInput.value = publishData.publishUrl; + gDialog.BrowseUrlInput.value = publishData.browseUrl; + gDialog.UsernameInput.value = publishData.username; + gDialog.PasswordInput.value = publishData.password; + gDialog.SavePassword.checked = false; + } + } + } + } + try { + gPreviousTitle = GetDocumentTitle(); + } catch (e) {} + + gDialog.PageTitleInput.value = gPreviousTitle; + gDialog.FilenameInput.value = decodeURIComponent(filename); + + if (!addNewSite) + { + // If not adding a site and we haven't selected a site -- use initial or default site + if (gDialog.SiteList.selectedIndex == -1) + gDialog.SiteList.selectedIndex = (gInitialSiteIndex != -1) ? gInitialSiteIndex : gDefaultSiteIndex; + + // Fill in all the site data for currently-selected site + SelectSiteList(); + SetTextboxFocus(gDialog.PageTitleInput); + } + + if (gDialog.SiteList.selectedIndex == -1) + { + // No selected site -- assume same directory + gDialog.OtherDirRadiogroup.selectedItem = gDialog.SameLocationRadio; + } + else if (gPublishSiteData[gDialog.SiteList.selectedIndex].docDir == + gPublishSiteData[gDialog.SiteList.selectedIndex].otherDir) + { + // For now, check "same location" if dirs are already set to same directory + gDialog.OtherDirRadiogroup.selectedItem = gDialog.SameLocationRadio; + } + else + { + gDialog.OtherDirRadiogroup.selectedItem = gDialog.UseSubdirRadio; + } + + doEnabling(); + + SetWindowLocation(); +} + +function FillSiteList() +{ + gDialog.SiteList.removeAllItems(); + gDefaultSiteIndex = -1; + + // Fill the site lists + var count = gPublishSiteData.length; + var i; + + for (i = 0; i < count; i++) + { + var name = gPublishSiteData[i].siteName; + var menuitem = gDialog.SiteList.appendItem(name); + // Highlight the default site + if (name == gDefaultSiteName) + { + gDefaultSiteIndex = i; + if (menuitem) + { + menuitem.setAttribute("class", "menuitem-highlight-1"); + menuitem.setAttribute("default", "true"); + } + } + // Find initial site location + if (name == gInitialSiteName) + gInitialSiteIndex = i; + } +} + +function doEnabling() +{ + var disableOther = !gDialog.OtherDirCheckbox.checked; + gDialog.SameLocationRadio.disabled = disableOther; + gDialog.UseSubdirRadio.disabled = disableOther; + gDialog.OtherDirList.disabled = (disableOther || gDialog.SameLocationRadio.selected); +} + +function SelectSiteList() +{ + var selectedSiteIndex = gDialog.SiteList.selectedIndex; + + var siteName = ""; + var publishUrl = ""; + var browseUrl = ""; + var username = ""; + var password = ""; + var savePassword = false; + var publishOtherFiles = true; + + gDialog.DocDirList.removeAllItems(); + gDialog.OtherDirList.removeAllItems(); + + if (gPublishSiteData && selectedSiteIndex != -1) + { + siteName = gPublishSiteData[selectedSiteIndex].siteName; + publishUrl = gPublishSiteData[selectedSiteIndex].publishUrl; + browseUrl = gPublishSiteData[selectedSiteIndex].browseUrl; + username = gPublishSiteData[selectedSiteIndex].username; + savePassword = gPasswordManagerOn ? gPublishSiteData[selectedSiteIndex].savePassword : false; + if (savePassword) + password = gPublishSiteData[selectedSiteIndex].password; + + // Fill the directory menulists + if (gPublishSiteData[selectedSiteIndex].dirList.length) + { + for (var i = 0; i < gPublishSiteData[selectedSiteIndex].dirList.length; i++) + { + gDialog.DocDirList.appendItem(gPublishSiteData[selectedSiteIndex].dirList[i]); + gDialog.OtherDirList.appendItem(gPublishSiteData[selectedSiteIndex].dirList[i]); + } + } + gDialog.DocDirList.value = FormatDirForPublishing(gPublishSiteData[selectedSiteIndex].docDir); + gDialog.OtherDirList.value = FormatDirForPublishing(gPublishSiteData[selectedSiteIndex].otherDir); + publishOtherFiles = gPublishSiteData[selectedSiteIndex].publishOtherFiles; + + } + else + { + gDialog.DocDirList.value = ""; + gDialog.OtherDirList.value = ""; + } + + gDialog.SiteNameInput.value = siteName; + gDialog.PublishUrlInput.value = publishUrl; + gDialog.BrowseUrlInput.value = browseUrl; + gDialog.UsernameInput.value = username; + gDialog.PasswordInput.value = password; + gDialog.SavePassword.checked = savePassword; + gDialog.OtherDirCheckbox.checked = publishOtherFiles; + + doEnabling(); +} + +function AddNewSite() +{ + // Button in Publish panel allows user + // to automatically switch to "Settings" panel + // to enter data for new site + SwitchPanel(gDialog.SettingsTab); + + gDialog.SiteList.selectedIndex = -1; + + SelectSiteList(); + + gSettingsChanged = true; + + SetTextboxFocus(gDialog.SiteNameInput); +} + +function SelectPublishTab() +{ + if (gSettingsChanged && !ValidateSettings()) + return; + + SwitchPanel(gDialog.PublishTab); + SetTextboxFocus(gDialog.PageTitleInput); +} + +function SelectSettingsTab() +{ + SwitchPanel(gDialog.SettingsTab); + SetTextboxFocus(gDialog.SiteNameInput); +} + +function SwitchPanel(tab) +{ + if (gDialog.TabBox.selectedTab != tab) + gDialog.TabBox.selectedTab = tab; +} + +function onInputSettings() +{ + // TODO: Save current data during SelectSite and compare here + // to detect if real change has occurred? + gSettingsChanged = true; +} + +function GetPublishUrlInput() +{ + gDialog.PublishUrlInput.value = FormatUrlForPublishing(gDialog.PublishUrlInput.value); + return gDialog.PublishUrlInput.value; +} + +function GetBrowseUrlInput() +{ + gDialog.BrowseUrlInput.value = FormatUrlForPublishing(gDialog.BrowseUrlInput.value); + return gDialog.BrowseUrlInput.value; +} + +function GetDocDirInput() +{ + gDialog.DocDirList.value = FormatDirForPublishing(gDialog.DocDirList.value); + return gDialog.DocDirList.value; +} + +function GetOtherDirInput() +{ + gDialog.OtherDirList.value = FormatDirForPublishing(gDialog.OtherDirList.value); + return gDialog.OtherDirList.value; +} + +function ChooseDir(menulist) +{ + //TODO: For FTP publish destinations, get file listing of just dirs + // and build a tree to let user select dir +} + +function ValidateSettings() +{ + var siteName = TrimString(gDialog.SiteNameInput.value); + if (!siteName) + { + ShowErrorInPanel(gDialog.SettingsTab, "MissingSiteNameError", gDialog.SiteNameInput); + return false; + } + if (PublishSiteNameExists(siteName, gPublishSiteData, gDialog.SiteList.selectedIndex)) + { + SwitchPanel(gDialog.SettingsTab); + ShowInputErrorMessage(GetString("DuplicateSiteNameError").replace(/%name%/, siteName)); + SetTextboxFocus(gDialog.SiteNameInput); + return false; + } + + // Extract username and password while removing them from publishingUrl + var urlUserObj = {}; + var urlPassObj = {}; + var publishUrl = StripUsernamePassword(gDialog.PublishUrlInput.value, urlUserObj, urlPassObj); + if (publishUrl) + { + publishUrl = FormatUrlForPublishing(publishUrl); + + // Assume scheme = "ftp://" if missing + // This compensates when user enters hostname w/o scheme (as most ISPs provide) + if (!GetScheme(publishUrl)) + publishUrl = "ftp://" + publishUrl; + + gDialog.PublishUrlInput.value = publishUrl; + } + else + { + ShowErrorInPanel(gDialog.SettingsTab, "MissingPublishUrlError", gDialog.PublishUrlInput); + return false; + } + var browseUrl = GetBrowseUrlInput(); + + var username = TrimString(gDialog.UsernameInput.value); + var savePassword = gDialog.SavePassword.checked; + var password = gDialog.PasswordInput.value; + var publishOtherFiles = gDialog.OtherDirCheckbox.checked; + + //XXX If there was a username and/or password in the publishUrl + // AND in the input field, which do we use? + // Let's use those in url only if input is empty + if (!username) + { + username = urlUserObj.value; + gDialog.UsernameInput.value = username; + gSettingsChanged = true; + } + if (!password) + { + password = urlPassObj.value; + gDialog.PasswordInput.value = password; + gSettingsChanged = true; + } + + // Update or add data for a site + var siteIndex = gDialog.SiteList.selectedIndex; + var newSite = false; + + if (siteIndex == -1) + { + // No site is selected, add a new site at the end + if (gPublishSiteData) + { + siteIndex = gPublishSiteData.length; + } + else + { + // First time: start entire site array + gPublishSiteData = new Array(1); + siteIndex = 0; + gDefaultSiteIndex = 0; + gDefaultSiteName = siteName; + } + gPublishSiteData[siteIndex] = {}; + gPublishSiteData[siteIndex].docDir = ""; + gPublishSiteData[siteIndex].otherDir = ""; + gPublishSiteData[siteIndex].dirList = [""]; + gPublishSiteData[siteIndex].publishOtherFiles = true; + gPublishSiteData[siteIndex].previousSiteName = siteName; + newSite = true; + } + gPublishSiteData[siteIndex].siteName = siteName; + gPublishSiteData[siteIndex].publishUrl = publishUrl; + gPublishSiteData[siteIndex].browseUrl = browseUrl; + gPublishSiteData[siteIndex].username = username; + // Don't save password in data that will be saved in prefs + gPublishSiteData[siteIndex].password = savePassword ? password : ""; + gPublishSiteData[siteIndex].savePassword = savePassword; + + if (publishOtherFiles != gPublishSiteData[siteIndex].publishOtherFiles) + gSettingsChanged = true; + + gPublishSiteData[siteIndex].publishOtherFiles = publishOtherFiles; + + gDialog.SiteList.selectedIndex = siteIndex; + if (siteIndex == gDefaultSiteIndex) + gDefaultSiteName = siteName; + + // Should never be empty, but be sure we have a default site + if (!gDefaultSiteName) + { + gDefaultSiteName = gPublishSiteData[0].siteName; + gDefaultSiteIndex = 0; + } + + // Rebuild the site menulist if we added a new site + if (newSite) + { + FillSiteList(); + gDialog.SiteList.selectedIndex = siteIndex; + } + else + { + // Update selected item if sitename changed + var selectedItem = gDialog.SiteList.selectedItem; + if (selectedItem) + { + var oldName = selectedItem.getAttribute("label"); + if (oldName != siteName) + { + selectedItem.setAttribute("label", siteName); + gDialog.SiteList.setAttribute("label", siteName); + gSettingsChanged = true; + if (oldName == gDefaultSiteName) + gDefaultSiteName = siteName; + } + } + } + + // Get the directory name in site to publish to + var docDir = GetDocDirInput(); + + gPublishSiteData[siteIndex].docDir = docDir; + + // And directory for images and other files + var otherDir = GetOtherDirInput(); + if (gDialog.SameLocationRadio.selected) + otherDir = docDir; + else + otherDir = GetOtherDirInput(); + + gPublishSiteData[siteIndex].otherDir = otherDir; + + // Fill return data object + gReturnData.siteName = siteName; + gReturnData.previousSiteName = gPublishSiteData[siteIndex].previousSiteName; + gReturnData.publishUrl = publishUrl; + gReturnData.browseUrl = browseUrl; + gReturnData.username = username; + // Note that we use the password for the next publish action + // even if savePassword is false; but we won't save it in PasswordManager database + gReturnData.password = password; + gReturnData.savePassword = savePassword; + gReturnData.docDir = gPublishSiteData[siteIndex].docDir; + gReturnData.otherDir = gPublishSiteData[siteIndex].otherDir; + gReturnData.publishOtherFiles = publishOtherFiles; + gReturnData.dirList = gPublishSiteData[siteIndex].dirList; + return true; +} + +function ValidateData() +{ + if (!ValidateSettings()) + return false; + + var siteIndex = gDialog.SiteList.selectedIndex; + if (siteIndex == -1) + return false; + + var filename = TrimString(gDialog.FilenameInput.value); + if (!filename) + { + ShowErrorInPanel(gDialog.PublishTab, "MissingPublishFilename", gDialog.FilenameInput); + return false; + } + gReturnData.filename = filename; + + return true; +} + +function ShowErrorInPanel(tab, errorMsgId, widgetWithError) +{ + SwitchPanel(tab); + ShowInputErrorMessage(GetString(errorMsgId)); + if (widgetWithError) + SetTextboxFocus(widgetWithError); +} + +function onAccept(event) +{ + if (ValidateData()) + { + // DON'T save the docDir and otherDir before trying to publish + gReturnData.saveDirs = false; + + // We save new site data to prefs only if we are attempting to publish + if (gSettingsChanged) + SavePublishDataToPrefs(gReturnData); + + // Set flag to resave data after publishing + // so we save docDir and otherDir if we published successfully + gReturnData.savePublishData = true; + + var title = TrimString(gDialog.PageTitleInput.value); + if (title != gPreviousTitle) + SetDocumentTitle(title); + + SaveWindowLocation(); + window.opener.ok = true; + return; + } + + event.preventDefault(); +} diff --git a/comm/suite/editor/components/dialogs/content/EditorPublish.xhtml b/comm/suite/editor/components/dialogs/content/EditorPublish.xhtml new file mode 100644 index 0000000000..ed15c9f7d2 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EditorPublish.xhtml @@ -0,0 +1,132 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/menulist.css" type="text/css"?> + +<?xul-overlay href="chrome://editor/content/EditorPublishOverlay.xhtml"?> + +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorPublish.dtd"> + +<dialog title="&windowTitle.label;" + id="publishDlg" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup()" + buttons="accept,cancel" + buttonlabelaccept="&publishButton.label;"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EditorPublish.js"/> + <script src="chrome://editor/content/publishprefs.js"/> + <script src="chrome://messenger/content/customElements.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <tabbox id="TabBox"> + <tabs flex="1"> + <tab id="PublishTab" oncommand="SelectPublishTab()" label="&publishTab.label;"/> + <tab id="SettingsTab" oncommand="SelectSettingsTab()" label="&settingsTab.label;"/> + </tabs> + <tabpanels> + <!-- PUBLISH PANEL --> + <vbox> + <spacer class="spacer"/> + <grid pack="start"> + <columns><column/><column/><column/></columns> + <rows> + <row align="center"> + <label value="&siteList.label;" + accesskey="&siteList.accesskey;" + control="SiteList"/> + <!-- Contents filled in at runtime --> + <menulist id="SiteList" + style="min-width:18em; max-width:18em;" crop="right" + tooltiptext="&siteList.tooltip;" + oncommand="SelectSiteList();"/> + <hbox> + <button label="&newSiteButton.label;" + accesskey="&newSiteButton.accesskey;" + oncommand="AddNewSite();"/> + <spacer flex="1"/> + </hbox> + </row> + <spacer class="spacer"/> + <row align="center"> + <label value="&pageTitle.label;" accesskey="&pageTitle.accesskey;" + control="PageTitleInput"/> + <textbox id="PageTitleInput" + tooltiptext="&pageTitle.tooltip;" class="minWidth15"/> + <label value="&pageTitleExample.label;"/> + </row> + <row align="center"> + <label value="&filename.label;" accesskey="&filename.accesskey;" + control="FilenameInput"/> + <textbox id="FilenameInput" + tooltiptext="&filename.tooltip;" class="minWidth15 uri-element"/> + <label value="&filenameExample.label;"/> + </row> + </rows> + </grid> + <spacer class="spacer"/> + <label value="&docDirList.label;" + accesskey="&docDirList.accesskey;" + control="DocDirList"/> + <hbox align="center"> + <!-- Contents filled in at runtime --> + <menulist is="menulist-editable" id="DocDirList" + class="minWidth20 uri-element" editable="true" flex="1" + tooltiptext="&docDirList.tooltip;" oninput="onInputSettings();"/> + </hbox> + <spacer class="spacer"/> + <groupbox> + <caption> + <checkbox id="OtherDirCheckbox" label="&publishImgCheckbox.label;" + accesskey="&publishImgCheckbox.accesskey;" + tooltiptext="&publishImgCheckbox.tooltip;" + oncommand="doEnabling();"/> + </caption> + <vbox> + <radiogroup id="OtherDirRadiogroup"> + <hbox> + <spacer class="checkbox-spacer"/> + <radio id="SameLocationRadio" label="&sameLocationRadio.label;" + accesskey="&sameLocationRadio.accesskey;" + tooltiptext="&sameLocationRadio.tooltip;" + oncommand="doEnabling();"/> + </hbox> + <hbox> + <spacer class="checkbox-spacer"/> + <radio id="UseSubdirRadio" label="&useSubdirRadio.label;" + accesskey="&useSubdirRadio.accesskey;" + tooltiptext="&useSubdirRadio.tooltip;" + oncommand="doEnabling();"/> + </hbox> + </radiogroup> + </vbox> + <hbox> + <spacer class="checkbox-spacer"/> + <spacer class="radio-spacer"/> + <!-- Contents filled in at runtime --> + <menulist is="menulist-editable" id="OtherDirList" + class="minWidth20 uri-element" + editable="true" flex="1" tooltiptext="&otherDirList.tooltip;" + oninput="onInputSettings();"/> + </hbox> + </groupbox> + <spacer flex="1"/> + </vbox><!-- Publish Panel --> + + <!-- SETTINGS PANEL --> + <hbox id="SettingsPanel"> + <!-- from EditorPublishOverlay.xhtml --> + <vbox id="PublishSettingsInputs" flex="1"/> + </hbox><!-- Settings Panel --> + </tabpanels> + </tabbox> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EditorPublishOverlay.xhtml b/comm/suite/editor/components/dialogs/content/EditorPublishOverlay.xhtml new file mode 100644 index 0000000000..136a75623c --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EditorPublishOverlay.xhtml @@ -0,0 +1,66 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE overlay SYSTEM "chrome://editor/locale/EditorPublish.dtd"> + +<overlay id="EditorPublishOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"> + +<vbox id="PublishSettingsInputs"> + <groupbox id="ServerSettingsBox"> + <label class="header">&serverInfo.label;</label> + <hbox align="center"> + <label value="&siteName.label;" accesskey="&siteName.accesskey;" + control="SiteNameInput"/> + <textbox id="SiteNameInput" class="MinWidth20em" flex="1" + tooltiptext="&siteName.tooltip;" oninput="onInputSettings();"/> + </hbox> + <spacer class="spacer"/> + <label value="&siteUrl.label;" accesskey="&siteUrl.accesskey;" + control="PublishUrlInput"/> + <textbox id="PublishUrlInput" class="MinWidth20em uri-element" + tooltiptext="&siteUrl.tooltip;" oninput="onInputSettings();"/> + <spacer class="spacer"/> + <label value="&browseUrl.label;" accesskey="&browseUrl.accesskey;" + control="BrowseUrlInput"/> + <textbox id="BrowseUrlInput" class="MinWidth20em uri-element" + tooltiptext="&browseUrl.tooltip;" oninput="onInputSettings();"/> + <spacer class="spacer"/> + </groupbox> + <groupbox id="LoginSettingsBox"> + <label class="header">&loginInfo.label;</label> + <grid> + <columns><column flex="1"/><column flex="3"/></columns> + <rows> + <row align="center"> + <label value="&username.label;" accesskey="&username.accesskey;" + control="UsernameInput"/> + <textbox id="UsernameInput" class="MinWidth10em" flex="1" + tooltiptext="&username.tooltip;" oninput="onInputSettings();"/> + </row> + <row align="center"> + <label value="&password.label;" accesskey="&password.accesskey;" + control="PasswordInput"/> + <hbox> + <textbox id="PasswordInput" type="password" class="MinWidth5em" + oninput="onInputSettings();" + tooltiptext="&password.tooltip;"/> + <checkbox id="SavePassword" label="&savePassword.label;" + accesskey="&savePassword.accesskey;" + tooltiptext="&savePassword.tooltip;" + oncommand="onInputSettings();"/> + </hbox> + </row> + </rows> + </grid> + <spacer class="spacer"/> + </groupbox> +</vbox> + +</overlay> diff --git a/comm/suite/editor/components/dialogs/content/EditorPublishProgress.js b/comm/suite/editor/components/dialogs/content/EditorPublishProgress.js new file mode 100644 index 0000000000..dafa053661 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EditorPublishProgress.js @@ -0,0 +1,391 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var gInProgress = true; +var gPublishData; +var gPersistObj; +var gTotalFileCount = 0; +var gSucceededCount = 0; +var gFinished = false; +var gPublishingFailed = false; +var gFileNotFound = false; +var gStatusMessage=""; + +var gTimerID; +var gAllowEnterKey = false; + +// Publishing error codes +// These are translated from C++ error code strings like this: +// kFileNotFound = "FILE_NOT_FOUND", +const kNetReset = 2152398868; // nsISocketTransportService.idl +const kFileNotFound = 2152857618; +const kNotConnected = 2152398860; // in netCore.h +const kConnectionRefused = 2152398861; // nsISocketTransportService.idl +const kNetTimeout = 2152398862; // nsISocketTransportService.idl +const kNoConnectionOrTimeout = 2152398878; +const kPortAccessNotAllowed = 2152398867; // netCore.h +const kOffline = 2152398865; // netCore.h +const kDiskFull = 2152857610; +const kNoDeviceSpace = 2152857616; +const kNameTooLong = 2152857617; +const kAccessDenied = 2152857621; + +// These are more errors that I don't think we encounter during publishing, +// so we don't have error strings yet. Let's keep them here for future reference +//const kUnrecognizedPath = 2152857601; +//const kUnresolvableSymlink = 2152857602; +//const kUnknownType = 2152857604; +//const kDestinationNotDir = 2152857605; +//const kTargetDoesNotExist = 2152857606; +//const kAlreadyExists = 2152857608; +//const kInvalidPath = 2152857609; +//const kNotDirectory = 2152857612; +//const kIsDirectory = 2152857613; +//const kIsLocked = 2152857614; +//const kTooBig = 2152857615; +//const kReadOnly = 2152857619; +//const kDirectoryNotEmpty = 2152857620; +//const kErrorBindingRedirected = 2152398851; +//const kAlreadyConnected = 2152398859; // in netCore.h +//const kInProgress = 2152398863; // netCore.h +//const kNoContent = 2152398865; // netCore.h +//const kUnknownProtocol = 2152398866 // netCore.h +//const kFtpLogin = 2152398869; // ftpCore.h +//const kFtpCWD = 2152398870; // ftpCore.h +//const kFtpPasv = 2152398871; // ftpCore.h +//const kFtpPwd = 2152398872; // ftpCore.h + +document.addEventListener("dialogaccept", onEnterKey); +document.addEventListener("dialogcancel", onClose); + +function Startup() +{ + gPublishData = window.arguments[0]; + if (!gPublishData) + { + dump("No publish data!\n"); + window.close(); + return; + } + + gDialog.FileList = document.getElementById("FileList"); + gDialog.FinalStatusMessage = document.getElementById("FinalStatusMessage"); + gDialog.StatusMessage = document.getElementById("StatusMessage"); + gDialog.KeepOpen = document.getElementById("KeepOpen"); + gDialog.Close = document.documentElement.getButton("cancel"); + + SetWindowLocation(); + var title = GetDocumentTitle(); + if (!title) + title = "(" + opener.gUntitledString + ")"; + document.title = GetString("PublishProgressCaption").replace(/%title%/, title); + + document.getElementById("PublishToSite").value = + GetString("PublishToSite").replace(/%title%/, TruncateStringAtWordEnd(gPublishData.siteName, 25)); + + // Show publishing destination URL + document.getElementById("PublishUrl").value = gPublishData.publishUrl; + + // Show subdirectories only if not empty + if (gPublishData.docDir || gPublishData.otherDir) + { + if (gPublishData.docDir) + document.getElementById("docDir").value = gPublishData.docDir; + else + document.getElementById("DocSubdir").hidden = true; + + if (gPublishData.publishOtherFiles && gPublishData.otherDir) + document.getElementById("otherDir").value = gPublishData.otherDir; + else + document.getElementById("OtherSubdir").hidden = true; + } + else + document.getElementById("Subdirectories").hidden = true; + + // Add the document to the "publish to" list as quick as possible! + SetProgressStatus(gPublishData.filename, "busy"); + + if (gPublishData.publishOtherFiles) + { + // When publishing images as well, expand list to show more items + gDialog.FileList.setAttribute("rows", 5); + window.sizeToContent(); + } + + // Now that dialog is initialized, we can start publishing + gPersistObj = window.opener.StartPublishing(); +} + +// this function is to be used when we cancel persist's saving +// since not all messages will be returned to us if we cancel +// this function changes status for all non-done/non-failure to failure +function SetProgressStatusCancel() +{ + let listitems = document.querySelectorAll('listitem:not([progress="done"]):not([progress="failed"])'); + if (!listitems) + return; + + for (var i=0; i < listitems.length; i++) + { + listitems[i].setAttribute("progress", "failed"); + } +} + +// Add filename to list of files to publish +// or set status for file already in the list +// Returns true if file was in the list +function SetProgressStatus(filename, status) +{ + if (!filename) + return false; + + if (!status) + status = "busy"; + + // Just set attribute for status icon if we already have this filename. + let listitem = document.querySelector('listitem[label="' + filename + '"]'); + if (listitem) + { + listitem.setAttribute("progress", status); + return true; + } + // We're adding a new file item to list + gTotalFileCount++; + + listitem = document.createXULElement("listitem"); + if (listitem) + { + listitem.setAttribute("class", "listitem-iconic progressitem"); + // This triggers CSS to show icon for each status state + listitem.setAttribute("progress", status); + listitem.setAttribute("label", filename); + gDialog.FileList.appendChild(listitem); + } + return false; +} + +function SetProgressFinished(filename, networkStatus) +{ + var abortPublishing = false; + if (filename) + { + var status = networkStatus ? "failed" : "done"; + if (networkStatus == 0) + gSucceededCount++; + + SetProgressStatus(filename, status); + } + + if (networkStatus != 0) // Error condition + { + // We abort on all errors except if image file was not found + abortPublishing = networkStatus != kFileNotFound; + + // Mark all remaining files as "failed" + if (abortPublishing) + { + gPublishingFailed = true; + SetProgressStatusCancel(); + gDialog.FinalStatusMessage.value = GetString("PublishFailed"); + } + + switch (networkStatus) + { + case kFileNotFound: + gFileNotFound = true; + if (filename) + gStatusMessage = GetString("FileNotFound").replace(/%file%/, filename); + break; + case kNetReset: + // We get this when subdir doesn't exist AND + // if filename used is same as an existing subdir + var dir = (gPublishData.filename == filename) ? + gPublishData.docDir : gPublishData.otherDir; + + if (dir) + { + // This is the ambiguous case when we can't tell if subdir or filename is bad + // Remove terminal "/" from dir string and insert into message + gStatusMessage = GetString("SubdirDoesNotExist").replace(/%dir%/, dir.slice(0, dir.length-1)); + gStatusMessage = gStatusMessage.replace(/%file%/, filename); + + // Remove directory from saved prefs + // XXX Note that if subdir is good, + // but filename = next level subdirectory name, + // we really shouldn't remove subdirectory, + // but it's impossible to differentiate this case! + RemovePublishSubdirectoryFromPrefs(gPublishData, dir); + } + else if (filename) + gStatusMessage = GetString("FilenameIsSubdir").replace(/%file%/, filename); + + break; + case kNotConnected: + case kConnectionRefused: + case kNetTimeout: + case kNoConnectionOrTimeout: + case kPortAccessNotAllowed: + gStatusMessage = GetString("ServerNotAvailable"); + break; + case kOffline: + gStatusMessage = GetString("Offline"); + break; + case kDiskFull: + case kNoDeviceSpace: + if (filename) + gStatusMessage = GetString("DiskFull").replace(/%file%/, filename); + break; + case kNameTooLong: + if (filename) + gStatusMessage = GetString("NameTooLong").replace(/%file%/, filename); + break; + case kAccessDenied: + if (filename) + gStatusMessage = GetString("AccessDenied").replace(/%file%/, filename); + break; + case kUnknownType: + default: + gStatusMessage = GetString("UnknownPublishError") + break; + } + } + else if (!filename) + { + gFinished = true; + + document.documentElement.setAttribute("buttonlabelcancel", + document.documentElement.getAttribute("buttonlabelclose")); + + if (!gStatusMessage) + gStatusMessage = GetString(gPublishingFailed ? "UnknownPublishError" : "AllFilesPublished"); + + // Now allow "Enter/Return" key to close the dialog + AllowDefaultButton(); + + if (gPublishingFailed || gFileNotFound) + { + // Show "Troubleshooting" button to help solving problems + // and key for successful / failed files + document.getElementById("failureBox").hidden = false; + } + } + + if (gStatusMessage) + SetStatusMessage(gStatusMessage); +} + +function CheckKeepOpen() +{ + if (gTimerID) + { + clearTimeout(gTimerID); + gTimerID = null; + } +} + +function onClose() +{ + if (!gFinished) + { + const buttonFlags = (Services.prompt.BUTTON_TITLE_IS_STRING * + Services.prompt.BUTTON_POS_0) + + (Services.prompt.BUTTON_TITLE_CANCEL * + Services.prompt.BUTTON_POS_1); + let button = Services.prompt.confirmEx(window, + GetString("CancelPublishTitle"), + GetString("CancelPublishMessage"), + buttonFlags, + GetString("CancelPublishContinue"), + null, null, null, {}); + if (button == 0) + return false; + } + + if (gTimerID) + { + clearTimeout(gTimerID); + gTimerID = null; + } + + if (!gFinished && gPersistObj) + { + try { + gPersistObj.cancelSave(); + } catch (e) {} + } + SaveWindowLocation(); + + // Tell caller so they can cleanup and restore editability + window.opener.FinishPublishing(); + return true; +} + +function AllowDefaultButton() +{ + gDialog.Close.setAttribute("default","true"); + gAllowEnterKey = true; +} + +function onEnterKey(event) +{ + if (gAllowEnterKey) + return CloseDialog(); + + event.preventDefault(); +} + +function RequestCloseDialog() +{ + // Finish progress messages, settings buttons etc. + SetProgressFinished(null, 0); + + if (!gDialog.KeepOpen.checked) + { + // Leave window open a minimum amount of time + gTimerID = setTimeout(CloseDialog, 3000); + } + + // Set "completed" message if we succeeded + // (Some image files may have failed, + // but we don't abort publishing for that) + if (!gPublishingFailed) + { + gDialog.FinalStatusMessage.value = GetString("PublishCompleted"); + if (gFileNotFound && gTotalFileCount-gSucceededCount) + { + // Show number of files that failed to upload + gStatusMessage = + (GetString("FailedFileMsg").replace(/%x%/,(gTotalFileCount-gSucceededCount))) + .replace(/%total%/,gTotalFileCount); + + SetStatusMessage(gStatusMessage); + } + } +} + +function SetStatusMessage(message) +{ + // Status message is a child of <description> element + // so text can wrap to multiple lines if necessary + if (gDialog.StatusMessage.firstChild) + { + gDialog.StatusMessage.firstChild.data = message; + } + else + { + var textNode = document.createTextNode(message); + if (textNode) + gDialog.StatusMessage.appendChild(textNode); + } + window.sizeToContent(); +} + +function CloseDialog() +{ + SaveWindowLocation(); + window.opener.FinishPublishing(); + try { + window.close(); + } catch (e) {} +} diff --git a/comm/suite/editor/components/dialogs/content/EditorPublishProgress.xhtml b/comm/suite/editor/components/dialogs/content/EditorPublishProgress.xhtml new file mode 100644 index 0000000000..2cf422c8b5 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EditorPublishProgress.xhtml @@ -0,0 +1,66 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorPublishProgress.dtd"> + +<dialog title="" + id="publishProgressDlg" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + buttons="cancel" + buttonlabelclose="&closeButton.label;" + onload="Startup()"> + + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/publishprefs.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EditorPublishProgress.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <groupbox> + <caption><label id="PublishToSite"/></caption> + <label value="&siteUrl.label;"/> + <hbox> + <label class="indent bold" id="PublishUrl"/> + </hbox> + <spacer class="spacer"/> + <grid id="Subdirectories"> + <columns><column/><column/></columns> + <rows> + <row id="DocSubdir"> + <label value="&docSubdir.label;"/> + <label id="docDir"/> + </row> + <row id="OtherSubdir"> + <label value="&otherSubdir.label;"/> + <label id="otherDir"/> + </row> + </rows> + </grid> + <label id="OtherUrl" class="bold" style="margin-left:3em"/> + </groupbox> + <groupbox> + <caption><label value="&fileList.label;"/></caption> + <vbox align="center" style="max-width:30em"> + <label id="FinalStatusMessage" class="bold" value="&status.label;"/> + </vbox> + <description id="StatusMessage" class="wrap" style="max-width:30em; min-height: 1em"/> + <vbox flex="1"> + <listbox id="FileList" rows="1"/> + </vbox> + <hbox align="center" id="failureBox" hidden="true"> + <image class="progressitem" progress="done"/> + <label value="&succeeded.label;"/> + <spacer class="bigspacer"/> + <image class="progressitem" progress="failed"/> + <label value="&failed.label;"/> + </hbox> + </groupbox> + <checkbox id="KeepOpen" label="&keepOpen;" oncommand="CheckKeepOpen();" persist="checked"/> + <separator class="groove"/> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EditorPublishSettings.js b/comm/suite/editor/components/dialogs/content/EditorPublishSettings.js new file mode 100644 index 0000000000..01677ae8c0 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EditorPublishSettings.js @@ -0,0 +1,343 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var gPublishSiteData; +var gPublishDataChanged = false; +var gDefaultSiteIndex = -1; +var gDefaultSiteName; +var gPreviousDefaultSite; +var gPreviousTitle; +var gSettingsChanged = false; +var gSiteDataChanged = false; +var gAddNewSite = false; +var gCurrentSiteIndex = -1; +var gPasswordManagerOn = true; + +// Dialog initialization code + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +function Startup() +{ + if (!GetCurrentEditor()) + { + window.close(); + return; + } + + gDialog.SiteList = document.getElementById("SiteList"); + gDialog.SiteNameInput = document.getElementById("SiteNameInput"); + gDialog.PublishUrlInput = document.getElementById("PublishUrlInput"); + gDialog.BrowseUrlInput = document.getElementById("BrowseUrlInput"); + gDialog.UsernameInput = document.getElementById("UsernameInput"); + gDialog.PasswordInput = document.getElementById("PasswordInput"); + gDialog.SavePassword = document.getElementById("SavePassword"); + gDialog.SetDefaultButton = document.getElementById("SetDefaultButton"); + gDialog.RemoveSiteButton = document.getElementById("RemoveSiteButton"); + gDialog.OkButton = document.documentElement.getButton("accept"); + + gPublishSiteData = GetPublishSiteData(); + gDefaultSiteName = GetDefaultPublishSiteName(); + gPreviousDefaultSite = gDefaultSiteName; + + gPasswordManagerOn = Services.prefs.getBoolPref("signon.rememberSignons"); + gDialog.SavePassword.disabled = !gPasswordManagerOn; + + InitDialog(); + + SetWindowLocation(); +} + +function InitDialog() +{ + // If there's no current site data, start a new item in the Settings panel + if (!gPublishSiteData) + { + AddNewSite(); + } + else + { + FillSiteList(); + + // uncomment next code line if you want preselection of the default + // publishing site + //InitSiteSettings(gDefaultSiteIndex); + + SetTextboxFocus(gDialog.SiteNameInput); + } +} + +function FillSiteList() +{ + // Prevent triggering SelectSiteList() actions + gIsSelecting = true; + ClearListbox(gDialog.SiteList); + gIsSelecting = false; + gDefaultSiteIndex = -1; + + // Fill the site list + var count = gPublishSiteData.length; + for (var i = 0; i < count; i++) + { + var name = gPublishSiteData[i].siteName; + var item = gDialog.SiteList.appendItem(name); + SetPublishItemStyle(item); + if (name == gDefaultSiteName) + gDefaultSiteIndex = i; + } +} + +function SetPublishItemStyle(item) +{ + // Display default site with bold style + if (item) + { + if (item.getAttribute("label") == gDefaultSiteName) + item.setAttribute("class", "bold"); + else + item.removeAttribute("class"); + } +} + +function AddNewSite() +{ + // Save any pending changes locally first + if (!ApplyChanges()) + return; + + // Initialize Setting widgets to none of the selected sites + InitSiteSettings(-1); + gAddNewSite = true; + + SetTextboxFocus(gDialog.SiteNameInput); +} + +function RemoveSite() +{ + if (!gPublishSiteData) + return; + + var index = gDialog.SiteList.selectedIndex; + if (index != -1) + { + let item = gDialog.SiteList.selectedItem; + var nameToRemove = item.getAttribute("label"); + + // Remove one item from site data array + gPublishSiteData.splice(index, 1); + // Remove item from site list + gDialog.SiteList.clearSelection(); + item.remove(); + + // Adjust if we removed last item and reselect a site + if (index >= gPublishSiteData.length) + index--; + InitSiteSettings(index); + + if (nameToRemove == gDefaultSiteName) + { + // Deleting current default -- set to new selected item + // Arbitrary, but what else to do? + SetDefault(); + } + gSiteDataChanged = true; + } +} + +function SetDefault() +{ + if (!gPublishSiteData) + return; + + var index = gDialog.SiteList.selectedIndex; + if (index != -1) + { + gDefaultSiteIndex = index; + gDefaultSiteName = gPublishSiteData[index].siteName; + + // Set bold style on new default + var item = gDialog.SiteList.firstChild; + while (item) + { + SetPublishItemStyle(item); + item = item.nextSibling; + } + } +} + +// Recursion prevention: +// Use when you don't want to trigger ApplyChanges and InitSiteSettings +var gIsSelecting = false; + +function SelectSiteList() +{ + if (gIsSelecting) + return; + + gIsSelecting = true; + var newIndex = gDialog.SiteList.selectedIndex; + + // Save any pending changes locally first + if (!ApplyChanges()) + return; + + InitSiteSettings(newIndex); + + gIsSelecting = false; +} + +// Use this to prevent recursion in SelectSiteList +function SetSelectedSiteIndex(index) +{ + gIsSelecting = true; + gDialog.SiteList.selectedIndex = index; + gIsSelecting = false; +} + +function InitSiteSettings(selectedSiteIndex) +{ + // Index to the site we will need to update if settings changed + gCurrentSiteIndex = selectedSiteIndex; + + SetSelectedSiteIndex(selectedSiteIndex); + var haveData = (gPublishSiteData && selectedSiteIndex != -1); + + gDialog.SiteNameInput.value = haveData ? gPublishSiteData[selectedSiteIndex].siteName : ""; + gDialog.PublishUrlInput.value = haveData ? gPublishSiteData[selectedSiteIndex].publishUrl : ""; + gDialog.BrowseUrlInput.value = haveData ? gPublishSiteData[selectedSiteIndex].browseUrl : ""; + gDialog.UsernameInput.value = haveData ? gPublishSiteData[selectedSiteIndex].username : ""; + + var savePassord = haveData && gPasswordManagerOn; + gDialog.PasswordInput.value = savePassord ? gPublishSiteData[selectedSiteIndex].password : ""; + gDialog.SavePassword.checked = savePassord ? gPublishSiteData[selectedSiteIndex].savePassword : false; + + gDialog.SetDefaultButton.disabled = !haveData; + gDialog.RemoveSiteButton.disabled = !haveData; + gSettingsChanged = false; +} + +function onInputSettings() +{ + // TODO: Save current data during SelectSite1 and compare here + // to detect if real change has occurred? + gSettingsChanged = true; +} + +function ApplyChanges() +{ + if (gSettingsChanged && !UpdateSettings()) + { + // Restore selection to previously current site + SetSelectedSiteIndex(gCurrentSiteIndex); + return false; + } + return true; +} + +function UpdateSettings() +{ + // Validate and add new site + var newName = TrimString(gDialog.SiteNameInput.value); + if (!newName) + { + ShowInputErrorMessage(GetString("MissingSiteNameError"), gDialog.SiteNameInput); + return false; + } + if (PublishSiteNameExists(newName, gPublishSiteData, gCurrentSiteIndex)) + { + ShowInputErrorMessage(GetString("DuplicateSiteNameError").replace(/%name%/, newName)); + SetTextboxFocus(gDialog.SiteNameInput); + return false; + } + + var newUrl = FormatUrlForPublishing(gDialog.PublishUrlInput.value); + if (!newUrl) + { + ShowInputErrorMessage(GetString("MissingPublishUrlError"), gDialog.PublishUrlInput); + return false; + } + + // Start assuming we're updating existing site at gCurrentSiteIndex + var newSiteData = false; + + if (!gPublishSiteData) + { + // First time used - Create the first site profile + gPublishSiteData = new Array(1); + gCurrentSiteIndex = 0; + newSiteData = true; + } + else if (gCurrentSiteIndex == -1) + { + // No currently-selected site, + // must be adding a new site + // Add new data at the end of list + gCurrentSiteIndex = gPublishSiteData.length; + newSiteData = true; + } + + if (newSiteData) + { + // Init new site profile + gPublishSiteData[gCurrentSiteIndex] = {}; + gPublishSiteData[gCurrentSiteIndex].docDir = ""; + gPublishSiteData[gCurrentSiteIndex].otherDir = ""; + gPublishSiteData[gCurrentSiteIndex].dirList = [""]; + gPublishSiteData[gCurrentSiteIndex].previousSiteName = newName; + } + + gPublishSiteData[gCurrentSiteIndex].siteName = newName; + gPublishSiteData[gCurrentSiteIndex].publishUrl = newUrl; + gPublishSiteData[gCurrentSiteIndex].browseUrl = FormatUrlForPublishing(gDialog.BrowseUrlInput.value); + gPublishSiteData[gCurrentSiteIndex].username = TrimString(gDialog.UsernameInput.value); + gPublishSiteData[gCurrentSiteIndex].password= gDialog.PasswordInput.value; + gPublishSiteData[gCurrentSiteIndex].savePassword = gDialog.SavePassword.checked; + + if (gCurrentSiteIndex == gDefaultSiteIndex) + gDefaultSiteName = newName; + + // When adding the very first site, assume that's the default + if (gPublishSiteData.length == 1 && !gDefaultSiteName) + { + gDefaultSiteName = gPublishSiteData[0].siteName; + gDefaultSiteIndex = 0; + } + + FillSiteList(); + + // Select current site in list + SetSelectedSiteIndex(gCurrentSiteIndex); + + // Signal saving data to prefs + gSiteDataChanged = true; + + // Clear current site flags + gSettingsChanged = false; + gAddNewSite = false; + + return true; +} + +function onAccept(event) +{ + // Save any pending changes locally first + if (!ApplyChanges()) { + event.preventDefault(); + return; + } + + if (gSiteDataChanged) + { + // Save all local data to prefs + SavePublishSiteDataToPrefs(gPublishSiteData, gDefaultSiteName); + } + else if (gPreviousDefaultSite != gDefaultSiteName) + { + // only the default site was changed + SetDefaultSiteName(gDefaultSiteName); + } + + SaveWindowLocation(); +} diff --git a/comm/suite/editor/components/dialogs/content/EditorPublishSettings.xhtml b/comm/suite/editor/components/dialogs/content/EditorPublishSettings.xhtml new file mode 100644 index 0000000000..ace6c2fc5d --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EditorPublishSettings.xhtml @@ -0,0 +1,50 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<?xul-overlay href="chrome://editor/content/EditorPublishOverlay.xhtml"?> + +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorPublish.dtd"> + +<dialog title="&windowTitleSettings.label;" + id="publishSettingsDlg" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload="Startup()" + buttons="accept,cancel"> + + <!-- Methods common to all editor dialogs --> + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EditorPublishSettings.js"/> + <script src="chrome://editor/content/publishprefs.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <hbox id="SettingsPanel"> + <groupbox align="center"> + <label class="header">&publishSites.label;</label> + <!-- XXX: If tree isn't wrapped in vbox, it appears BELOW next vbox --> + <vbox flex="1"> + <listbox rows="4" id="SiteList" flex="1" onselect="SelectSiteList();"/> + </vbox> + <hbox pack="center"> + <vbox> + <button id="NewSiteButton" label="&newSiteButton.label;" + accesskey="&newSiteButton.accesskey;" oncommand="AddNewSite();"/> + <button id="SetDefaultButton" label="&setDefaultButton.label;" + accesskey="&setDefaultButton.accesskey;" oncommand="SetDefault();"/> + <button id="RemoveSiteButton" label="&removeButton.label;" + accesskey="&removeButton.accesskey;" oncommand="RemoveSite();"/> + </vbox> + </hbox> + </groupbox> + <!-- from EditorPublishOverlay.xhtml --> + <vbox id="PublishSettingsInputs"/> + </hbox> + <spacer class="spacer"/> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.js b/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.js new file mode 100644 index 0000000000..745a8bfd30 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.js @@ -0,0 +1,155 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +var {CharsetMenu} = ChromeUtils.import("resource://gre/modules/CharsetMenu.jsm"); + +var gCharset=""; +var gTitleWasEdited = false; +var gCharsetWasChanged = false; +var gInsertNewContentType = false; +var gContenttypeElement; +var gInitDone = false; +var gCharsetInfo; + +//Cancel() is in EdDialogCommon.js + +var gCharsetView = { + get rowCount() { return gCharsetInfo.length; }, + selection: null, + getRowProperties: function(index) { return ""; }, + getCellProperties: function(index, column) { return ""; }, + getColumnProperties: function(columm) { return ""; }, + isContainer: function() { return false; }, + isContainerOpen: function() { return false; }, + isContainerEmpty: function() { return true; }, + isSeparator: function() { return false; }, + isSorted: function() { return false; }, + canDrop: function(index, orientation) { return false; }, + drop: function(index, orientation) {}, + getParentIndex: function(index) { return -1; }, + hasNextSibling: function(index, after) { return false; }, + getLevel: function(index) { return 1; }, + getImageSrc: function(index) { return null; }, + getProgressMode: function(index) { return 0; }, + getCellValue: function(index) { return ""; }, + getCellText: function(index) { return gCharsetInfo[index].label; }, + toggleOpenState: function(index) {}, + cycleHeader: function(column) {}, + selectionChanged: function() {}, + cycleCell: function(index, column) {}, + isEditable: function isEditable(index, column) { return false; }, +}; + +function Startup() +{ + var editor = GetCurrentEditor(); + if (!editor) + { + window.close(); + return; + } + + gDialog.TitleInput = document.getElementById("TitleInput"); + gDialog.charsetTree = document.getElementById('CharsetTree'); + gDialog.exportToText = document.getElementById('ExportToText'); + + gContenttypeElement = GetMetaElementByAttribute("http-equiv", "content-type"); + if (!gContenttypeElement && (editor.contentsMIMEType != 'text/plain')) + { + gContenttypeElement = CreateMetaElementWithAttribute("http-equiv", "content-type"); + if (!gContenttypeElement ) + { + window.close(); + return; + } + gInsertNewContentType = true; + } + + try { + gCharset = editor.documentCharacterSet; + } catch (e) {} + + var data = CharsetMenu.getData(); + var charsets = data.pinnedCharsets.concat(data.otherCharsets); + gCharsetInfo = CharsetMenu.getCharsetInfo(charsets.map(info => info.value)); + gDialog.charsetTree.view = gCharsetView; + + InitDialog(); + + // Use the same text as the messagebox for getting title by regular "Save" + document.getElementById("EnterTitleLabel").setAttribute("value",GetString("NeedDocTitle")); + // This is an <HTML> element so it wraps -- append a child textnode + var helpTextParent = document.getElementById("TitleHelp"); + var helpText = document.createTextNode(GetString("DocTitleHelp")); + if (helpTextParent) + helpTextParent.appendChild(helpText); + + // SET FOCUS TO FIRST CONTROL + SetTextboxFocus(gDialog.TitleInput); + + gInitDone = true; + + SetWindowLocation(); +} + + +function InitDialog() +{ + gDialog.TitleInput.value = GetDocumentTitle(); + + var tree = gDialog.charsetTree; + var index = gCharsetInfo.map(info => info.value).indexOf(gCharset); + if (index >= 0) { + tree.view.selection.select(index); + tree.ensureRowIsVisible(index); + } +} + + +function onAccept() +{ + var editor = GetCurrentEditor(); + editor.beginTransaction(); + + if(gCharsetWasChanged) + { + try { + SetMetaElementContent(gContenttypeElement, "text/html; charset=" + gCharset, gInsertNewContentType, true); + editor.documentCharacterSet = gCharset; + } catch (e) {} + } + + editor.endTransaction(); + + if(gTitleWasEdited) + SetDocumentTitle(TrimString(gDialog.TitleInput.value)); + + window.opener.ok = true; + window.opener.exportToText = gDialog.exportToText.checked; + SaveWindowLocation(); +} + + +function SelectCharset() +{ + if(gInitDone) + { + try + { + gCharset = gCharsetInfo[gDialog.charsetTree.currentIndex].value; + if (gCharset) + gCharsetWasChanged = true; + } + catch(e) {} + } +} + + +function TitleChanged() +{ + gTitleWasEdited = true; +} diff --git a/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.xhtml b/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.xhtml new file mode 100644 index 0000000000..815a56dfe3 --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.xhtml @@ -0,0 +1,46 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?> +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorSaveAsCharset.dtd"> + +<dialog title="&windowTitle2.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + onload = "Startup()" + style="width: 32em;"> + + <script src="chrome://editor/content/editorUtilities.js"/> + <script src="chrome://editor/content/EdDialogCommon.js"/> + <script src="chrome://editor/content/EditorSaveAsCharset.js"/> + + <spacer id="location" offsetY="50" persist="offsetX offsetY"/> + + <groupbox> + <hbox class="groupbox-title"> + <label class="header">&documentTitleTitle.label;</label> + </hbox> + <label id="EnterTitleLabel"/> + <textbox id="TitleInput" oninput="TitleChanged();"/> + <description id="TitleHelp" class="wrap" style="width:1em" /> + </groupbox> + + <groupbox flex="1"> + <hbox class="groupbox-title"> + <label class="header">&documentCharsetTitle2.label;</label> + </hbox> + <label value="&documentCharsetDesc2.label;"/> + <tree id="CharsetTree" rows="8" hidecolumnpicker="true" onselect="SelectCharset();"> + <treecols> + <treecol id="CharsetCol" flex="1" hideheader="true"/> + </treecols> + <treechildren/> + </tree> + </groupbox> + + <checkbox id="ExportToText" label="&documentExportToText.label;" /> + <separator class="groove"/> +</dialog> diff --git a/comm/suite/editor/components/dialogs/content/edImage.inc.xhtml b/comm/suite/editor/components/dialogs/content/edImage.inc.xhtml new file mode 100644 index 0000000000..e80fb0457c --- /dev/null +++ b/comm/suite/editor/components/dialogs/content/edImage.inc.xhtml @@ -0,0 +1,248 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + <vbox id="imageLocation"> + <spacer class="spacer"/> + <label control = "srcInput" + value = "&locationEditField.label;" + accesskey="&locationEditField.accessKey;" + tooltiptext="&locationEditField.tooltip;" + /> + <tooltip id="shortenedDataURI"> + <label value="&locationEditField.shortenedDataURI;"/> + </tooltip> + <textbox id="srcInput" oninput="ChangeImageSrc();" tabindex="1" class="uri-element" + tooltiptext="&locationEditField.tooltip;"/> + <hbox id="MakeRelativeHbox"> + <checkbox id="MakeRelativeCheckbox" + for="srcInput" + tabindex="2" + label="&makeUrlRelative.label;" + accesskey="&makeUrlRelative.accessKey;" + oncommand="MakeInputValueRelativeOrAbsolute(this);" + tooltiptext="&makeUrlRelative.tooltip;"/> + <checkbox id="AttachSourceToMail" + hidden="true" + label="&attachImageSource.label;" + accesskey="&attachImageSource.accesskey;" + oncommand="DoAttachSourceCheckbox()"/> + <spacer flex="1"/> + <button id="ChooseFile" + tabindex="3" + oncommand="chooseFile()" + label="&chooseFileButton.label;" + accesskey="&chooseFileButton.accessKey;"/> + </hbox> + <spacer class="spacer"/> + <radiogroup id="altTextRadioGroup" flex="1"> + <grid> + <columns><column/><column flex="1"/></columns> + <rows> + <row align="center"> + <label + style = "margin-left: 26px" + control = "titleInput" + accesskey = "&title.accessKey;" + value ="&title.label;" + tooltiptext="&title.tooltip;" + for = "titleInput"/> + <textbox flex="1" + id = "titleInput" + class = "MinWidth20em" + tooltiptext="&title.tooltip;" + tabindex="4"/> + </row> + <row align="center"> + <radio id="altTextRadio" value="usealt-yes" + label="&altText.label;" + accesskey="&altText.accessKey;" + tooltiptext="&altTextEditField.tooltip;" +#ifndef MOZ_SUITE + persist="selected" +#endif + oncommand = "SetAltTextDisabled(false);" + tabindex="5"/> + <textbox flex="1" + id = "altTextInput" + class = "MinWidth20em" + tooltiptext="&altTextEditField.tooltip;" + oninput = "SetAltTextDisabled(false);" + tabindex="6"/> + </row> + </rows> + </grid> + + <radio id="noAltTextRadio" value="usealt-no" + label="&noAltText.label;" + accesskey = "&noAltText.accessKey;" +#ifndef MOZ_SUITE + persist="selected" +#endif + oncommand = "SetAltTextDisabled(true);"/> + </radiogroup> + </vbox> + + <vbox id="imageDimensions" align="start"> + <spacer class="spacer"/> + <hbox> + <radiogroup id="imgSizeGroup"> + <radio + id = "actualSizeRadio" + label = "&actualSizeRadio.label;" + accesskey = "&actualSizeRadio.accessKey;" + tooltiptext="&actualSizeRadio.tooltip;" + oncommand = "SetActualSize()" + value="actual"/> + <radio + id = "customSizeRadio" + label = "&customSizeRadio.label;" + selected = "true" + accesskey = "&customSizeRadio.accessKey;" + tooltiptext="&customSizeRadio.tooltip;" + oncommand = "doDimensionEnabling();" + value="custom"/> + </radiogroup> + <spacer flex="1"/> + <vbox> + <spacer flex="1"/> + <checkbox id="constrainCheckbox" label="&constrainCheckbox.label;" + accesskey="&constrainCheckbox.accessKey;" + oncommand="ToggleConstrain()" + tooltiptext="&constrainCheckbox.tooltip;"/> + </vbox> + <spacer flex="1"/> + </hbox> + <spacer class="spacer"/> + <grid class="indent"> + <columns><column/><column/><column flex="1"/></columns> + <rows> + <row align="center"> + <label id = "widthLabel" + control = "widthInput" + accesskey = "&widthEditField.accessKey;" + value = "&widthEditField.label;" /> + <textbox + id = "widthInput" + class = "narrow" + oninput = "constrainProportions(this.id, 'heightInput')"/> + <menulist id = "widthUnitsMenulist" + oncommand = "doDimensionEnabling();" /> + <!-- contents are appended by JS --> + </row> + <row align="center"> + <label id = "heightLabel" + control = "heightInput" + accesskey = "&heightEditField.accessKey;" + value = "&heightEditField.label;" /> + <textbox + id = "heightInput" + class = "narrow" + oninput = "constrainProportions(this.id, 'widthInput')"/> + <menulist id = "heightUnitsMenulist" + oncommand = "doDimensionEnabling();" /> + <!-- contents are appended by JS --> + </row> + </rows> + </grid> + <spacer flex="1"/> + </vbox> + + <hbox id="imageAppearance"> + <groupbox> + <hbox class="groupbox-title"> + <label id="spacingLabel" class="header">&spacingBox.label;</label> + </hbox> + <grid> + <columns><column/><column/><column/></columns> + <rows> + <row align="center"> + <label + class = "align-right" + id = "leftrightLabel" + control = "imageleftrightInput" + accesskey = "&leftRightEditField.accessKey;" + value = "&leftRightEditField.label;"/> + <textbox + class = "narrow" + id = "imageleftrightInput" + oninput = "forceInteger(this.id)"/> + <label + id = "leftrighttypeLabel" + value = "&pixelsPopup.value;" /> + </row> + <spacer class="spacer"/> + <row align="center"> + <label + class = "align-right" + id = "topbottomLabel" + control = "imagetopbottomInput" + accesskey = "&topBottomEditField.accessKey;" + value = "&topBottomEditField.label;"/> + <textbox + class = "narrow" + id = "imagetopbottomInput" + oninput = "forceInteger(this.id)"/> + <label id="topbottomtypeLabel" + value="&pixelsPopup.value;" /> + </row> + <spacer class="spacer"/> + <row align="center"> + <label class="align-right" + id="borderLabel" + control="border" + accesskey="&borderEditField.accessKey;" + value="&borderEditField.label;"/> + <textbox + class = "narrow" + id = "border" + oninput = "forceInteger(this.id)"/> + <label id="bordertypeLabel" + value="&pixelsPopup.value;" /> + </row> + </rows> + </grid> + </groupbox> + + <vbox> + <groupbox align="start"> + <hbox class="groupbox-title"> + <label id="alignLabel" class="header">&alignment.label;</label> + </hbox> + <menulist id="alignTypeSelect" class="align-menu"> + <menupopup> + <menuitem class="align-menu menuitem-iconic" + value="top" + label="&topPopup.value;"/> + <menuitem class="align-menu menuitem-iconic" + value="middle" + label="¢erPopup.value;"/> + <menuitem class="align-menu menuitem-iconic" + value="bottom" + label="&bottomPopup.value;"/> + <!-- HTML attribute value is opposite of the button label on purpose --> + <menuitem class="align-menu menuitem-iconic" + value="right" + label="&wrapLeftPopup.value;"/> + <menuitem class="align-menu menuitem-iconic" + value="left" + label="&wrapRightPopup.value;"/> + </menupopup> + </menulist> + </groupbox> + + <groupbox> + <hbox class="groupbox-title"> + <label id="imagemapLabel" class="header">&imagemapBox.label;</label> + </hbox> + <hbox equalsize="always"> + <button id="removeImageMap" + oncommand="removeImageMap()" + accesskey="&removeImageMapButton.accessKey;" + label="&removeImageMapButton.label;" + flex="1"/> + <spacer flex="1"/><!-- remove when we restore Image Map Editor --> + </hbox> + </groupbox> + </vbox> + </hbox> diff --git a/comm/suite/editor/components/dialogs/jar.mn b/comm/suite/editor/components/dialogs/jar.mn new file mode 100644 index 0000000000..705fe0068c --- /dev/null +++ b/comm/suite/editor/components/dialogs/jar.mn @@ -0,0 +1,82 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +comm.jar: + content/editor/EdAdvancedEdit.js (content/EdAdvancedEdit.js) + content/editor/EdAdvancedEdit.xhtml (content/EdAdvancedEdit.xhtml) + content/editor/EdAEAttributes.js (content/EdAEAttributes.js) + content/editor/EdAECSSAttributes.js (content/EdAECSSAttributes.js) + content/editor/EdAEHTMLAttributes.js (content/EdAEHTMLAttributes.js) + content/editor/EdAEJSEAttributes.js (content/EdAEJSEAttributes.js) + content/editor/EdButtonProps.js (content/EdButtonProps.js) + content/editor/EdButtonProps.xhtml (content/EdButtonProps.xhtml) + content/editor/EdColorPicker.js (content/EdColorPicker.js) + content/editor/EdColorPicker.xhtml (content/EdColorPicker.xhtml) + content/editor/EdColorProps.js (content/EdColorProps.js) + content/editor/EdColorProps.xhtml (content/EdColorProps.xhtml) + content/editor/EdConvertToTable.js (content/EdConvertToTable.js) + content/editor/EdConvertToTable.xhtml (content/EdConvertToTable.xhtml) + content/editor/EdDialogCommon.js (content/EdDialogCommon.js) + content/editor/EdDialogTemplate.js (content/EdDialogTemplate.js) + content/editor/EdDialogTemplate.xhtml (content/EdDialogTemplate.xhtml) + content/editor/EdDictionary.js (content/EdDictionary.js) + content/editor/EdDictionary.xhtml (content/EdDictionary.xhtml) + content/editor/EdFieldSetProps.js (content/EdFieldSetProps.js) + content/editor/EdFieldSetProps.xhtml (content/EdFieldSetProps.xhtml) + content/editor/EdFormProps.js (content/EdFormProps.js) + content/editor/EdFormProps.xhtml (content/EdFormProps.xhtml) + content/editor/EdHLineProps.js (content/EdHLineProps.js) + content/editor/EdHLineProps.xhtml (content/EdHLineProps.xhtml) + content/editor/edImage.inc.xhtml (content/edImage.inc.xhtml) + content/editor/EdImageDialog.js (content/EdImageDialog.js) + content/editor/EdImageLinkLoader.js (content/EdImageLinkLoader.js) + content/editor/EdImageProps.js (content/EdImageProps.js) +* content/editor/EdImageProps.xhtml (content/EdImageProps.xhtml) + content/editor/EdInputImage.js (content/EdInputImage.js) +* content/editor/EdInputImage.xhtml (content/EdInputImage.xhtml) + content/editor/EdInputProps.js (content/EdInputProps.js) + content/editor/EdInputProps.xhtml (content/EdInputProps.xhtml) + content/editor/EdInsertChars.js (content/EdInsertChars.js) + content/editor/EdInsertChars.xhtml (content/EdInsertChars.xhtml) + content/editor/EdInsertMath.js (content/EdInsertMath.js) + content/editor/EdInsertMath.xhtml (content/EdInsertMath.xhtml) + content/editor/EdInsertTable.js (content/EdInsertTable.js) + content/editor/EdInsertTable.xhtml (content/EdInsertTable.xhtml) + content/editor/EdInsertTOC.js (content/EdInsertTOC.js) + content/editor/EdInsertTOC.xhtml (content/EdInsertTOC.xhtml) + content/editor/EdInsSrc.js (content/EdInsSrc.js) + content/editor/EdInsSrc.xhtml (content/EdInsSrc.xhtml) + content/editor/EditConflict.js (content/EditConflict.js) + content/editor/EditConflict.xhtml (content/EditConflict.xhtml) + content/editor/EditorPublish.js (content/EditorPublish.js) + content/editor/EditorPublish.xhtml (content/EditorPublish.xhtml) + content/editor/EditorPublishOverlay.xhtml (content/EditorPublishOverlay.xhtml) + content/editor/EditorPublishProgress.js (content/EditorPublishProgress.js) + content/editor/EditorPublishProgress.xhtml (content/EditorPublishProgress.xhtml) + content/editor/EditorPublishSettings.js (content/EditorPublishSettings.js) + content/editor/EditorPublishSettings.xhtml (content/EditorPublishSettings.xhtml) + content/editor/EditorSaveAsCharset.js (content/EditorSaveAsCharset.js) + content/editor/EditorSaveAsCharset.xhtml (content/EditorSaveAsCharset.xhtml) + content/editor/EdLabelProps.js (content/EdLabelProps.js) + content/editor/EdLabelProps.xhtml (content/EdLabelProps.xhtml) + content/editor/EdLinkProps.js (content/EdLinkProps.js) + content/editor/EdLinkProps.xhtml (content/EdLinkProps.xhtml) + content/editor/EdListProps.js (content/EdListProps.js) + content/editor/EdListProps.xhtml (content/EdListProps.xhtml) + content/editor/EdNamedAnchorProps.js (content/EdNamedAnchorProps.js) + content/editor/EdNamedAnchorProps.xhtml (content/EdNamedAnchorProps.xhtml) + content/editor/EdPageProps.js (content/EdPageProps.js) + content/editor/EdPageProps.xhtml (content/EdPageProps.xhtml) + content/editor/EdReplace.js (content/EdReplace.js) + content/editor/EdReplace.xhtml (content/EdReplace.xhtml) + content/editor/EdSelectProps.js (content/EdSelectProps.js) + content/editor/EdSelectProps.xhtml (content/EdSelectProps.xhtml) + content/editor/EdSnapToGrid.js (content/EdSnapToGrid.js) + content/editor/EdSnapToGrid.xhtml (content/EdSnapToGrid.xhtml) + content/editor/EdSpellCheck.js (content/EdSpellCheck.js) + content/editor/EdSpellCheck.xhtml (content/EdSpellCheck.xhtml) + content/editor/EdTableProps.js (content/EdTableProps.js) + content/editor/EdTableProps.xhtml (content/EdTableProps.xhtml) + content/editor/EdTextAreaProps.js (content/EdTextAreaProps.js) + content/editor/EdTextAreaProps.xhtml (content/EdTextAreaProps.xhtml) diff --git a/comm/suite/editor/components/dialogs/moz.build b/comm/suite/editor/components/dialogs/moz.build new file mode 100644 index 0000000000..de5cd1bf81 --- /dev/null +++ b/comm/suite/editor/components/dialogs/moz.build @@ -0,0 +1,6 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +JAR_MANIFESTS += ["jar.mn"] diff --git a/comm/suite/editor/components/moz.build b/comm/suite/editor/components/moz.build new file mode 100644 index 0000000000..cc5a5d1960 --- /dev/null +++ b/comm/suite/editor/components/moz.build @@ -0,0 +1,10 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += [ + "dialogs", + "prefs", + "texzilla", +] diff --git a/comm/suite/editor/components/prefs/content/editorPrefsOverlay.xhtml b/comm/suite/editor/components/prefs/content/editorPrefsOverlay.xhtml new file mode 100644 index 0000000000..41112036ca --- /dev/null +++ b/comm/suite/editor/components/prefs/content/editorPrefsOverlay.xhtml @@ -0,0 +1,50 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<!DOCTYPE overlay [ +<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > +%brandDTD; +<!ENTITY % editorPrefsOverlayDTD SYSTEM "chrome://editor/locale/editorPrefsOverlay.dtd" > +%editorPrefsOverlayDTD; +]> + +<overlay id="editorPrefsOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"> + + <preferences id="appearance_preferences"> + <preference id="general.startup.editor" + name="general.startup.editor" + type="bool"/> + </preferences> + + <!-- editor startup toggle --> + <groupbox id="generalStartupPreferences"> + <checkbox id="generalStartupEditor" + insertafter="generalStartupMail,generalStartupBrowser" + label="&editorCheck.label;" + accesskey="&editorCheck.accesskey;" + preference="general.startup.editor"/> + </groupbox> + <!-- category tree entries for editor --> + <treechildren id="prefsPanelChildren"> + <treeitem container="true" + id="composerItem" + insertbefore="securityItem" + label="&compose.label;" + prefpane="composer_pane" + url="chrome://editor/content/pref-composer.xhtml" + helpTopic="composer_prefs_general"> + <treechildren id="composerChildren"> + <treeitem id="editingItem" + label="&editing.label;" + prefpane="editing_pane" + url="chrome://editor/content/pref-editing.xhtml" + helpTopic="composer_prefs_newpage"/> + </treechildren> + </treeitem> + </treechildren> + +</overlay> diff --git a/comm/suite/editor/components/prefs/content/pref-composer.xhtml b/comm/suite/editor/components/prefs/content/pref-composer.xhtml new file mode 100644 index 0000000000..79b4a1514c --- /dev/null +++ b/comm/suite/editor/components/prefs/content/pref-composer.xhtml @@ -0,0 +1,84 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<!DOCTYPE overlay SYSTEM "chrome://editor/locale/pref-composer.dtd"> + +<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"> + <prefpane id="composer_pane" label="&pref.composer.title;"> + + <preferences id="composer_preferences"> + <preference id="editor.history.url_maximum" + name="editor.history.url_maximum" + type="int"/> + <preference id="editor.prettyprint" + name="editor.prettyprint" + type="bool" + inverted="true"/> + <preference id="editor.save_associated_files" + name="editor.save_associated_files" + type="bool"/> + <preference id="editor.always_show_publish_dialog" + name="editor.always_show_publish_dialog" + type="bool"/> + <preference id="editor.table.maintain_structure" + name="editor.table.maintain_structure" + type="bool"/> + <preference id="editor.use_css" + name="editor.use_css" + type="bool"/> + <preference id="editor.CR_creates_new_p" + name="editor.CR_creates_new_p" + type="bool"/> + </preferences> + + <!-- Recent files menu --> + <groupbox> + <label class="header">&recentFiles.title;</label> + <hbox align="center"> + <label value="&documentsInMenu.label;" + accesskey="&documentsInMenu.accesskey;" + control="recentFiles"/> + <html:input id="recentFiles" type="number" class="size3" + min="0" max="99" value="10" + preference="editor.history.url_maximum"/> + </hbox> + </groupbox> + + <!-- HTML formatting on output --> + <groupbox> + <label class="header">&savingFiles.title;</label> + <checkbox id="preserveFormatting" + label="&preserveExisting.label;" + accesskey="&preserveExisting.accesskey;" + tooltiptext="&preserveExisting.tooltip;" + preference="editor.prettyprint"/> + <checkbox id="saveAssociatedFiles" + label="&saveAssociatedFiles.label;" + accesskey="&saveAssociatedFiles.accesskey;" + preference="editor.save_associated_files"/> + <checkbox id="showPublishDialog" + label="&showPublishDialog.label;" + accesskey="&showPublishDialog.accesskey;" + preference="editor.always_show_publish_dialog"/> + </groupbox> + + <groupbox align="start"> + <label class="header">&composerEditing.label;</label> + <checkbox id="maintainTableStructure" + label="&maintainStructure.label;" + accesskey="&maintainStructure.accesskey;" + tooltiptext="&maintainStructure.tooltip;" + preference="editor.table.maintain_structure"/> + <checkbox id="useCSS" + label="&useCSS.label;" + accesskey="&useCSS.accesskey;" + preference="editor.use_css"/> + <checkbox id="crInPCreatesNewP" + label="&crInPCreatesNewP.label;" + accesskey="&crInPCreatesNewP.accesskey;" + preference="editor.CR_creates_new_p"/> + </groupbox> + </prefpane> +</overlay> diff --git a/comm/suite/editor/components/prefs/content/pref-editing.js b/comm/suite/editor/components/prefs/content/pref-editing.js new file mode 100644 index 0000000000..61f4c1edef --- /dev/null +++ b/comm/suite/editor/components/prefs/content/pref-editing.js @@ -0,0 +1,187 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const browserPrefsObserver = +{ + observe: function(aSubject, aTopic, aData) + { + if (aTopic != "nsPref:changed" || document.getElementById("editor.use_custom_colors").value) + return; + + switch (aData) + { + case "browser.anchor_color": + SetColorPreview("linkText", Services.prefs.getCharPref(aData)); + break; + case "browser.active_color": + SetColorPreview("activeLinkText", Services.prefs.getCharPref(aData)); + break; + case "browser.visited_color": + SetColorPreview("visitedLinkText", Services.prefs.getCharPref(aData)); + break; + default: + SetBgAndFgColors(Services.prefs.getBoolPref("browser.display.use_system_colors")) + } + } +}; + +function Startup() +{ + // Add browser prefs observers + Services.prefs.addObserver("browser.display.use_system_colors", browserPrefsObserver); + Services.prefs.addObserver("browser.display.foreground_color", browserPrefsObserver); + Services.prefs.addObserver("browser.display.background_color", browserPrefsObserver); + Services.prefs.addObserver("browser.anchor_color", browserPrefsObserver); + Services.prefs.addObserver("browser.active_color", browserPrefsObserver); + Services.prefs.addObserver("browser.visited_color", browserPrefsObserver); + + // Add event listener so we can remove our observers + window.addEventListener("unload", WindowOnUnload, {capture: false, once: true}); + UpdateDependent(document.getElementById("editor.use_custom_colors").value); +} + +function GetColorAndUpdatePref(aType, aButtonID) +{ + // Don't allow a blank color, i.e., using the "default" + var colorObj = { NoDefault:true, Type:"", TextColor:0, PageColor:0, Cancel:false }; + var preference = document.getElementById("editor." + aButtonID + "_color"); + + if (aButtonID == "background") + colorObj.PageColor = preference.value; + else + colorObj.TextColor = preference.value; + + colorObj.Type = aType; + + window.openDialog("chrome://editor/content/EdColorPicker.xhtml", "_blank", "chrome,close,titlebar,modal", "", colorObj); + + // User canceled the dialog + if (colorObj.Cancel) + return; + + // Update preference with picked color + if (aType == "Page") + preference.value = colorObj.BackgroundColor; + else + preference.value = colorObj.TextColor; +} + +function UpdateDependent(aCustomEnabled) +{ + ToggleElements(aCustomEnabled); + + if (aCustomEnabled) + { // Set current editor colors on preview and buttons + SetColors("textCW", "normalText", document.getElementById("editor.text_color").value); + SetColors("linkCW", "linkText", document.getElementById("editor.link_color").value); + SetColors("activeCW", "activeLinkText", document.getElementById("editor.active_link_color").value); + SetColors("visitedCW", "visitedLinkText", document.getElementById("editor.followed_link_color").value); + SetColors("backgroundCW", "ColorPreview", document.getElementById("editor.background_color").value); + } + else + { // Set current browser colors on preview + SetBgAndFgColors(Services.prefs.getBoolPref("browser.display.use_system_colors")); + SetColorPreview("linkText", Services.prefs.getCharPref("browser.anchor_color")); + SetColorPreview("activeLinkText", Services.prefs.getCharPref("browser.active_color")); + SetColorPreview("visitedLinkText", Services.prefs.getCharPref("browser.visited_color")); + } +} + +function ToggleElements(aCustomEnabled) +{ + var buttons = document.getElementById("color-rows").getElementsByTagName("button"); + + for (var i = 0; i < buttons.length; i++) + { + let isLocked = CheckLocked(buttons[i].id); + buttons[i].disabled = !aCustomEnabled || isLocked; + buttons[i].previousSibling.disabled = !aCustomEnabled || isLocked; + buttons[i].firstChild.setAttribute("default", !aCustomEnabled || isLocked); + } +} + +function CheckLocked(aButtonID) +{ + return document.getElementById("editor." + aButtonID + "_color").locked; +} + +// Updates preview and button color when a editor color pref change +function UpdateColors(aColorWellID, aPreviewID, aColor) +{ + // Only show editor colors from prefs if we're in custom mode + if (!document.getElementById("editor.use_custom_colors").value) + return; + + SetColors(aColorWellID, aPreviewID, aColor) +} + +function SetColors(aColorWellID, aPreviewID, aColor) +{ + SetColorWell(aColorWellID, aColor); + SetColorPreview(aPreviewID, aColor); +} + +function SetColorWell(aColorWellID, aColor) +{ + document.getElementById(aColorWellID).style.backgroundColor = aColor; +} + +function SetColorPreview(aPreviewID, aColor) +{ + if (aPreviewID == "ColorPreview") + document.getElementById(aPreviewID).style.backgroundColor = aColor; + else + document.getElementById(aPreviewID).style.color = aColor; +} + +function UpdateBgImagePreview(aImage) +{ + var colorPreview = document.getElementById("ColorPreview"); + colorPreview.style.backgroundImage = aImage && "url(" + aImage + ")"; +} + +// Sets browser background/foreground colors +function SetBgAndFgColors(aSysPrefEnabled) +{ + if (aSysPrefEnabled) + { // Use system colors + SetColorPreview("normalText", "windowtext"); + SetColorPreview("ColorPreview", "window"); + } + else + { + SetColorPreview("normalText", Services.prefs.getCharPref("browser.display.foreground_color")); + SetColorPreview("ColorPreview", Services.prefs.getCharPref("browser.display.background_color")); + } +} + +function ChooseImageFile() +{ + const nsIFilePicker = Ci.nsIFilePicker; + var fp = Cc["@mozilla.org/filepicker;1"] + .createInstance(nsIFilePicker); + var editorBundle = document.getElementById("bundle_editor"); + var title = editorBundle.getString("SelectImageFile"); + fp.init(window, title, nsIFilePicker.modeOpen); + fp.appendFilters(nsIFilePicker.filterImages); + fp.open(rv => { + if (rv != nsIFilePicker.returnOK || !fp.file) { + return; + } + document.getElementById("editor.default_background_image").value = fp.fileURL.spec; + let textbox = document.getElementById("backgroundImageInput"); + textbox.focus(); + textbox.select(); + }); +} + +function WindowOnUnload() +{ + Services.prefs.removeObserver("browser.display.use_system_colors", browserPrefsObserver, false); + Services.prefs.removeObserver("browser.display.foreground_color", browserPrefsObserver, false); + Services.prefs.removeObserver("browser.display.background_color", browserPrefsObserver, false); + Services.prefs.removeObserver("browser.anchor_color", browserPrefsObserver, false); + Services.prefs.removeObserver("browser.active_color", browserPrefsObserver, false); + Services.prefs.removeObserver("browser.visited_color", browserPrefsObserver, false); +} diff --git a/comm/suite/editor/components/prefs/content/pref-editing.xhtml b/comm/suite/editor/components/prefs/content/pref-editing.xhtml new file mode 100644 index 0000000000..140ff320ce --- /dev/null +++ b/comm/suite/editor/components/prefs/content/pref-editing.xhtml @@ -0,0 +1,181 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?> + +<!DOCTYPE overlay SYSTEM "chrome://editor/locale/pref-editing.dtd"> + +<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"> + <prefpane id="editing_pane" + label="&pref.editing.title;" + script="chrome://editor/content/pref-editing.js"> + + <preferences id="editing_preferences"> + <preference id="editor.author" + name="editor.author" + type="string"/> + <preference id="editor.use_custom_colors" + name="editor.use_custom_colors" + type="bool" + onchange="UpdateDependent(this.value);"/> + <preference id="editor.text_color" + name="editor.text_color" + type="string" + onchange="UpdateColors('textCW', 'normalText', this.value);"/> + <preference id="editor.link_color" + name="editor.link_color" + type="string" + onchange="UpdateColors('linkCW', 'linkText', this.value);"/> + <preference id="editor.active_link_color" + name="editor.active_link_color" + type="string" + onchange="UpdateColors('activeCW', 'activeLinkText', this.value);"/> + <preference id="editor.followed_link_color" + name="editor.followed_link_color" + type="string" + onchange="UpdateColors('visitedCW', 'visitedLinkText', this.value);"/> + <preference id="editor.background_color" + name="editor.background_color" + type="string" + onchange="UpdateColors('backgroundCW', 'ColorPreview', this.value);"/> + <preference id="editor.default_background_image" + name="editor.default_background_image" + type="string" + onchange="UpdateBgImagePreview(this.value);"/> + </preferences> + + <stringbundle id="bundle_editor" + src="chrome://editor/locale/editor.properties"/> + + <vbox> + <label value="&authorName.label;" + accesskey="&authorName.accesskey;" + control="editorAuthor"> + </label> + <hbox> + <textbox id="editorAuthor" + flex="1" + preference="editor.author"/> + <spacer flex="1"/> + </hbox> + </vbox> + <spacer class="smallspacer"/> + <groupbox align="start"> + <label class="header">&pageColorHeader;</label> + <radiogroup id="useCustomColors" + preference="editor.use_custom_colors"> + <radio id="defaultColorsRadio" + value="false" + label="&defaultColors.label;" + accesskey="&defaultColors.accesskey;"/> + <radio id="customColorsRadio" + value="true" + label="&customColors.label;" + accesskey="&customColors.accesskey;"/> + </radiogroup> + <hbox class="indent"> + <grid> + <columns><column/><column/></columns> + <rows id="color-rows"> + <row align="center"> + <label id="textLabel" + value="&normalText.label;&colon.character;" + accesskey="&normalText.accesskey;" + control="text"/> + <button id="text" + class="color-button" + oncommand="GetColorAndUpdatePref('Text', 'text');"> + <spacer id="textCW" class="color-well"/> + </button> + </row> + <row align="center"> + <label id="linkLabel" + value="&linkText.label;&colon.character;" + accesskey="&linkText.accesskey;" + control="link"/> + <button id="link" + class="color-button" + oncommand="GetColorAndUpdatePref('Link', 'link');"> + <spacer id="linkCW" class="color-well"/> + </button> + </row> + <row align="center"> + <label id="activeLinkLabel" + value="&activeLinkText.label;&colon.character;" + accesskey="&activeLinkText.accesskey;" + control="active_link"/> + <button id="active_link" + class="color-button" + oncommand="GetColorAndUpdatePref('ActiveLink', 'active_link');"> + <spacer id="activeCW" class="color-well"/> + </button> + </row> + <row align="center"> + <label id="visitedLinkLabel" + value ="&visitedLinkText.label;&colon.character;" + accesskey="&visitedLinkText.accesskey;" + control="followed_link"/> + <button id="followed_link" + class="color-button" + oncommand="GetColorAndUpdatePref('VisitedLink', 'followed_link');"> + <spacer id="visitedCW" class="color-well"/> + </button> + </row> + <row align="center"> + <label id="backgroundLabel" + value="&background.label;" + accesskey="&background.accesskey;" + control="background"/> + <button id="background" + class="color-button" + oncommand="GetColorAndUpdatePref('Page', 'background');"> + <spacer id="backgroundCW" class="color-well"/> + </button> + </row> + </rows> + </grid> + <vbox id="ColorPreview" + flex="1"> + <spacer flex="1"/> + <label id="normalText" + class="larger" + value="&normalText.label;"/> + <spacer flex="1"/> + <label id="linkText" + class="larger" + value="&linkText.label;"/> + <spacer flex="1"/> + <label id="activeLinkText" + class="larger" + value="&activeLinkText.label;"/> + <spacer flex="1"/> + <label id="visitedLinkText" + class="larger" + value="&visitedLinkText.label;"/> + <spacer flex="1"/> + </vbox> + <spacer flex="1"/> + </hbox> + <spacer class="spacer"/> + <label id="backgroundImageLabel" + value="&backgroundImage.label;" + accesskey="&backgroundImage.accesskey;" + control="backgroundImageInput"> + </label> + <hbox align="center"> + <textbox id="backgroundImageInput" + class="uri-element" + preference="editor.default_background_image" + style="min-width: 23em;" + flex="1"/> + <button label="&chooseFile.label;" + accesskey="&chooseFile.accesskey;" + oncommand="ChooseImageFile();"> + <observes element="backgroundImageInput" attribute="disabled"/> + </button> + </hbox> + </groupbox> + </prefpane> +</overlay> diff --git a/comm/suite/editor/components/prefs/jar.mn b/comm/suite/editor/components/prefs/jar.mn new file mode 100644 index 0000000000..308da7340c --- /dev/null +++ b/comm/suite/editor/components/prefs/jar.mn @@ -0,0 +1,11 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +comm.jar: +% overlay chrome://communicator/content/pref/preferences.xhtml chrome://editor/content/editorPrefsOverlay.xhtml +% overlay chrome://communicator/content/pref/pref-appearance.xhtml chrome://editor/content/editorPrefsOverlay.xhtml + content/editor/editorPrefsOverlay.xhtml (content/editorPrefsOverlay.xhtml) + content/editor/pref-composer.xhtml (content/pref-composer.xhtml) + content/editor/pref-editing.js (content/pref-editing.js) + content/editor/pref-editing.xhtml (content/pref-editing.xhtml) diff --git a/comm/suite/editor/components/prefs/moz.build b/comm/suite/editor/components/prefs/moz.build new file mode 100644 index 0000000000..de5cd1bf81 --- /dev/null +++ b/comm/suite/editor/components/prefs/moz.build @@ -0,0 +1,6 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +JAR_MANIFESTS += ["jar.mn"] diff --git a/comm/suite/editor/components/texzilla/content/TeXZilla.js b/comm/suite/editor/components/texzilla/content/TeXZilla.js new file mode 100644 index 0000000000..0f8e3e5b29 --- /dev/null +++ b/comm/suite/editor/components/texzilla/content/TeXZilla.js @@ -0,0 +1,339 @@ +/* THIS IS A GENERATED FILE. DO NOT EDIT THIS DIRECTLY. */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +(function() { +"using strict"; +var nb=void 0,tb=!0,xb=null,yb=!1,zb=function(){function c(b,a,c){var $a;c=c||{};for($a=b.length;$a--;c[b[$a]]=a);return c}function Fb(b){return b.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}function sb(b){b="negativeveryverythinmathspace negativeverythinmathspace negativemediummathspace negativethickmathspace negativeverythickmathspace negativeveryverythickmathspace veryverythinmathspace verythinmathspace thinmathspace mediummathspace thickmathspace verythickmathspace veryverythickmathspace".split(" ").indexOf(b); +return(-1===b?0:b-6)/18}function tc(b){b=b.trim();var a=/(-?[0-9]*(?:[0-9]\.?|\.[0-9])[0-9]*)(e[mx]|in|cm|mm|p[xtc]|%)?/.exec(b);return a?(a[1]=parseFloat(a[1]),a[2]||(a[1]*=100,a[2]="%"),{i:a[1],k:a[2]}):{i:sb(b),k:"em"}}function Pb(b){var a="<"+b.tag,c;for(c in b.attributes)b.attributes[c]!==nb&&(a+=" "+c+'="'+b.attributes[c]+'"');b.content?(a+=">",Array.isArray(b.content)?b.content.forEach(function(b){a+=Pb(b)}):a+=b.content,a+="</"+b.tag+">"):a+="/>";return a}function e(b,a,c){return{tag:b,content:a, +attributes:c}}function ab(b,a,c){return e("mo",Fb(b),{lspace:a!==nb?a+"em":nb,rspace:c!==nb?c+"em":nb})}function Ub(b,a){return e("mi",Fb(b),a?{mathvariant:"normal"}:nb)}function Gb(b){return e("mspace",xb,{width:b+"em"})}function uc(b,a){var c="bold italic bold-italic script bold-script fraktur double-struck bold-fraktur sans-serif bold-sans-serif sans-serif-italic sans-serif-bold-italic monospace initial tailed looped stretched".split(" ").indexOf(a);if(930==b)return b;if(988==b)return 0==c?120778: +b;if(989==b)return 0==c?120779:b;if(305==b)return 1==c?120484:b;if(567==b)return 1==c?120485:b;var $a;if(65<=b&&90>=b||97<=b&&122>=b){if(12<c)return b;c=(90>=b?b-65:26+b-97)+119808+52*c;$a={119893:8462,119965:8492,119968:8496,119969:8497,119971:8459,119972:8464,119975:8466,119976:8499,119981:8475,119994:8495,119996:8458,120004:8500,120070:8493,120075:8460,120076:8465,120085:8476,120093:8488,120122:8450,120127:8461,120133:8469,120135:8473,120136:8474,120137:8477,120145:8484};return $a[c]?$a[c]:c}if(48<= +b&&57>=b){switch(c){case 0:c=0;break;case 6:c=1;break;case 8:c=2;break;case 9:c=3;break;case 12:c=4;break;default:return b}return b-48+10*c+120782}if(1536<=b&&1791>=b){switch(c){case 13:$a={1576:126497,1578:126517,1579:126518,1580:126498,1581:126503,1582:126519,1587:126510,1588:126516,1589:126513,1590:126521,1593:126511,1594:126523,1601:126512,1602:126514,1603:126506,1604:126507,1605:126508,1606:126509,1607:126500,1610:126505};break;case 14:$a={1580:126530,1581:126535,1582:126551,1587:126542,1588:126548, +1589:126545,1590:126553,1593:126543,1594:126555,1602:126546,1604:126539,1606:126541,1610:126537,1647:126559,1722:126557};break;case 16:$a={1576:126561,1578:126581,1579:126582,1580:126562,1581:126567,1582:126583,1587:126574,1588:126580,1589:126577,1590:126585,1591:126568,1592:126586,1593:126575,1594:126587,1601:126576,1602:126578,1603:126570,1605:126572,1606:126573,1607:126564,1610:126569,1646:126588,1697:126590};break;case 15:$a={1575:126592,1576:126593,1578:126613,1579:126614,1580:126594,1581:126599, +1582:126615,1583:126595,1584:126616,1585:126611,1586:126598,1587:126606,1588:126612,1589:126609,1590:126617,1591:126600,1592:126618,1593:126607,1594:126619,1601:126608,1602:126610,1604:126603,1605:126604,1606:126605,1607:126596,1608:126597,1610:126601};break;case 6:$a={1576:126625,1578:126645,1579:126646,1580:126626,1581:126631,1582:126647,1583:126627,1584:126648,1585:126643,1586:126630,1587:126638,1588:126644,1589:126641,1590:126649,1591:126632,1592:126650,1593:126639,1594:126651,1601:126640,1602:126642, +1604:126635,1605:126636,1606:126637,1608:126629,1610:126633};break;default:return b}return $a[b]?$a[b]:b}if(913<=b&&937>=b)$a=b-913;else if(945<=b&&969>=b)$a=26+b-945;else switch(b){case 1012:$a=17;break;case 8711:$a=25;break;case 8706:$a=51;break;case 1013:$a=52;break;case 977:$a=53;break;case 1008:$a=54;break;case 981:$a=55;break;case 1009:$a=56;break;case 982:$a=57;break;default:return b}switch(c){case 0:c=0;break;case 1:c=1;break;case 2:c=2;break;case 9:c=3;break;case 11:c=4;break;default:return b}return $a+ +120488+58*c}function vc(b,a){var c=tb,$a;for($a in a)-1!==["mathcolor","mathbackground","mathvariant"].indexOf($a)?"mathvariant"!==$a&&1!=b.length?c=yb:b.forEach(function(b){if(-1!==["mi","mn","mo","mtext","ms"].indexOf(b.tag)){if(b.attributes||(b.attributes={}),!b.attributes[$a])if("mathvariant"===$a){var d;if(!(d="normal"!==a[$a])){if("mi"!==b.tag)d=yb;else{d=b.content;var e=d.codePointAt(0);d=1===d.length&&65535>=e||2===d.length&&65535<e}d=!d}if(d){if(d=a[$a],"normal"!==d){for(var e=b.content, +g="",m=0;m<e.length;m++){var s=e.codePointAt(m);65535<s?(g+=e[m],m++,g+=e[m]):g+=String.fromCodePoint(uc(s,d))}b.content=g}}else b.attributes[$a]=a[$a]}else b.attributes[$a]=a[$a]}else c=yb}):c=yb;return c}function db(b,a,c){a=a||"mrow";if("mstyle"===a){if(1==b.length&&"mrow"===b[0].tag&&!b[0].attributes)return db(b[0].content,a,c);if(vc(b,c))return db(b)}return 1==b.length&&"mrow"===a&&!c?b[0]:e(a,b,c)}function Ib(b,a,c,$a){return e("math",[e("semantics",[db(b),e("annotation",Fb($a),{encoding:"TeX"})])], +{xmlns:Qb,display:a?"block":nb,dir:c?"rtl":nb})}function Vb(b){if(!b||b.namespaceURI!==Qb)return xb;if("semantics"===b.tagName)for(b=b.firstElementChild;b;b=b.nextElementSibling){if(b.namespaceURI===Qb&&"annotation"===b.localName&&-1!==wc.indexOf(b.getAttribute("encoding")))return b.textContent}else if(1===b.childElementCount)return Vb(b.firstElementChild);return xb}function xc(b){for(var a="",c,$a,e=0;e<b.length;e++)c=b.charCodeAt(e),128>c?a+=b.charAt(e):55296<=c&&56319>=c?(e++,$a=b.charCodeAt(e), +a+="&#x"+(1024*(c-55296)+$a-56320+65536).toString(16)+";"):a+="&#x"+c.toString(16)+";";return a}function Rb(){this.e={}}var Wb=[1,4],Xb=[1,6],Yb=[1,7],Zb=[1,8],$b=[1,9],Ab=[68,195,198,200,202,204],m=[1,27],s=[1,124],v=[1,52],x=[1,48],h=[1,28],q=[1,29],p=[1,30],y=[1,31],f=[1,32],u=[1,33],n=[1,34],k=[1,35],r=[1,37],t=[1,38],l=[1,39],w=[1,40],z=[1,41],A=[1,42],B=[1,43],C=[1,44],D=[1,45],E=[1,46],F=[1,47],G=[1,49],H=[1,50],I=[1,51],J=[1,53],K=[1,54],L=[1,55],M=[1,56],N=[1,57],O=[1,58],P=[1,59],Q=[1,60], +R=[1,61],S=[1,62],T=[1,63],U=[1,64],V=[1,65],W=[1,66],X=[1,67],Y=[1,68],Z=[1,69],$=[1,70],aa=[1,71],ba=[1,72],ca=[1,73],da=[1,74],ea=[1,75],fa=[1,76],ga=[1,77],ha=[1,78],ia=[1,79],ja=[1,80],ka=[1,81],la=[1,82],ma=[1,83],na=[1,84],oa=[1,85],pa=[1,86],qa=[1,87],ra=[1,88],sa=[1,89],ta=[1,90],ua=[1,91],va=[1,92],wa=[1,93],xa=[1,94],ya=[1,95],za=[1,96],Aa=[1,97],Ba=[1,98],Ca=[1,99],Da=[1,100],Ea=[1,101],Fa=[1,102],Ga=[1,103],Ha=[1,104],Ia=[1,105],Ja=[1,106],Ka=[1,107],eb=[1,24],La=[1,108],Ma=[1,109],Na= +[1,110],Oa=[1,111],Pa=[1,112],Qa=[1,113],Ra=[1,114],Sa=[1,115],Ta=[1,116],Ua=[1,117],Va=[1,118],Wa=[1,119],Xa=[1,120],Ya=[1,121],bb=[1,122],cb=[1,123],fb=[1,16],gb=[1,17],hb=[1,18],ib=[1,19],jb=[1,20],kb=[1,21],lb=[1,22],ac=[6,10,53,64,65,66,144,146,148,150,152,154,156,158,160,162,164,189,192,199,201,203,205],Db=[8,49,50,51,56,57,58,59,60,61,62,63,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113, +114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,139,141,142,145,147,149,151,153,155,157,159,161,163,165,166,173,174,179,180,181,182,183,184,185],mb=[1,134],ub=[6,8,10,49,50,51,53,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,139, +141,142,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,173,174,189,192,199,201,203,205],Za=[1,137],g=[6,8,10,49,50,51,53,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,138,139,141,142,144,145,146,147,148,149,150,151,152,153,154, +155,156,157,158,159,160,161,162,163,164,165,166,169,170,171,173,174,189,192,199,201,203,205],Sb=[1,161],pb=[2,197],qb=[1,217],vb=[1,214],Eb=[6,8,10,49,50,51,53,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,139,141,142,144,145,146,147,148,149,150,151,152,153,154,155,156, +157,158,159,160,161,162,163,164,165,166,169,170,173,174,189,192,199,201,203,205],bc=[1,241],Bb=[1,243],Cb=[1,244],Mb=[1,259],cc=[4,8],dc=[1,275],ec=[8,49,50,51,56,57,58,59,60,61,62,63,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,138,139,141,142,145,147,149,151,153,155,157,159,161,163,165,166],wb=[1,286], +Nb=[10,144,146,148,150,152,154,156,158,160,162,164,192],fc=[1,288],Jb=[10,144,146,148,150,152,154,156,158,160,162,164,189,192],gc=[164,189,192],Tb=[10,189,192],Kb=[1,343],Lb=[1,344],hc=[1,352],ic=[1,353],jc=[4,8,49,50,51,56,57,58,59,60,61,62,63,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,139,141,142, +145,147,149,151,153,155,157,159,161,163,165,166],Ob=[10,21,23],Hb=[10,21,23,25,27],kc=[1,399],lc=[1,400],mc=[1,401],nc=[1,402],oc=[1,403],pc=[1,404],qc=[1,405],rc=[1,406],sc=[10,19,21,23,25,27,29,31,33,35,37,39,41],ob=[10,19,21,23,29,31,33,35,37,39,41],rb={trace:function(){},e:{},la:{error:2,textOptArg:3,"[":4,TEXTOPTARG:5,"]":6,textArg:7,"{":8,TEXTARG:9,"}":10,lengthOptArg:11,lengthArg:12,attrOptArg:13,attrArg:14,tokenContent:15,arrayAlign:16,columnAlign:17,collayout:18,COLLAYOUT:19,colalign:20, +COLALIGN:21,rowalign:22,ROWALIGN:23,rowspan:24,ROWSPAN:25,colspan:26,COLSPAN:27,align:28,ALIGN:29,eqrows:30,EQROWS:31,eqcols:32,EQCOLS:33,rowlines:34,ROWLINES:35,collines:36,COLLINES:37,frame:38,FRAME:39,padding:40,PADDING:41,cellopt:42,celloptList:43,rowopt:44,arrayopt:45,arrayoptList:46,rowoptList:47,left:48,LEFT:49,OPFS:50,".":51,right:52,RIGHT:53,closedTerm:54,styledExpression:55,BIG:56,BBIG:57,BIGG:58,BBIGG:59,BIGL:60,BBIGL:61,BIGGL:62,BBIGGL:63,TEXATOP:64,TEXOVER:65,TEXCHOOSE:66,NUM:67,TEXT:68, +A:69,AILL:70,AIUL:71,AILG:72,AIUG:73,F:74,MI:75,MN:76,MO:77,OP:78,OPS:79,OPAS:80,MS:81,MTEXT:82,HIGH_SURROGATE:83,LOW_SURROGATE:84,BMP_CHARACTER:85,OPERATORNAME:86,MATHOP:87,MATHBIN:88,MATHREL:89,FRAC:90,ROOT:91,SQRT:92,UNDERSET:93,OVERSET:94,UNDEROVERSET:95,XARROW:96,MATHRLAP:97,MATHLLAP:98,MATHCLAP:99,PHANTOM:100,TFRAC:101,BINOM:102,TBINOM:103,PMOD:104,UNDERBRACE:105,UNDERLINE:106,OVERBRACE:107,ACCENT:108,ACCENTNS:109,BOXED:110,SLASH:111,QUAD:112,QQUAD:113,NEGSPACE:114,NEGMEDSPACE:115,NEGTHICKSPACE:116, +THINSPACE:117,MEDSPACE:118,THICKSPACE:119,SPACE:120,MATHRAISEBOX:121,MATHBB:122,MATHBF:123,MATHBIT:124,MATHSCR:125,MATHBSCR:126,MATHSF:127,MATHFRAK:128,MATHIT:129,MATHTT:130,MATHRM:131,HREF:132,STATUSLINE:133,TOOLTIP:134,TOGGLE:135,BTOGGLE:136,closedTermList:137,ETOGGLE:138,TENSOR:139,subsupList:140,MULTI:141,BMATRIX:142,tableRowList:143,EMATRIX:144,BGATHERED:145,EGATHERED:146,BPMATRIX:147,EPMATRIX:148,BBMATRIX:149,EBMATRIX:150,BVMATRIX:151,EVMATRIX:152,BBBMATRIX:153,EBBMATRIX:154,BVVMATRIX:155,EVVMATRIX:156, +BSMALLMATRIX:157,ESMALLMATRIX:158,BCASES:159,ECASES:160,BALIGNED:161,EALIGNED:162,BARRAY:163,EARRAY:164,SUBSTACK:165,ARRAY:166,ARRAYOPTS:167,compoundTerm:168,_:169,"^":170,OPP:171,opm:172,OPM:173,FM:174,compoundTermList:175,subsupTermScript:176,subsupTerm:177,textstyle:178,DISPLAYSTYLE:179,TEXTSTYLE:180,TEXTSIZE:181,SCRIPTSIZE:182,SCRIPTSCRIPTSIZE:183,COLOR:184,BGCOLOR:185,tableCell:186,CELLOPTS:187,tableCellList:188,COLSEP:189,tableRow:190,ROWOPTS:191,ROWSEP:192,document:193,documentItemList:194, +EOF:195,documentItem:196,mathItem:197,STARTMATH0:198,ENDMATH0:199,STARTMATH1:200,ENDMATH1:201,STARTMATH2:202,ENDMATH2:203,STARTMATH3:204,ENDMATH3:205,$accept:0,$end:1},z:{2:"error",4:"[",5:"TEXTOPTARG",6:"]",8:"{",9:"TEXTARG",10:"}",19:"COLLAYOUT",21:"COLALIGN",23:"ROWALIGN",25:"ROWSPAN",27:"COLSPAN",29:"ALIGN",31:"EQROWS",33:"EQCOLS",35:"ROWLINES",37:"COLLINES",39:"FRAME",41:"PADDING",49:"LEFT",50:"OPFS",51:".",53:"RIGHT",56:"BIG",57:"BBIG",58:"BIGG",59:"BBIGG",60:"BIGL",61:"BBIGL",62:"BIGGL",63:"BBIGGL", +64:"TEXATOP",65:"TEXOVER",66:"TEXCHOOSE",67:"NUM",68:"TEXT",69:"A",70:"AILL",71:"AIUL",72:"AILG",73:"AIUG",74:"F",75:"MI",76:"MN",77:"MO",78:"OP",79:"OPS",80:"OPAS",81:"MS",82:"MTEXT",83:"HIGH_SURROGATE",84:"LOW_SURROGATE",85:"BMP_CHARACTER",86:"OPERATORNAME",87:"MATHOP",88:"MATHBIN",89:"MATHREL",90:"FRAC",91:"ROOT",92:"SQRT",93:"UNDERSET",94:"OVERSET",95:"UNDEROVERSET",96:"XARROW",97:"MATHRLAP",98:"MATHLLAP",99:"MATHCLAP",100:"PHANTOM",101:"TFRAC",102:"BINOM",103:"TBINOM",104:"PMOD",105:"UNDERBRACE", +106:"UNDERLINE",107:"OVERBRACE",108:"ACCENT",109:"ACCENTNS",110:"BOXED",111:"SLASH",112:"QUAD",113:"QQUAD",114:"NEGSPACE",115:"NEGMEDSPACE",116:"NEGTHICKSPACE",117:"THINSPACE",118:"MEDSPACE",119:"THICKSPACE",120:"SPACE",121:"MATHRAISEBOX",122:"MATHBB",123:"MATHBF",124:"MATHBIT",125:"MATHSCR",126:"MATHBSCR",127:"MATHSF",128:"MATHFRAK",129:"MATHIT",130:"MATHTT",131:"MATHRM",132:"HREF",133:"STATUSLINE",134:"TOOLTIP",135:"TOGGLE",136:"BTOGGLE",138:"ETOGGLE",139:"TENSOR",141:"MULTI",142:"BMATRIX",144:"EMATRIX", +145:"BGATHERED",146:"EGATHERED",147:"BPMATRIX",148:"EPMATRIX",149:"BBMATRIX",150:"EBMATRIX",151:"BVMATRIX",152:"EVMATRIX",153:"BBBMATRIX",154:"EBBMATRIX",155:"BVVMATRIX",156:"EVVMATRIX",157:"BSMALLMATRIX",158:"ESMALLMATRIX",159:"BCASES",160:"ECASES",161:"BALIGNED",162:"EALIGNED",163:"BARRAY",164:"EARRAY",165:"SUBSTACK",166:"ARRAY",167:"ARRAYOPTS",169:"_",170:"^",171:"OPP",173:"OPM",174:"FM",179:"DISPLAYSTYLE",180:"TEXTSTYLE",181:"TEXTSIZE",182:"SCRIPTSIZE",183:"SCRIPTSCRIPTSIZE",184:"COLOR",185:"BGCOLOR", +187:"CELLOPTS",189:"COLSEP",191:"ROWOPTS",192:"ROWSEP",195:"EOF",198:"STARTMATH0",199:"ENDMATH0",200:"STARTMATH1",201:"ENDMATH1",202:"STARTMATH2",203:"ENDMATH2",204:"STARTMATH3",205:"ENDMATH3"},W:[0,[3,3],[7,3],[11,3],[12,3],[13,1],[14,1],[15,1],[16,1],[17,1],[18,2],[20,2],[22,2],[24,2],[26,2],[28,2],[30,2],[32,2],[34,2],[36,2],[38,2],[40,2],[42,1],[42,1],[42,1],[42,1],[43,1],[43,2],[44,1],[44,1],[45,1],[45,1],[45,1],[45,1],[45,1],[45,1],[45,1],[45,1],[45,1],[45,1],[46,1],[46,2],[47,1],[47,2],[48, +2],[48,2],[52,2],[52,2],[54,2],[54,3],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,3],[54,5],[54,5],[54,5],[54,5],[54,5],[54,5],[54,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,2],[54,2],[54,2],[54,1],[54,1],[54,1],[54,1],[54,1],[54,2],[54,4],[54,2],[54,2],[54,1],[54,2],[54,2],[54,2],[54,2],[54,3],[54,3],[54,2],[54,5],[54,3],[54,3],[54,4],[54,5],[54,2],[54,2],[54,2],[54,2],[54,2],[54,3],[54,3],[54,3],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,1],[54,1],[54, +1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,4],[54,5],[54,4],[54,3],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,3],[54,3],[54,3],[54,3],[54,3],[54,5],[54,8],[54,7],[54,7],[54,3],[54,3],[54,3],[54,3],[54,3],[54,3],[54,3],[54,3],[54,3],[54,3],[54,5],[54,4],[54,4],[54,4],[54,8],[137,1],[137,2],[168,3],[168,5],[168,4],[168,5],[168,4],[168,3],[168,3],[168,2],[168,1],[168,5],[168,5],[168,3],[168,3],[168,1],[172,1],[172,1],[175,1],[175,2],[176,1],[176,1],[177,4],[177,2],[177, +2],[177,3],[140,1],[140,2],[178,1],[178,1],[178,1],[178,1],[178,1],[178,2],[178,2],[55,2],[55,1],[186,0],[186,5],[186,1],[188,1],[188,3],[190,5],[190,1],[143,1],[143,3],[193,2],[194,1],[194,2],[196,1],[196,1],[197,2],[197,3],[197,2],[197,3],[197,3],[197,3]],H:function(b,a,c,$a,g,d){b=d.length-1;switch(g){case 1:this.b=d[b-1].replace(/\\[\\\]]/g,function(a){return a.slice(1)});this.b=Fb(this.b);break;case 2:this.b=d[b-1].replace(/\\[\\\}]/g,function(a){return a.slice(1)});this.b=Fb(this.b);break;case 3:case 4:this.b= +tc(d[b-1]);break;case 5:case 6:this.b=d[b].replace(/"/g,""");break;case 7:this.b=d[b].replace(/\s+/g," ").replace(/^ | $/g," ");break;case 8:d[b]=d[b].trim();if("t"===d[b])this.b="axis 1";else if("c"===d[b])this.b="center";else if("b"===d[b])this.b="axis -1";else throw"Unknown array alignment";break;case 9:this.b="";d[b]=d[b].replace(/\s+/g,"");for($a=0;$a<d[b].length;$a++)"c"===d[b][$a]?this.b+=" center":"l"===d[b][$a]?this.b+=" left":"r"===d[b][$a]&&(this.b+=" right");if(this.b.length)this.b= +this.b.slice(1);else throw"Invalid column alignments";break;case 10:case 11:this.b={columnalign:d[b]};break;case 12:this.b={rowalign:d[b]};break;case 13:this.b={rowspan:d[b]};break;case 14:this.b={colspan:d[b]};break;case 15:this.b={align:d[b]};break;case 16:this.b={equalrows:d[b]};break;case 17:this.b={equalcolumns:d[b]};break;case 18:this.b={rowlines:d[b]};break;case 19:this.b={columnlines:d[b]};break;case 20:this.b={frame:d[b]};break;case 21:this.b={rowspacing:d[b],columnspacing:d[b]};break;case 22:case 23:case 24:case 25:case 26:case 28:case 29:case 30:case 31:case 32:case 33:case 34:case 35:case 36:case 37:case 38:case 39:case 40:case 42:case 170:case 175:case 180:case 181:case 186:case 196:case 207:case 209:this.b= +d[b];break;case 27:case 41:case 43:this.b=Object.assign(d[b-1],d[b]);break;case 44:case 46:this.b=ab(d[b]);break;case 45:case 47:this.b="";break;case 48:this.b=e("mrow");break;case 49:this.b=db(d[b-1]);break;case 50:case 54:this.b=e("mo",d[b],{maxsize:"1.2em",minsize:"1.2em"});break;case 51:case 55:this.b=e("mo",d[b],{maxsize:"1.8em",minsize:"1.8em"});break;case 52:case 56:this.b=e("mo",d[b],{maxsize:"2.4em",minsize:"2.4em"});break;case 53:case 57:this.b=e("mo",d[b],{maxsize:"3em",minsize:"3em"}); +break;case 58:this.b=e("mrow",[d[b-2],db(d[b-1]),d[b]]);break;case 59:this.b=e("mfrac",[db(d[b-3]),db(d[b-1])],{linethickness:"0px"});break;case 60:this.b=e("mfrac",[db(d[b-3]),db(d[b-1])],{linethickness:"0px"});this.b=e("mrow",[d[b-4],this.b,d[b]]);break;case 61:this.b=e("mfrac",[db(d[b-3]),db(d[b-1])]);break;case 62:this.b=e("mfrac",[db(d[b-3]),db(d[b-1])]);this.b=e("mrow",[d[b-4],this.b,d[b]]);break;case 63:this.b=e("mfrac",[db(d[b-3]),db(d[b-1])],{linethickness:"0px"});this.b=e("mrow",[ab("("), +this.b,ab(")")]);break;case 64:this.b=e("mfrac",[db(d[b-3]),db(d[b-1])],{linethickness:"0px"});this.b=e("mrow",[d[b-4],this.b,d[b]]);this.b=e("mrow",[ab("("),this.b,ab(")")]);break;case 65:case 74:this.b=e("mn",d[b]);break;case 66:case 83:case 85:this.b=e("mtext",d[b]);break;case 67:case 68:case 69:case 70:this.b=Ub(d[b]);break;case 71:this.b=Ub(d[b],tb);break;case 72:case 177:this.b=ab(d[b],0,0);break;case 73:this.b=e("mi",d[b]);break;case 75:case 76:case 77:case 176:this.b=ab(d[b]);break;case 78:case 79:case 80:this.b= +e("mo",d[b],{stretchy:"false"});break;case 81:this.b=e("ms",d[b]);break;case 82:this.b=e("ms",d[b],{lquote:d[b-2],rquote:d[b-1]});break;case 84:this.b=e("mtext",d[b-1]+d[b]);break;case 86:this.b=ab(d[b],0,sb("thinmathspace"));break;case 87:this.b=ab(d[b],sb("thinmathspace"),sb("thinmathspace"));break;case 88:this.b=ab(d[b],sb("mediummathspace"),sb("mediummathspace"));break;case 89:this.b=ab(d[b],sb("thickmathspace"),sb("thickmathspace"));break;case 90:this.b=e("mfrac",[d[b-1],d[b]]);break;case 91:this.b= +e("mroot",[d[b],d[b-1]]);break;case 92:this.b=e("msqrt",[d[b]]);break;case 93:this.b=e("mroot",[d[b],db(d[b-2])]);break;case 94:this.b=e("munder",[d[b],d[b-1]]);break;case 95:this.b=e("mover",[d[b],d[b-1]]);break;case 96:this.b=e("munderover",[d[b],d[b-2],d[b-1]]);break;case 97:this.b="mrow"===d[b].tag&&!d[b].content&&!d[b].attributes?e("munder",[ab(d[b-4]),db(d[b-2])]):e("munderover",[ab(d[b-4]),db(d[b-2]),d[b]]);break;case 98:this.b=e("mover",[ab(d[b-1]),d[b]]);break;case 99:this.b=e("mpadded", +[d[b]],{width:"0em"});break;case 100:this.b=e("mpadded",[d[b]],{width:"0em",lspace:"-100%width"});break;case 101:this.b=e("mpadded",[d[b]],{width:"0em",lspace:"-50%width"});break;case 102:this.b=e("mphantom",[d[b]]);break;case 103:this.b=e("mfrac",[d[b-1],d[b]]);this.b=db([this.b],"mstyle",{displaystyle:"false"});break;case 104:this.b=e("mfrac",[d[b-1],d[b]],{linethickness:"0px"});this.b=e("mrow",[ab("("),this.b,ab(")")]);break;case 105:this.b=e("mfrac",[d[b-1],d[b]],{linethickness:"0px"});this.b= +db([this.b],"mstyle",{displaystyle:"false"});this.b=e("mrow",[ab("("),this.b,ab(")")]);break;case 106:this.b=e("mrow",[ab("(",sb("mediummathspace")),ab("mod",nb,sb("thinmathspace")),d[b],ab(")",nb,sb("mediummathspace"))]);break;case 107:this.b=e("munder",[d[b],ab("⏟")]);break;case 108:this.b=e("munder",[d[b],ab("_")]);break;case 109:this.b=e("mover",[d[b],ab("⏞")]);break;case 110:this.b=e("mover",[d[b],ab(d[b-1])]);break;case 111:this.b=e("mover",[d[b],e("mo",d[b-1],{stretchy:"false"})]);break;case 112:this.b= +e("menclose",[d[b]],{notation:"box"});break;case 113:this.b=e("menclose",[d[b]],{notation:"updiagonalstrike"});break;case 114:this.b=Gb(1);break;case 115:this.b=Gb(2);break;case 116:this.b=Gb(sb("negativethinmathspace"));break;case 117:this.b=Gb(sb("negativemediummathspace"));break;case 118:this.b=Gb(sb("negativethickmathspace"));break;case 119:this.b=Gb(sb("thinmathspace"));break;case 120:this.b=Gb(sb("mediummathspace"));break;case 121:this.b=Gb(sb("thickmathspace"));break;case 122:this.b=e("mspace", +xb,{height:"."+d[b-2]+"ex",depth:"."+d[b-1]+"ex",width:"."+d[b]+"em"});break;case 123:this.b=e("mpadded",[d[b]],{voffset:d[b-3].i+d[b-3].k,height:d[b-2].i+d[b-2].k,depth:d[b-1].i+d[b-1].k});break;case 124:this.b=e("mpadded",[d[b]],{voffset:d[b-2].i+d[b-2].k,height:d[b-1].i+d[b-1].k,depth:0>d[b-2].i?"+"+-d[b-2].i+d[b-2].k:"depth"});break;case 125:$a={voffset:d[b-1].i+d[b-1].k};0<=d[b-1].i?$a.height="+"+d[b-1].i+d[b-1].k:($a.height="0pt",$a.depth="+"+-d[b-1].i+d[b-1].k);this.b=e("mpadded",[d[b]],$a); +break;case 126:this.b=db([d[b]],"mstyle",{mathvariant:"double-struck"});break;case 127:this.b=db([d[b]],"mstyle",{mathvariant:"bold"});break;case 128:this.b=db([d[b]],"mstyle",{mathvariant:"bold-italic"});break;case 129:this.b=db([d[b]],"mstyle",{mathvariant:"script"});break;case 130:this.b=db([d[b]],"mstyle",{mathvariant:"bold-script"});break;case 131:this.b=db([d[b]],"mstyle",{mathvariant:"sans-serif"});break;case 132:this.b=db([d[b]],"mstyle",{mathvariant:"fraktur"});break;case 133:this.b=db([d[b]], +"mstyle",{mathvariant:"italic"});break;case 134:this.b=db([d[b]],"mstyle",{mathvariant:"monospace"});break;case 135:this.b=db([d[b]],"mstyle",{mathvariant:"normal"});break;case 136:this.b=e("mrow",[d[b]],$a.v?xb:{href:d[b-1]});break;case 137:this.b=$a.v?d[b]:e("maction",[d[b],e("mtext",d[b-1])],{actiontype:"statusline"});break;case 138:this.b=$a.v?d[b]:e("maction",[d[b],e("mtext",d[b-1])],{actiontype:"tooltip"});break;case 139:this.b=$a.v?d[b]:e("maction",[d[b-1],d[b]],{actiontype:"toggle",selection:"2"}); +break;case 140:this.b=$a.v?e("mrow",d[b-1]):e("maction",d[b-1],{actiontype:"toggle"});break;case 141:case 144:this.b=e("mmultiscripts",[d[b-3]].concat(d[b-1]));break;case 142:this.b=e("mmultiscripts",[d[b-3]].concat(d[b-1]).concat(e("mprescripts")).concat(d[b-5]));break;case 143:this.b=e("mmultiscripts",[d[b-2],e("mprescripts")].concat(d[b-4]));break;case 145:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex"});break;case 146:this.b=e("mtable",d[b-1],{displaystyle:"true",rowspacing:"1.0ex"}); +break;case 147:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex"});this.b=e("mrow",[ab("("),this.b,ab(")")]);break;case 148:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex"});this.b=e("mrow",[ab("["),this.b,ab("]")]);break;case 149:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex"});this.b=e("mrow",[ab("|"),this.b,ab("|")]);break;case 150:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex"});this.b=e("mrow",[ab("{"),this.b,ab("}")]);break; +case 151:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex"});this.b=e("mrow",[ab("‖"),this.b,ab("‖")]);break;case 152:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex"});this.b=db([this.b],"mstyle",{scriptlevel:"2"});break;case 153:this.b=e("mtable",d[b-1],{displaystyle:"false",columnalign:"left left"});this.b=e("mrow",[ab("{"),this.b]);break;case 154:this.b=e("mtable",d[b-1],{displaystyle:"true",columnalign:"right left right left right left right left right left", +columnspacing:"0em"});break;case 155:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex",align:d[b-3],columnalign:d[b-2]});break;case 156:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex",columnalign:d[b-2]});break;case 157:this.b=e("mtable",d[b-1],{displaystyle:"false",columnalign:"center",rowspacing:"0.5ex"});break;case 158:this.b=e("mtable",d[b-1],{displaystyle:"false"});break;case 159:this.b=e("mtable",d[b-1],Object.assign(d[b-3],{displaystyle:"false"}));break;case 160:this.b= +[d[b]];break;case 161:this.b=d[b-1].concat([d[b]]);break;case 162:this.b=e("mmultiscripts",[d[b-1]].concat(d[b]));break;case 163:this.b=e("msubsup",[d[b-4],d[b-2],d[b]]);break;case 164:this.b=e("msubsup",[d[b-3],d[b-1],ab(d[b])]);break;case 165:this.b=e("msubsup",[d[b-4],d[b],d[b-2]]);break;case 166:this.b=e("msubsup",[d[b-3],d[b],ab(d[b-2])]);break;case 167:this.b=e("msub",[d[b-2],d[b]]);break;case 168:this.b=e("msup",[d[b-2],d[b]]);break;case 169:this.b=e("msup",[d[b-1],ab(d[b])]);break;case 171:this.b= +e("munderover",[d[b-4],d[b-2],d[b]]);break;case 172:this.b=e("munderover",[d[b-4],d[b],d[b-2]]);break;case 173:this.b=e("munder",[d[b-2],d[b]]);break;case 174:this.b=e("mover",[d[b-2],d[b]]);break;case 178:case 200:case 204:this.b=[d[b]];break;case 179:this.b=d[b-1].concat([d[b]]);break;case 182:this.b=[d[b-2],d[b]];break;case 183:this.b=[d[b],e("none")];break;case 184:case 185:this.b=[e("none"),d[b]];break;case 187:this.b=d[b-1].concat(d[b]);break;case 188:this.b={displaystyle:"true"};break;case 189:this.b= +{displaystyle:"false"};break;case 190:this.b={scriptlevel:"0"};break;case 191:this.b={scriptlevel:"1"};break;case 192:this.b={scriptlevel:"2"};break;case 193:this.b={mathcolor:d[b]};break;case 194:this.b={mathbackground:d[b]};break;case 195:this.b=[db(d[b],"mstyle",d[b-1])];break;case 197:this.b=e("mtd",[]);break;case 198:this.b=db(d[b],"mtd",d[b-2]);break;case 199:this.b=db(d[b],"mtd");break;case 201:case 205:this.b=d[b-2].concat([d[b]]);break;case 202:this.b=this.b=e("mtr",d[b],d[b-2]);break;case 203:this.b= +e("mtr",d[b]);break;case 206:return this.b=d[b-1];case 208:this.b=d[b-1]+d[b];break;case 210:this.b=Pb(d[b]);break;case 211:this.b=Ib([e("mrow")],yb,yb,$a.t);break;case 212:this.b=Ib(d[b-1],yb,yb,$a.t);break;case 213:this.b=Ib([e("mrow")],tb,yb,$a.t);break;case 214:this.b=Ib(d[b-1],tb,yb,$a.t);break;case 215:this.b=Ib(d[b-1],yb,yb,$a.t);break;case 216:this.b=Ib(d[b-1],tb,yb,$a.t)}},ma:[{68:Wb,193:1,194:2,196:3,197:5,198:Xb,200:Yb,202:Zb,204:$b},{1:[3]},{68:Wb,195:[1,10],196:11,197:5,198:Xb,200:Yb, +202:Zb,204:$b},c(Ab,[2,207]),c(Ab,[2,209]),c(Ab,[2,210]),{8:m,48:36,49:s,50:v,51:x,54:25,55:13,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da, +130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,199:[1,12]},{8:m,48:36,49:s,50:v,51:x,54:25,55:126,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa, +101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,201:[1,125]},{8:m,48:36,49:s,50:v,51:x,54:25,55:127,56:h, +57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa, +153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},{8:m,48:36,49:s,50:v,51:x,54:25,55:128,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa, +117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},{1:[2,206]},c(Ab,[2,208]),c(Ab,[2,211]),{199:[1,129]},{8:m,48:36,49:s,50:v,51:x,54:25,55:130,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B, +74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26, +173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},c(ac,[2,196],{54:25,172:26,48:36,168:131,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa, +124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb}),c(Db,[2,188]),c(Db,[2,189]),c(Db,[2,190]),c(Db,[2,191]),c(Db,[2,192]),{7:133,8:mb,14:132},{7:133,8:mb,14:135},c(ub,[2,178]),{8:m,48:36,49:s,50:v,51:x,54:136,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N, +87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(ub,[2,170],{169:[1,138],170:[1,139],171:[1,140]}),c(ub,[2,175],{169:[1, +141],170:[1,142]}),{8:m,10:[1,143],48:36,49:s,50:v,51:x,54:25,55:144,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha, +134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},{50:[1,145]},{50:[1,146]},{50:[1,147]},{50:[1,148]},{50:[1,149]},{50:[1,150]},{50:[1,151]},{50:[1,152]},{8:m,48:36,49:s,50:v,51:x,54:25,55:153,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O, +88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb, +184:kb,185:lb},c(g,[2,65]),c(g,[2,66]),c(g,[2,67]),c(g,[2,68]),c(g,[2,69]),c(g,[2,70]),c(g,[2,71]),c(g,[2,72]),{7:155,8:mb,15:154},{7:155,8:mb,15:156},{7:155,8:mb,15:157},c(g,[2,76]),c(g,[2,77]),c(g,[2,78]),c(g,[2,79]),c(g,[2,80]),{3:160,4:Sb,7:155,8:mb,13:159,15:158},{7:155,8:mb,15:162},{84:[1,163]},c(g,[2,85]),{7:164,8:mb},{7:165,8:mb},{7:166,8:mb},{7:167,8:mb},{8:m,48:36,49:s,50:v,51:x,54:168,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H, +80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:169,56:h,57:q, +58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra, +155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{4:[1,171],8:m,48:36,49:s,50:v,51:x,54:170,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da, +130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:172,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa, +117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:173,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da, +104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:174,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N, +87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{4:[1,175],8:m,48:36,49:s,50:v,51:x,54:176,56:h,57:q,58:p,59:y,60:f,61:u, +62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua, +161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:177,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia, +135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:178,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va, +122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:179,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia, +109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:180,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U, +94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:181,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B, +74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36, +49:s,50:v,51:x,54:182,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma, +145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:183,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa, +127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:184,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na, +114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:185,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa, +101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:186,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I, +81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:187,56:h,57:q,58:p, +59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa, +157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:188,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga, +133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:189,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta, +120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:190,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga, +107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:191,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R, +91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,114]),c(g,[2,115]),c(g,[2,116]),c(g,[2,117]),c(g,[2,118]),c(g,[2,119]),c(g,[2,120]), +c(g,[2,121]),{7:192,8:mb},{8:[1,194],12:193},{8:m,48:36,49:s,50:v,51:x,54:195,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa, +132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:196,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa, +119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:197,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa, +106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:198,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P, +89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:199,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t, +69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa, +166:Ya},{8:m,48:36,49:s,50:v,51:x,54:200,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za, +141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:201,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya, +125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:202,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la, +112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:203,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y, +98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:204,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F, +78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{7:133,8:mb,14:205},{7:206,8:mb}, +{7:207,8:mb},{8:m,48:36,49:s,50:v,51:x,54:208,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka, +139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:210,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa, +124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,137:209,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:[1,211]},c([144,189,192],pb,{178:14,175:15,168:23,54:25,172:26,48:36,143:212,190:213,188:215,186:216,55:218,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U, +94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c([146,189,192],pb,{178:14,175:15, +168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:219,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea, +131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c([148,189,192],pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:220,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S, +92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c([150,189,192],pb, +{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:221,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca, +129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c([152,189,192],pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:222,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P, +89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c([154, +189,192],pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:223,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba, +128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c([156,189,192],pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:224,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O, +88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}), +c([158,189,192],pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:225,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa, +127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c([160,189,192],pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:226,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M, +86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb, +191:vb}),c([162,189,192],pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:227,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya, +125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),{3:230,4:Sb,7:231,8:mb,16:228,17:229},{8:[1,232]},{8:[1,233]},c(Eb,[2,176]),c(Eb,[2,177]),{50:[1,234],51:[1,235]},c(Ab,[2,213]),{201:[1,236]},{203:[1,237]},{205:[1,238]},c(Ab,[2,212]),c(ac,[2,195]),c(ub,[2,179]),c(Db,[2,193]),c([8,10, +19,21,23,25,27,29,31,33,35,37,39,41,49,50,51,56,57,58,59,60,61,62,63,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,139,141,142,145,147,149,151,153,155,157,159,161,163,165,166,173,174,179,180,181,182,183,184,185],[2,6]),{9:[1,239]},c(Db,[2,194]),{8:bc,140:240,169:Bb,170:Cb,177:242},{8:m,48:36,49:s,50:v, +51:x,54:245,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa, +149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:246,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca, +129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:247,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa, +116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(ub,[2,169],{169:[1,248]}),{8:m,48:36,49:s,50:v,51:x,54:249,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y, +98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:250,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F, +78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,48]),{10:[1,251],64:[1,252], +65:[1,253],66:[1,254]},c(g,[2,50]),c(g,[2,51]),c(g,[2,52]),c(g,[2,53]),c(g,[2,54]),c(g,[2,55]),c(g,[2,56]),c(g,[2,57]),{52:255,53:Mb,64:[1,256],65:[1,257],66:[1,258]},c(g,[2,73]),c(g,[2,7]),c(g,[2,74]),c(g,[2,75]),c(g,[2,81]),{3:160,4:Sb,13:260},c(cc,[2,5]),{5:[1,261]},c(g,[2,83]),c(g,[2,84]),c(g,[2,86]),c(g,[2,87]),c(g,[2,88]),c(g,[2,89]),{8:m,48:36,49:s,50:v,51:x,54:262,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M, +86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:263,56:h,57:q,58:p,59:y,60:f,61:u,62:n, +63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua, +161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,92]),{8:m,48:36,49:s,50:v,51:x,54:25,55:264,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa, +132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},{8:m,48:36,49:s,50:v,51:x,54:265,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa, +106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:266,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P, +89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:267,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t, +69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa, +166:Ya},{8:m,48:36,49:s,50:v,51:x,54:25,55:268,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka, +139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},c(g,[2,98]),c(g,[2,99]),c(g,[2,100]),c(g,[2,101]),c(g,[2,102]),{8:m,48:36,49:s,50:v,51:x,54:269,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba, +102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:270,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K, +83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:271,56:h,57:q,58:p,59:y,60:f, +61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta, +159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,106]),c(g,[2,107]),c(g,[2,108]),c(g,[2,109]),c(g,[2,110]),c(g,[2,111]),c(g,[2,112]),c(g,[2,113]),{7:272,8:mb},{4:dc,8:m,11:273,48:36,49:s,50:v,51:x,54:274,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa, +115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{9:[1,276]},c(g,[2,126]),c(g,[2,127]),c(g,[2,128]),c(g,[2,129]),c(g,[2,130]),c(g,[2,131]),c(g,[2,132]),c(g,[2,133]),c(g,[2,134]),c(g,[2,135]),{8:m,48:36,49:s,50:v,51:x,54:277,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A, +73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36, +49:s,50:v,51:x,54:278,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma, +145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:279,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa, +127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:280,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na, +114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:282,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa, +101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,138:[1,281],139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(ec,[2,160]),{10:[1,284],140:283,169:Bb,170:Cb,177:242},{144:[1,285],192:wb},c(Nb,[2,204]),{8:[1,287]},c(Nb,[2,203],{189:fc}),c(Jb, +[2,200]),{8:[1,289]},c(Jb,[2,199]),{146:[1,290],192:wb},{148:[1,291],192:wb},{150:[1,292],192:wb},{152:[1,293],192:wb},{154:[1,294],192:wb},{156:[1,295],192:wb},{158:[1,296],192:wb},{160:[1,297],192:wb},{162:[1,298],192:wb},{7:231,8:mb,17:299},c(gc,pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:300,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q, +90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),{8:[2,8]}, +c([8,49,50,51,56,57,58,59,60,61,62,63,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,139,141,142,145,147,149,151,153,155,157,159,161,163,164,165,166,173,174,179,180,181,182,183,184,185,187,189,191,192],[2,9]),c(Tb,pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:301,8:m,49:s, +50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa, +149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c(Tb,pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:302,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga, +107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,167:[1,303],173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c(Db,[2,44]),c(Db,[2,45]),c(Ab,[2,214]),c(Ab,[2,215]),c(Ab,[2,216]),{10:[1,304]},c(ub,[2,162],{177:305, +169:Bb,170:Cb}),{140:306,169:Bb,170:Cb,177:242},c(Eb,[2,186]),{8:m,48:36,49:s,50:v,51:x,54:309,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da, +130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,170:[1,308],172:310,173:bb,174:cb,176:307},{8:m,48:36,49:s,50:v,51:x,54:309,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka, +111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,172:310,173:bb,174:cb,176:311},{8:bc},c(ub,[2,167],{170:[1,312],171:[1,313]}),c(ub,[2,168],{169:[1,314]}),{8:m,48:36,49:s,50:v,51:x,54:315,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B, +74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(ub,[2,173], +{170:[1,316]}),c(ub,[2,174],{169:[1,317]}),c(g,[2,49]),{8:m,48:36,49:s,50:v,51:x,54:25,55:318,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da, +130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},{8:m,48:36,49:s,50:v,51:x,54:25,55:319,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca, +103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},{8:m,48:36,49:s,50:v,51:x,54:25,55:320,56:h,57:q,58:p,59:y,60:f,61:u, +62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua, +161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},c(g,[2,58]),{8:m,48:36,49:s,50:v,51:x,54:25,55:321,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa, +119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},{8:m,48:36,49:s,50:v,51:x,54:25,55:322,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O, +88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb, +184:kb,185:lb},{8:m,48:36,49:s,50:v,51:x,54:25,55:323,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja, +136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},{50:[1,324],51:[1,325]},{7:155,8:mb,15:326},{6:[1,327]},c(g,[2,90]),c(g,[2,91]),{6:[1,328]},c(g,[2,94]),c(g,[2,95]),{8:m,48:36,49:s,50:v,51:x,54:329,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q, +90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{6:[1,330]},c(g,[2,103]),c(g,[2,104]),c(g,[2,105]),{7:331,8:mb},{4:dc,8:m,11:332,48:36, +49:s,50:v,51:x,54:333,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma, +145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,125]),{5:[1,334]},{10:[1,335]},c(g,[2,136]),c(g,[2,137]),c(g,[2,138]),c(g,[2,139]),c(g,[2,140]),c(ec,[2,161]),{10:[1,336],169:Bb,170:Cb,177:305},{8:m,48:36,49:s,50:v,51:x,54:337,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea, +105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,145]),c(Jb,pb,{178:14,175:15,168:23,54:25,172:26,48:36,188:215,186:216,55:218,190:338,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w, +71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya, +173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),{20:341,21:Kb,22:342,23:Lb,44:340,47:339},c(Jb,pb,{178:14,175:15,168:23,54:25,172:26,48:36,55:218,186:345,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na, +114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb}),{20:348,21:Kb,22:349,23:Lb,24:350,25:hc,26:351,27:ic,42:347,43:346},c(g,[2,146]),c(g,[2,147]),c(g,[2,148]),c(g,[2,149]),c(g,[2,150]),c(g,[2,151]),c(g,[2,152]),c(g, +[2,153]),c(g,[2,154]),c(gc,pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:354,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya, +125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),{164:[1,355],192:wb},{10:[1,356],192:wb},{10:[1,357],192:wb},{8:[1,358]},c([6,8,10,19,21,23,25,27,29,31,33,35,37,39,41,49,50,51,53,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90, +91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,138,139,141,142,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,169,170,171,173,174,179,180,181,182,183,184,185,187,189,191,192,199,201,203,205],[2,2]),c(Eb,[2,187]),{10:[1,359],169:Bb,170:Cb,177:305},c([6,8,10,49,50,51,53,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75, +76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,139,141,142,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,169,173,174,189,192,199,201,203,205],[2,183],{170:[1,360]}),{8:m,48:36,49:s,50:v,51:x,54:309,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E, +77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,172:310,173:bb,174:cb,176:361}, +c(Eb,[2,180]),c(Eb,[2,181]),c(Eb,[2,184]),{8:m,48:36,49:s,50:v,51:x,54:362,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga, +133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(ub,[2,164]),{8:m,48:36,49:s,50:v,51:x,54:363,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra, +118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(ub,[2,166]),{8:m,48:36,49:s,50:v,51:x,54:364,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca, +103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:365,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L, +85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{10:[1,366]},{10:[1,367]},{10:[1,368]},{52:369,53:Mb},{52:370, +53:Mb},{52:371,53:Mb},c(g,[2,46]),c(g,[2,47]),c(g,[2,82]),c(cc,[2,1]),{8:m,48:36,49:s,50:v,51:x,54:372,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca, +129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,96]),{8:m,48:36,49:s,50:v,51:x,54:373,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na, +114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,122]),{8:m,48:36,49:s,50:v,51:x,54:374,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y, +98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,124]),{6:[1,375]},c(jc,[2,4]),{8:m,48:36,49:s,50:v,51:x,54:376,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w, +71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya}, +{8:[1,377]},c(Nb,[2,205]),{10:[1,378],20:341,21:Kb,22:342,23:Lb,44:379},c(Ob,[2,42]),c(Ob,[2,28]),c(Ob,[2,29]),{7:133,8:mb,14:380},{7:133,8:mb,14:381},c(Jb,[2,201]),{10:[1,382],20:348,21:Kb,22:349,23:Lb,24:350,25:hc,26:351,27:ic,42:383},c(Hb,[2,26]),c(Hb,[2,22]),c(Hb,[2,23]),c(Hb,[2,24]),c(Hb,[2,25]),{7:133,8:mb,14:384},{7:133,8:mb,14:385},{164:[1,386],192:wb},c(g,[2,156]),c(g,[2,157]),c(g,[2,158]),{18:389,19:kc,20:390,21:Kb,22:391,23:Lb,28:392,29:lc,30:393,31:mc,32:394,33:nc,34:395,35:oc,36:396, +37:pc,38:397,39:qc,40:398,41:rc,45:388,46:387},c(g,[2,141]),{8:m,48:36,49:s,50:v,51:x,54:309,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da, +130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,172:310,173:bb,174:cb,176:407},c(Eb,[2,185]),c(ub,[2,163]),c(ub,[2,165]),c(ub,[2,171]),c(ub,[2,172]),c(g,[2,59]),c(g,[2,61]),c(g,[2,63]),c(g,[2,60]),c(g,[2,62]),c(g,[2,64]),c(g,[2,93]),c(g,[2,97]),c(g,[2,123]),c(jc,[2,3]),{8:[1,408]},{140:409,169:Bb,170:Cb,177:242},c(Jb,pb,{178:14,175:15,168:23,54:25,172:26,48:36,186:216,55:218,188:410,8:m,49:s, +50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa, +149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb}),c(Ob,[2,43]),c(sc,[2,11]),c(sc,[2,12]),{8:m,48:36,49:s,50:v,51:x,54:25,55:411,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la, +112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},c(Hb,[2,27]),c(Hb,[2,13]),c(Hb,[2,14]),c(g,[2,155]),{10:[1,412],18:389,19:kc,20:390,21:Kb,22:391,23:Lb,28:392,29:lc,30:393,31:mc, +32:394,33:nc,34:395,35:oc,36:396,37:pc,38:397,39:qc,40:398,41:rc,45:413},c(ob,[2,40]),c(ob,[2,30]),c(ob,[2,31]),c(ob,[2,32]),c(ob,[2,33]),c(ob,[2,34]),c(ob,[2,35]),c(ob,[2,36]),c(ob,[2,37]),c(ob,[2,38]),c(ob,[2,39]),{7:133,8:mb,14:414},{7:133,8:mb,14:415},{7:133,8:mb,14:416},{7:133,8:mb,14:417},{7:133,8:mb,14:418},{7:133,8:mb,14:419},{7:133,8:mb,14:420},{7:133,8:mb,14:421},c(Eb,[2,182]),{10:[1,423],140:422,169:Bb,170:Cb,177:242},{10:[1,424],169:Bb,170:Cb,177:305},c(Nb,[2,202],{189:fc}),c(Jb,[2,198]), +c(Tb,pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:425,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba, +128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c(ob,[2,41]),c(ob,[2,10]),c(ob,[2,15]),c(ob,[2,16]),c(ob,[2,17]),c(ob,[2,18]),c(ob,[2,19]),c(ob,[2,20]),c(ob,[2,21]),{10:[1,426],169:Bb,170:Cb,177:305},c(g,[2,143]),c(g,[2,144]),{10:[1,427],192:wb},c(g,[2,142]),c(g,[2,159])],N:{10:[2,206],230:[2,8]},parseError:function(b, +a){if(a.va)this.trace(b);else{var c=Error(b);c.hash=a;throw c;}},parse:function(b){var a=[0],c=[xb],e=[],g=this.ma,d="",m=0,s=0,v=0,x=e.slice.call(arguments,1),h=Object.create(this.S),q={},p;for(p in this.e)Object.prototype.hasOwnProperty.call(this.e,p)&&(q[p]=this.e[p]);h.ga(b,q);q.S=h;q.V=this;"undefined"==typeof h.c&&(h.c={});p=h.c;e.push(p);var y=h.options&&h.options.w;this.parseError="function"===typeof q.parseError?q.parseError:Object.getPrototypeOf(this).parseError;for(var f,u,n,k,r={},t,l;;){n= +a[a.length-1];if(this.N[n])k=this.N[n];else{if(f===xb||"undefined"==typeof f)f=nb,f=h.R()||1,"number"!==typeof f&&(f=this.la[f]||f);k=g[n]&&g[n][f]}if("undefined"===typeof k||!k.length||!k[0]){var w="";l=[];for(t in g[n])this.z[t]&&2<t&&l.push("'"+this.z[t]+"'");w=h.D?"Parse error on line "+(m+1)+":\n"+h.D()+"\nExpecting "+l.join(", ")+", got '"+(this.z[f]||f)+"'":"Parse error on line "+(m+1)+": Unexpected "+(1==f?"end of input":"'"+(this.z[f]||f)+"'");this.parseError(w,{text:h.match,$:this.z[f]|| +f,T:h.f,ta:p,qa:l})}if(k[0]instanceof Array&&1<k.length)throw Error("Parse Error: multiple actions possible at state: "+n+", token: "+f);switch(k[0]){case 1:a.push(f);c.push(h.a);e.push(h.c);a.push(k[1]);f=xb;u?(f=u,u=xb):(s=h.q,d=h.a,m=h.f,p=h.c,0<v&&v--);break;case 2:l=this.W[k[1]][1];r.b=c[c.length-l];r.K={r:e[e.length-(l||1)].r,o:e[e.length-1].o,l:e[e.length-(l||1)].l,m:e[e.length-1].m};y&&(r.K.n=[e[e.length-(l||1)].n[0],e[e.length-1].n[1]]);n=this.H.apply(r,[d,s,m,q,k[1],c,e].concat(x));if("undefined"!== +typeof n)return n;l&&(a=a.slice(0,-2*l),c=c.slice(0,-1*l),e=e.slice(0,-1*l));a.push(this.W[k[1]][0]);c.push(r.b);e.push(r.K);k=g[a[a.length-2]][a[a.length-1]];a.push(k);break;case 3:return tb}}return tb}},Qb="http://www.w3.org/1998/Math/MathML",wc="TeX LaTeX text/x-tex text/x-latex application/x-tex application/x-latex".split(" ");try{rb.C=new DOMParser}catch(yc){rb.C={parseFromString:function(){throw"DOMParser undefined. Did you call TeXZilla.setDOMParser?";}}}rb.fa=function(b){this.C=b};try{rb.G= +new XMLSerializer}catch(zc){rb.G={serializeToString:function(){throw"XMLSerializer undefined. Did you call TeXZilla.setXMLSerializer?";}}}rb.ja=function(b){this.G=b};rb.U=function(b){return this.C.parseFromString(b,"application/xml").documentElement};rb.ia=function(b){this.e.v=b};rb.ha=function(b){this.e.da=b};rb.ca=function(b){"string"===typeof b&&(b=this.U(b));return Vb(b)};rb.Z=function(b,a,c,g){var f;try{f=this.parse("\\("+b+"\\)"),c&&(f=f.replace(/^<math/,'<math dir="rtl"')),a&&(f=f.replace(/^<math/, +'<math display="block"'))}catch(d){if(g)throw d;f=Pb(Ib([e("merror",[e("mtext",Fb(d.message))])],a,c,b))}return f};rb.Y=function(b,a,c,e){return this.U(this.Z(b,a,c,e))};rb.na=function(b,a,c,e,f){var d,g;e===nb&&(e=64);f===nb&&(f=window.document);a=this.Y(b,tb,a);a.setAttribute("mathsize",e+"px");e=document.createElement("div");e.style.visibility="hidden";e.style.position="absolute";e.appendChild(a);f.body.appendChild(e);d=a.getBoundingClientRect();f.body.removeChild(e);e.removeChild(a);c?(c=Math.pow(2, +Math.ceil(Math.log(d.width)/Math.LN2)),f=Math.pow(2,Math.ceil(Math.log(d.height)/Math.LN2))):(c=Math.ceil(d.width),f=Math.ceil(d.height));g=document.createElementNS("http://www.w3.org/2000/svg","svg");g.setAttribute("width",c+"px");g.setAttribute("height",f+"px");e=document.createElementNS("http://www.w3.org/2000/svg","g");e.setAttribute("transform","translate("+(c-d.width)/2+","+(f-d.height)/2+")");g.appendChild(e);e=document.createElementNS("http://www.w3.org/2000/svg","foreignObject");e.setAttribute("width", +d.width);e.setAttribute("height",d.height);e.appendChild(a);g.firstChild.appendChild(e);a=new Image;a.src="data:image/svg+xml;base64,"+window.btoa(xc(this.G.serializeToString(g)));a.width=c;a.height=f;a.alt=Fb(b);return a};rb.Q=function(b,a){try{return this.parse(b)}catch(c){if(a)throw c;return b}};rb.P=function(b,a){var c,e,f;for(f=b.firstChild;f;f=f.nextSibling)switch(f.nodeType){case 1:this.P(f,a);break;case 3:this.e.O=tb;c=this.C.parseFromString("<root>"+zb.Q(f.data,a)+"</root>","application/xml").documentElement; +for(this.e.O=yb;e=c.firstChild;)b.insertBefore(c.removeChild(e),f);e=f.previousSibling;b.removeChild(f);f=e}};rb.S=function(){return{J:1,parseError:function(b,a){if(this.e.V)this.e.V.parseError(b,a);else throw Error(b);},ga:function(b,a){this.e=a||this.e||{};this.g=b;this.u=this.B=this.s=yb;this.f=this.q=0;this.a=this.h=this.match="";this.d=["INITIAL"];this.c={r:1,l:0,o:1,m:0};this.options.w&&(this.c.n=[0,0]);this.offset=0;return this},input:function(){var b=this.g[0];this.a+=b;this.q++;this.offset++; +this.match+=b;this.h+=b;b.match(/(?:\r\n?|\n).*/g)?(this.f++,this.c.o++):this.c.m++;this.options.w&&this.c.n[1]++;this.g=this.g.slice(1);return b},I:function(b){var a=b.length,c=b.split(/(?:\r\n?|\n)/g);this.g=b+this.g;this.a=this.a.substr(0,this.a.length-a);this.offset-=a;b=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1);this.h=this.h.substr(0,this.h.length-1);c.length-1&&(this.f-=c.length-1);var e=this.c.n;this.c={r:this.c.r,o:this.f+1,l:this.c.l,m:c?(c.length=== +b.length?this.c.l:0)+b[b.length-c.length].length-c[0].length:this.c.l-a};this.options.w&&(this.c.n=[e[0],e[0]+this.q-a]);this.q=this.a.length;return this},ua:function(){this.u=tb;return this},wa:function(){if(this.options.L)this.B=tb;else return this.parseError("Lexical error on line "+(this.f+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.D(),{text:"",$:xb,T:this.f});return this},sa:function(b){this.I(this.match.slice(b))}, +ea:function(){var b=this.h.substr(0,this.h.length-this.match.length);return(20<b.length?"...":"")+b.substr(-20).replace(/\n/g,"")},oa:function(){var b=this.match;20>b.length&&(b+=this.g.substr(0,20-b.length));return(b.substr(0,20)+(20<b.length?"...":"")).replace(/\n/g,"")},D:function(){var b=this.ea(),a=Array(b.length+1).join("-");return b+this.oa()+"\n"+a+"^"},X:function(b,a){var c,e;this.options.L&&(e={f:this.f,c:{r:this.c.r,o:this.o,l:this.c.l,m:this.c.m},a:this.a,match:this.match,matches:this.matches, +h:this.h,q:this.q,offset:this.offset,u:this.u,g:this.g,e:this.e,d:this.d.slice(0),s:this.s},this.options.w&&(e.c.n=this.c.n.slice(0)));if(c=b[0].match(/(?:\r\n?|\n).*/g))this.f+=c.length;this.c={r:this.c.o,o:this.f+1,l:this.c.m,m:c?c[c.length-1].length-c[c.length-1].match(/\r?\n?/)[0].length:this.c.m+b[0].length};this.a+=b[0];this.match+=b[0];this.matches=b;this.q=this.a.length;this.options.w&&(this.c.n=[this.offset,this.offset+=this.q]);this.B=this.u=yb;this.g=this.g.slice(b[0].length);this.h+=b[0]; +c=this.H.call(this,this.e,this,a,this.d[this.d.length-1]);this.s&&this.g&&(this.s=yb);if(c)return c;if(this.B)for(var f in e)this[f]=e[f];return yb},next:function(){if(this.s)return this.J;this.g||(this.s=tb);var b,a,c;this.u||(this.match=this.a="");for(var e=this.aa(),f=0;f<e.length;f++)if((a=this.g.match(this.rules[e[f]]))&&(!b||a[0].length>b[0].length))if(b=a,c=f,this.options.L){b=this.X(a,e[f]);if(b!==yb)return b;if(this.B)b=yb;else return yb}else if(!this.options.ra)break;return b?(b=this.X(b, +e[c]),b!==yb?b:yb):""===this.g?this.J:this.parseError("Lexical error on line "+(this.f+1)+". Unrecognized text.\n"+this.D(),{text:"",$:xb,T:this.f})},R:function(){var b=this.next();return b?b:this.R()},j:function(b){this.d.push(b)},p:function(){return 0<this.d.length-1?this.d.pop():this.d[0]},aa:function(){return this.d.length&&this.d[this.d.length-1]?this.M[this.d[this.d.length-1]].rules:this.M.INITIAL.rules},ya:function(b){b=this.d.length-1-Math.abs(b||0);return 0<=b?this.d[b]:"INITIAL"},pushState:function(b){this.j(b)}, +xa:function(){return this.d.length},options:{},H:function(b,a,c){switch(c){case 0:this.I(a.a);this.pushState("DOCUMENT");break;case 1:return this.pushState("MATH"+(0+!!b.da)),b.ka=this.h.length,"STARTMATH"+(2*("$"==a.a[0])+("$"==a.a[1]||"["==a.a[1]));case 2:return this.p(),"EOF";case 3:return a.a=a.a[1],"TEXT";case 4:return b.O&&(a.a=Fb(a.a)),"TEXT";case 5:return"TEXT";case 6:return this.p(),"[";case 7:this.I(a.a);this.p();this.p();break;case 8:return"TEXTOPTARG";case 9:return this.p(),"]";case 10:return"{"; +case 11:return"TEXTARG";case 12:return this.p(),"}";case 13:return this.p(),"]";case 15:return this.p(),b.ba=this.h.length-this.match.length,b.t=this.h.substring(b.ka,b.ba),"ENDMATH"+(2*("$"==a.a[0])+("$"==a.a[1]||"]"==a.a[1]));case 16:return"{";case 17:return"}";case 18:return"^";case 19:return"_";case 20:return".";case 21:return"COLSEP";case 22:return"ROWSEP";case 23:return"NUM";case 24:return"A";case 25:return a.a="Ζ","AIUG";case 26:return a.a="ζ","AILG";case 27:return this.pushState("OPTARG"), +this.pushState("TRYOPTARG"),a.a="⇌","XARROW";case 28:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="⇒","XARROW";case 29:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="→","XARROW";case 30:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="↦","XARROW";case 31:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="⇋","XARROW";case 32:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="⇔","XARROW";case 33:return this.pushState("OPTARG"), +this.pushState("TRYOPTARG"),a.a="↔","XARROW";case 34:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="⇐","XARROW";case 35:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="←","XARROW";case 36:return a.a="Ξ","AIUG";case 37:return a.a="ξ","AILG";case 38:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="↪","XARROW";case 39:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="↩","XARROW";case 40:return a.a="≀","OP";case 41:return a.a="℘","A";case 42:return a.a= +"⇀","ACCENT";case 43:return a.a="˜","ACCENT";case 44:return a.a="^","ACCENT";case 45:return a.a="ˇ","ACCENT";case 46:return a.a="¯","ACCENT";case 47:return a.a="≙","OP";case 48:return a.a="⋀","OPM";case 49:return a.a="∧","OP";case 50:return a.a="⦀","OPFS";case 51:return a.a="⊪","OP";case 52:return a.a="‖","OPFS";case 53:return a.a="|","OPFS";case 54:return a.a="⊻","OP";case 55:return a.a="⋁","OPM";case 56:return a.a="∨","OP";case 57:return a.a="⇀","ACCENTNS";case 58:return a.a="⋮","OP";case 59:return a.a= +"⊫","OP";case 60:return a.a="⊩","OP";case 61:return a.a="⊨","OP";case 62:return a.a="⊢","OP";case 63:return a.a="⫫","OP";case 64:return a.a="⊳","OP";case 65:return a.a="⊲","OP";case 66:return a.a="▵","OP";case 67:return a.a="ϑ","AILG";case 68:return a.a="⫌︀","OP";case 69:return a.a="⊋︀","OP";case 70:return a.a="⫋︀","OP";case 71:return a.a="⊊︀","OP";case 72:return a.a="⊊︀","OP";case 73:return a.a="ς","A";case 74:return a.a="ϱ","AILG";case 75:return a.a="∝","OP";case 76:return a.a="ϖ","AILG";case 77:return a.a= +"φ","AILG";case 78:return a.a="∅","A";case 79:return a.a="ϰ","AILG";case 80:return a.a="ε","AILG";case 81:return a.a="⤊","OPS";case 82:return a.a="⇈","OPS";case 83:return a.a="ϒ","A";case 84:return a.a="υ","AILG";case 85:return a.a="ϒ","A";case 86:return a.a="⊎","OP";case 87:return a.a="⨛","OP";case 88:return a.a="↿","OPS";case 89:return a.a="↾","OPS";case 90:return a.a="⇕","OPS";case 91:return a.a="↕","OPS";case 92:return a.a="↕","OPS";case 93:return a.a="⇑","OPS";case 94:return a.a="↑","OPS";case 95:return a.a= +"↑","OPS";case 96:return a.a="⊵","OP";case 97:return a.a="⊴","OP";case 98:return a.a="⋃","OPM";case 99:return a.a="∪","OP";case 100:return"UNDERSET";case 101:return"UNDEROVERSET";case 102:return"UNDERLINE";case 103:return"UNDERBRACE";case 104:return a.a="⋰","OP";case 105:return"OP";case 106:return"OP";case 107:return"OP";case 108:return"OP";case 109:return"OP";case 110:return"OP";case 111:return"OP";case 112:return"OP";case 113:return"OP";case 114:return"OP";case 115:return"OP";case 116:return"OP"; +case 117:return"OP";case 118:return"OP";case 119:return"OP";case 120:return"OP";case 121:return"OP";case 122:return"OP";case 123:return"OP";case 124:return"OP";case 125:return"OP";case 126:return"OP";case 127:return"OP";case 128:return"OP";case 129:return"OP";case 130:return"OP";case 131:return"OP";case 132:return"OP";case 133:return"OP";case 134:return"OP";case 135:return"OP";case 136:return"OP";case 137:return"OP";case 138:return"OPFS";case 139:return"OPFS";case 140:return"OP";case 141:return"OP"; +case 142:return"OP";case 143:return"OP";case 144:return"OP";case 145:return"OP";case 146:return"OP";case 147:return"OP";case 148:return"OP";case 149:return"OP";case 150:return"OP";case 151:return"OP";case 152:return"OP";case 153:return"OP";case 154:return"OP";case 155:return"OP";case 156:return"OP";case 157:return"OP";case 158:return"OP";case 159:return"OP";case 160:return"OP";case 161:return a.a="⤖","OP";case 162:return a.a="↠","OPS";case 163:return a.a="↞","OPS";case 164:return a.a="∭","OP";case 165:return a.a= +"⊵","OP";case 166:return a.a="▹","OP";case 167:return a.a="≜","OP";case 168:return a.a="⊴","OP";case 169:return a.a="◃","OP";case 170:return a.a="▿","OP";case 171:return a.a="▵","OP";case 172:return a.a="⤪","OP";case 173:return a.a="⤩","OP";case 174:return a.a="⊤","OP";case 175:return this.pushState("TEXTARG"),"TOOLTIP";case 176:return a.a="⤧","OP";case 177:return"TOGGLE";case 178:return a.a="⤨","OP";case 179:return a.a="→","OPS";case 180:return a.a="⊠","OP";case 181:return a.a="×","OP";case 182:return a.a= +"˜","ACCENTNS";case 183:return"THINSPACE";case 184:return"THICKSPACE";case 185:return a.a="∼","OP";case 186:return a.a="≈","OP";case 187:return a.a="Θ","AIUG";case 188:return a.a="θ","AILG";case 189:return a.a="∴","OP";case 190:return"TFRAC";case 191:return"TEXTSTYLE";case 192:return"TEXTSIZE";case 193:return a.a="”","OPF";case 194:return a.a="“","OPF";case 195:return a.a="~","OPS";case 196:return a.a="`","OP";case 197:return a.a="^","OPS";case 198:return a.a="´","OP";case 199:return this.j("TEXTARG"), +"MTEXT";case 200:return"TENSOR";case 201:return"TBINOM";case 202:return a.a="Τ","AIUG";case 203:return a.a="τ","AILG";case 204:return a.a="⇙","OPS";case 205:return a.a="↙","OPS";case 206:return a.a="⇙","OPS";case 207:return a.a="↙","OPS";case 208:return a.a="√","OPS";case 209:return a.a="⫌","OP";case 210:return a.a="⊋","OP";case 211:return a.a="⫆","OP";case 212:return a.a="⊇","OP";case 213:return a.a="⋑","OP";case 214:return a.a="⊃","OP";case 215:return a.a="∑","OPM";case 216:return a.a="≿","OP"; +case 217:return a.a="⋩","OP";case 218:return a.a="⪶","OP";case 219:return a.a="⪺","OP";case 220:return a.a="⪰","OP";case 221:return a.a="≽","OP";case 222:return a.a="⪸","OP";case 223:return a.a="≻","OP";case 224:return"SUBSTACK";case 225:return a.a="⫋","OP";case 226:return a.a="⊊","OP";case 227:return a.a="⫅","OP";case 228:return a.a="⊆","OP";case 229:return a.a="⋐","OP";case 230:return a.a="⊂","OP";case 231:return this.pushState("TEXTARG"),"STATUSLINE";case 232:return a.a="⋆","OP";case 233:return"OVERSET"; +case 234:return a.a="⫽","OP";case 235:return a.a="□","OP";case 236:return a.a="⊒","OP";case 237:return a.a="⊐","OP";case 238:return a.a="⊑","OP";case 239:return a.a="⊏","OP";case 240:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),"SQRT";case 241:return a.a="⊔","OP";case 242:return a.a="⊓","OP";case 243:return a.a="∢","OP";case 244:return a.a="♠","OP";case 245:return this.pushState("TEXTARG"),this.pushState("TEXTARG"),this.pushState("TEXTARG"),"SPACE";case 246:return a.a="⌣","OP";case 247:return a.a= +"⌣","OP";case 248:return a.a="∖","OP";case 249:return a.a="⌢","OP";case 250:return"SLASH";case 251:return a.a="≃","OP";case 252:return a.a="∼","OP";case 253:return a.a="Σ","AIUG";case 254:return a.a="σ","AILG";case 255:return a.a="⧢","OP";case 256:return a.a="∥","OP";case 257:return a.a="∣","OP";case 258:return a.a="♯","OP";case 259:return a.a="∖","OP";case 260:return a.a="⤭","OP";case 261:return a.a="⇘","OPS";case 262:return a.a="↘","OPS";case 263:return a.a="⇘","OPS";case 264:return a.a="↘","OPS"; +case 265:return"SCRIPTSIZE";case 266:return"SCRIPTSCRIPTSIZE";case 267:return a.a="⋊","OP";case 268:return a.a="↱","OPS";case 269:return a.a="⇛","OPS";case 270:return a.a="⟫","OPFS";case 271:return a.a="’","OPF";case 272:return this.j("TEXTARG"),"ROWSPAN";case 273:return"ROWOPTS";case 274:return this.pushState("TEXTARG"),"ROWLINES";case 275:return this.j("TEXTARG"),"ROWALIGN";case 276:return"ROOT";case 277:return a.a="⎱","OP";case 278:return a.a="≓","OP";case 279:return a.a="⟲","OP";case 280:return a.a= +"⋌","OP";case 281:return a.a="↝","OPS";case 282:return a.a="⇉","OPS";case 283:return a.a="⇌","OPS";case 284:return a.a="⇄","OPS";case 285:return a.a="⇀","OPS";case 286:return a.a="⇁","OPS";case 287:return a.a="⇾","OPS";case 288:return a.a="↣","OPS";case 289:return a.a="⇒","OPS";case 290:return a.a="→","OPS";case 291:return"RIGHT";case 292:return a.a="Ρ","AIUG";case 293:return a.a="ρ","AILG";case 294:return a.a="⊳","OP";case 295:return a.a="⌋","OPFS";case 296:return a.a="ℜ","A";case 297:return a.a= +"⤰","OP";case 298:return a.a="⤫","OP";case 299:return a.a="⌉","OPFS";case 300:return a.a="]","OPFS";case 301:return a.a="}","OPFS";case 302:return a.a="⟩","OPFS";case 303:return a.a="⟩","OPFS";case 304:return a.a="≟","OP";case 305:return a.a="⨌","OP";case 306:return"QUAD";case 307:return"QQUAD";case 308:return a.a="▪","OP";case 309:return a.a="Ψ","AIUG";case 310:return a.a="ψ","AILG";case 311:return a.a="∝","OP";case 312:return a.a="∏","OPM";case 313:return a.a="∏","OPM";case 314:return a.a="′","OPP"; +case 315:return a.a="≾","OP";case 316:return a.a="⋨","OP";case 317:return a.a="⪵","OP";case 318:return a.a="⪹","OP";case 319:return a.a="⪯","OP";case 320:return a.a="≼","OP";case 321:return a.a="⪷","OP";case 322:return a.a="≺","OP";case 323:return"PMOD";case 324:return a.a="±","OP";case 325:return a.a="⨥","OP";case 326:return a.a="⊞","OP";case 327:return a.a="⋔","OP";case 328:return a.a="Π","AIUG";case 329:return a.a="π","AILG";case 330:return a.a="Φ","AIUG";case 331:return a.a="ϕ","AILG";case 332:return"PHANTOM"; +case 333:return a.a="⫫","OP";case 334:return a.a="⊥","OP";case 335:return a.a="⪣","OP";case 336:return a.a="∂","OP";case 337:return a.a="⅋","OP";case 338:return a.a="∥","OP";case 339:return this.pushState("TEXTARG"),"PADDING";case 340:return"OVERSET";case 341:return a.a="¯","ACCENT";case 342:return"OVERBRACE";case 343:return"TEXOVER";case 344:return a.a="⨴","OP";case 345:return a.a="⊗","OP";case 346:return a.a="⊘","OP";case 347:return"OPS";case 348:return"OPP";case 349:return"OPM";case 350:return a.a= +"⨭","OP";case 351:return a.a="⊕","OP";case 352:return"OPFS";case 353:return"OPF";case 354:return this.j("TEXTARG"),"OPERATORNAME";case 355:return"OP";case 356:return a.a="⊖","OP";case 357:return a.a="ℴ","A";case 358:return a.a="Ω","AIUG";case 359:return a.a="ω","AILG";case 360:return a.a="∮","OP";case 361:return a.a="∯","OP";case 362:return a.a="∰","OP";case 363:return a.a="⊙","OP";case 364:return a.a="⊝","OP";case 365:return a.a="⦸","OP";case 366:return a.a="⤲","OP";case 367:return a.a="⇖","OPS"; +case 368:return a.a="↖","OPS";case 369:return a.a="⇖","OPS";case 370:return a.a="↖","OPS";case 371:return a.a="⊯","OP";case 372:return a.a="⊮","OP";case 373:return a.a="⊭","OP";case 374:return a.a="⊬","OP";case 375:return"NUM";case 376:return a.a="Ν","AIUG";case 377:return a.a="ν","AILG";case 378:return a.a="⋭","OP";case 379:return a.a="⋫","OP";case 380:return a.a="⋬","OP";case 381:return a.a="⋪","OP";case 382:return a.a="⊉","OP";case 383:return a.a="⊅","OP";case 384:return a.a="≿̸","OP";case 385:return a.a= +"⪰̸","OP";case 386:return a.a="⊁","OP";case 387:return a.a="⊈","OP";case 388:return a.a="⊈","OP";case 389:return a.a="⊄","OP";case 390:return a.a="≄","OP";case 391:return a.a="≁","OP";case 392:return a.a="∦","OP";case 393:return a.a="∤","OP";case 394:return a.a="⇏","OP";case 395:return a.a="↛","OP";case 396:return a.a="⪯̸","OP";case 397:return a.a="⊀","OP";case 398:return a.a="∦","OP";case 399:return a.a="∌","OP";case 400:return a.a="∉","OP";case 401:return a.a="¬","OP";case 402:return a.a="∤","OP"; +case 403:return a.a="≮","OP";case 404:return a.a="⩽̸","OP";case 405:return a.a="⩽̸","OP";case 406:return a.a="≰","OP";case 407:return a.a="⇎","OP";case 408:return a.a="↮","OP";case 409:return a.a="⇍","OP";case 410:return a.a="↚","OP";case 411:return a.a="∋","OP";case 412:return a.a="≯","OP";case 413:return a.a="⩾̸","OP";case 414:return a.a="⩾̸","OP";case 415:return a.a="≱","OP";case 416:return a.a="∄","OP";case 417:return a.a="≢","OP";case 418:return a.a="≂̸","OP";case 419:return a.a="≠","OP";case 420:return a.a= +"⤮","OP";case 421:return a.a="⤱","OP";case 422:return"NEGTHICKSPACE";case 423:return"NEGSPACE";case 424:return"NEGMEDSPACE";case 425:return a.a="¬","OP";case 426:return a.a="⇗","OPS";case 427:return a.a="↗","OPS";case 428:return a.a="⇗","OPS";case 429:return a.a="↗","OPS";case 430:return a.a="≠","OP";case 431:return a.a="≇","OP";case 432:return a.a="≎̸","OP";case 433:return a.a="≏̸","OP";case 434:return a.a="♮","OP";case 435:return a.a="≉","OP";case 436:return a.a="∇","OP";case 437:return"MULTI"; +case 438:return a.a="⊸","OP";case 439:return a.a="Μ","AIUG";case 440:return a.a="μ","AILG";case 441:return this.j("TEXTARG"),"MTEXT";case 442:return this.pushState("TEXTARG"),this.pushState("TEXTOPTARG"),this.pushState("TRYOPTARG"),this.pushState("TEXTOPTARG"),this.pushState("TRYOPTARG"),"MS";case 443:return a.a="∓","OP";case 444:return a.a="⊧","OP";case 445:return a.a="mod","MO";case 446:return this.pushState("TEXTARG"),"MO";case 447:return this.pushState("TEXTARG"),"MN";case 448:return a.a="⫛", +"OP";case 449:return a.a="⨪","OP";case 450:return a.a="⊟","OP";case 451:return a.a="−","OP";case 452:return a.a=a.a.slice(1),"FM";case 453:return a.a="∣","OP";case 454:return this.pushState("TEXTARG"),"MI";case 455:return a.a="℧","A";case 456:return a.a="℧","A";case 457:return"MEDSPACE";case 458:return a.a="∡","OP";case 459:return"MATHTT";case 460:return"MATHSF";case 461:return"MATHSCR";case 462:return"MATHRM";case 463:return"MATHRLAP";case 464:return this.j("TEXTARG"),"MATHREL";case 465:return this.pushState("TEXTOPTARG"), +this.pushState("TRYOPTARG"),this.pushState("TEXTOPTARG"),this.pushState("TRYOPTARG"),this.pushState("TEXTARG"),"MATHRAISEBOX";case 466:return this.j("TEXTARG"),"MATHOP";case 467:return"MATHIT";case 468:return"MATHLLAP";case 469:return"MATHIT";case 470:return"MATHFRAK";case 471:return"MATHFRAK";case 472:return"MATHCLAP";case 473:return"MATHSCR";case 474:return"MATHBSCR";case 475:return"MATHBIT";case 476:return this.j("TEXTARG"),"MATHBIN";case 477:return"MATHBF";case 478:return"MATHBSCR";case 479:return"MATHBB"; +case 480:return a.a="⤇","OP";case 481:return a.a="↦","OPS";case 482:return a.a="⤆","OP";case 483:return a.a="↦","OPS";case 484:return a.a="≨︀","OP";case 485:return a.a="≨︀","OP";case 486:return a.a="⋉","OP";case 487:return a.a="<","OP";case 488:return a.a="↰","OPS";case 489:return a.a="‘","OPF";case 490:return a.a="◊","OP";case 491:return a.a="⨜","OP";case 492:return a.a="↬","OPS";case 493:return a.a="↫","OPS";case 494:return a.a="⟹","OPS";case 495:return a.a="⟶","OPS";case 496:return a.a="⟼","OPS"; +case 497:return a.a="⟺","OPS";case 498:return a.a="⟷","OPS";case 499:return a.a="⟸","OPS";case 500:return a.a="⟵","OPS";case 501:return a.a="⋦","OP";case 502:return a.a="≨","OP";case 503:return a.a="⪇","OP";case 504:return a.a="⪉","OP";case 505:return a.a="⎰","OP";case 506:return a.a="⋘","OP";case 507:return a.a="⇚","OPS";case 508:return a.a="⟪","OPFS";case 509:return a.a="≪","OP";case 510:return a.a="⊲","OP";case 511:return a.a="⌊","OPFS";case 512:return a.a="≲","OP";case 513:return a.a="≶","OP"; +case 514:return a.a="⪋","OP";case 515:return a.a="⋚","OP";case 516:return a.a="⋖","OP";case 517:return a.a="⪅","OP";case 518:return a.a="<","OP";case 519:return a.a="⩽","OP";case 520:return a.a="≦","OP";case 521:return a.a="≤","OP";case 522:return a.a="⟳","OP";case 523:return a.a="⋋","OP";case 524:return a.a="↜","OPS";case 525:return a.a="↭","OPS";case 526:return a.a="⇋","OPS";case 527:return a.a="⇿","OPS";case 528:return a.a="⇆","OPS";case 529:return a.a="⇔","OPS";case 530:return a.a="↔","OPS";case 531:return a.a= +"⇇","OPS";case 532:return a.a="↼","OPS";case 533:return a.a="↽","OPS";case 534:return a.a="⇽","OPS";case 535:return a.a="↢","OPS";case 536:return a.a="⇐","OPS";case 537:return a.a="←","OPS";case 538:return"LEFT";case 539:return a.a="≤","OP";case 540:return a.a="…","OP";case 541:return a.a="⌈","OPFS";case 542:return a.a="[","OPFS";case 543:return a.a="{","OPFS";case 544:return a.a="⟨","OPFS";case 545:return a.a="⟨","OPFS";case 546:return a.a="Λ","AIUG";case 547:return a.a="λ","AILG";case 548:return a.a= +"∻","OP";case 549:return a.a="Κ","AIUG";case 550:return a.a="κ","AILG";case 551:return a.a="ȷ","AILL";case 552:return this.pushState("TEXTARG"),"MN";case 553:return a.a="Ι","AIUG";case 554:return a.a="ι","AILG";case 555:return a.a="⅋","OP";case 556:return a.a="⨘","OP";case 557:return a.a="⨽","OP";case 558:return a.a="⨼","OP";case 559:return a.a="⋂","OPM";case 560:return a.a="∩","OP";case 561:return a.a="⫴","OP";case 562:return a.a="⊺","OP";case 563:return a.a="∫","OP";case 564:return a.a="⨚","OP"; +case 565:return a.a="⨙","OP";case 566:return a.a="⨎","OP";case 567:return a.a="⨍","OP";case 568:return a.a="∫","OP";case 569:return a.a="∞","NUM";case 570:return a.a="∞","NUM";case 571:return a.a=a.a.slice(1),"FM";case 572:return a.a="∊","OP";case 573:return a.a="⇒","OPS";case 574:return a.a="⇐","OPS";case 575:return a.a="ı","AILL";case 576:return a.a="ℑ","A";case 577:return a.a="∬","OP";case 578:return a.a="∭","OP";case 579:return a.a="⨌","OP";case 580:return a.a="⟺","OPS";case 581:return a.a="ℏ", +"A";case 582:return this.pushState("TEXTARG"),"HREF";case 583:return a.a="↪","OPS";case 584:return a.a="↩","OPS";case 585:return a.a="⤦","OP";case 586:return a.a="⤥","OP";case 587:return a.a="♡","OP";case 588:return a.a="ℏ","A";case 589:return a.a="^","ACCENTNS";case 590:return a.a="≩︀","OP";case 591:return a.a="≩︀","OP";case 592:return a.a="≳","OP";case 593:return a.a="≷","OP";case 594:return a.a="⪌","OP";case 595:return a.a="⋛","OP";case 596:return a.a="⋗","OP";case 597:return a.a="⪆","OP";case 598:return a.a= +">","OP";case 599:return a.a=">","OP";case 600:return a.a="⋧","OP";case 601:return a.a="≩","OP";case 602:return a.a="⪈","OP";case 603:return a.a="⪊","OP";case 604:return a.a="ℷ","A";case 605:return a.a="⋙","OP";case 606:return a.a="≫","OP";case 607:return a.a="⩾","OP";case 608:return a.a="≧","OP";case 609:return a.a="≥","OP";case 610:return a.a="≥","OP";case 611:return a.a="Γ","AIUG";case 612:return a.a="γ","AILG";case 613:return a.a="⌢","OP";case 614:return this.pushState("TEXTARG"),"FRAME";case 615:return"FRAC"; +case 616:return a.a="⫝","OP";case 617:return a.a="⫝̸","OP";case 618:return a.a="∀","OP";case 619:return a.a="♭","OP";case 620:return a.a="⤬","OP";case 621:return a.a="⤯","OP";case 622:return a.a="≒","OP";case 623:return a.a="∃","OP";case 624:return a.a="ð","A";case 625:return a.a="ð","A";case 626:return a.a="Η","AIUG";case 627:return a.a="η","AILG";case 628:return a.a="≡","OP";case 629:return this.pushState("TEXTARG"),"EQROWS";case 630:return this.pushState("TEXTARG"),"EQCOLS";case 631:return a.a= +"⪕","OP";case 632:return a.a="⪖","OP";case 633:return a.a="≂","OP";case 634:return a.a="=∷","OP";case 635:return a.a="≕","OP";case 636:return a.a="−∷","OP";case 637:return a.a="=∷","OP";case 638:return a.a="=∷","OP";case 639:return a.a="=∷","OP";case 640:return a.a="≕","OP";case 641:return a.a="≖","OP";case 642:return a.a="ϵ","AILG";case 643:return"EVVMATRIX";case 644:return"EVMATRIX";case 645:return"ETOGGLE";case 646:return"EALIGNED";case 647:return"ESMALLMATRIX";case 648:return"EPMATRIX";case 649:return"EMATRIX"; +case 650:return"EGATHERED";case 651:return"ECASES";case 652:return"EBBMATRIX";case 653:return"EBMATRIX";case 654:return"EARRAY";case 655:return"EALIGNED";case 656:return a.a="∅","A";case 657:return a.a="∅","A";case 658:return a.a="↪","OPS";case 659:return a.a="ℓ","A";case 660:return a.a="↕","OPS";case 661:return a.a="⧟","OP";case 662:return a.a="⤐","OPS";case 663:return a.a="↕","OPS";case 664:return a.a="⇂","OPS";case 665:return a.a="⇃","OPS";case 666:return a.a="⇊","OPS";case 667:return a.a="⇓", +"OPS";case 668:return a.a="↓","OPS";case 669:return a.a="∬","OP";case 670:return a.a="⩞","OP";case 671:return a.a="⌆","OP";case 672:return a.a="…","OP";case 673:return a.a="∔","OP";case 674:return a.a="∸","OP";case 675:return a.a="≑","OP";case 676:return a.a="≑","OP";case 677:return a.a="≐","OP";case 678:return a.a="˙","ACCENT";case 679:return a.a="⋇","OP";case 680:return a.a="÷","OP";case 681:return"DISPLAYSTYLE";case 682:return a.a="⨈","OPM";case 683:return a.a="ϝ","A";case 684:return a.a="♢","OP"; +case 685:return a.a="⋄","OP";case 686:return a.a="⋄","OP";case 687:return a.a=a.a.slice(1),"FM";case 688:return a.a="Δ","AIUG";case 689:return a.a="δ","AILG";case 690:return a.a="∇","OP";case 691:return a.a="°","OP";case 692:return a.a="⤋","OPS";case 693:return a.a="⩷","OP";case 694:return a.a="⋱","OP";case 695:return a.a="̈","ACCENT";case 696:return a.a="⃛","OP";case 697:return a.a="⃛","ACCENT";case 698:return a.a="⃜","OP";case 699:return a.a="⃜","ACCENT";case 700:return a.a="‡","OP";case 701:return a.a= +"∷","OP";case 702:return a.a="⤏","OPS";case 703:return a.a="⫤","OP";case 704:return a.a="⫣","OP";case 705:return a.a="⊣","OP";case 706:return a.a="⤏","OPS";case 707:return a.a="⤎","OPS";case 708:return a.a="↓","OPS";case 709:return a.a="ℸ","A";case 710:return a.a="†","OP";case 711:return a.a="↷","OP";case 712:return a.a="↶","OP";case 713:return a.a="⤻","OP";case 714:return a.a="⋏","OP";case 715:return a.a="⋎","OP";case 716:return a.a="⋟","OP";case 717:return a.a="⋞","OP";case 718:return a.a="⊍","OP"; +case 719:return a.a="⋓","OP";case 720:return a.a="∪","OP";case 721:return a.a="∐","OPM";case 722:return a.a="∐","OPM";case 723:return a.a="∮","OP";case 724:return a.a="⨇","OPM";case 725:return a.a="∮","OP";case 726:return a.a="≅","OP";case 727:return a.a="∁","OP";case 728:return this.j("TEXTARG"),"COLSPAN";case 729:return this.pushState("TEXTARG"),"COLOR";case 730:return a.a="∷∼","OP";case 731:return a.a="∶∼","OP";case 732:return a.a="⩴","OP";case 733:return a.a="≔","OP";case 734:return a.a="∷−", +"OP";case 735:return a.a="≔","OP";case 736:return a.a="∷≈","OP";case 737:return a.a="∶≈","OP";case 738:return a.a="∷","OP";case 739:return a.a=":","OP";case 740:return this.pushState("TEXTARG"),"COLLINES";case 741:return this.pushState("TEXTARG"),"COLLAYOUT";case 742:return this.j("TEXTARG"),"COLALIGN";case 743:return a.a="♣","OP";case 744:return a.a="¯","ACCENT";case 745:return a.a="⊝","OP";case 746:return a.a="⊚","OP";case 747:return a.a="⊛","OP";case 748:return a.a="⥁","OP";case 749:return a.a= +"⥀","OP";case 750:return a.a="≗","OP";case 751:return a.a="∘","OP";case 752:return"TEXCHOOSE";case 753:return a.a="χ","AILG";case 754:return a.a="ˇ","ACCENTNS";case 755:return"CELLOPTS";case 756:return a.a="⋯","OP";case 757:return a.a="·","OP";case 758:return a.a="⋅","OP";case 759:return a.a="⋒","OP";case 760:return a.a="∩","OP";case 761:return a.a="⪮","OP";case 762:return a.a="≎","OP";case 763:return a.a="≏","OP";case 764:return a.a="•","OP";case 765:return a.a="⨲","OP";case 766:return a.a="⊠","OP"; +case 767:return a.a="⊞","OP";case 768:return a.a="⊟","OP";case 769:return"BOXED";case 770:return a.a="⊡","OP";case 771:return a.a="⧄","OP";case 772:return a.a="⧇","OP";case 773:return a.a="⧅","OP";case 774:return a.a="⧆","OP";case 775:return a.a="□","OP";case 776:return a.a="⋈","OP";case 777:return a.a="⊥","OP";case 778:return a.a="⊥","OP";case 779:return"MATHBF";case 780:return a.a="▸","OP";case 781:return a.a="◂","OP";case 782:return a.a="▾","OP";case 783:return a.a="▴","OP";case 784:return a.a= +"■","OP";case 785:return a.a="⧫","OP";case 786:return a.a="⤍","OPS";case 787:return"BINOM";case 788:return a.a="⋀","OPM";case 789:return a.a="⋁","OPM";case 790:return a.a="⨄","OPM";case 791:return a.a="△","OP";case 792:return a.a="▽","OP";case 793:return a.a="⨉","OPM";case 794:return a.a="★","OP";case 795:return a.a="⨆","OPM";case 796:return a.a="⨅","OPM";case 797:return"BBIG";case 798:return"BIG";case 799:return a.a="⨂","OPM";case 800:return a.a="⨁","OPM";case 801:return a.a="⨀","OPM";case 802:return"BBIGL"; +case 803:return"BIGL";case 804:return a.a="⫼","OPM";case 805:return"BBIGG";case 806:return"BIGG";case 807:return"BBIGGL";case 808:return"BIGGL";case 809:return"BBIGG";case 810:return"BIGG";case 811:return a.a="⨃","OPM";case 812:return a.a="⋃","OPM";case 813:return a.a="○","OP";case 814:return a.a="⋂","OPM";case 815:return"BBIG";case 816:return"BIG";case 817:return this.pushState("TEXTARG"),"BGCOLOR";case 818:return a.a="≬","OP";case 819:return a.a="ℶ","A";case 820:return a.a="Β","AIUG";case 821:return a.a= +"β","AILG";case 822:return"BVVMATRIX";case 823:return"BVMATRIX";case 824:return"BTOGGLE";case 825:return"BALIGNED";case 826:return"BSMALLMATRIX";case 827:return"BPMATRIX";case 828:return"BMATRIX";case 829:return"BGATHERED";case 830:return"BCASES";case 831:return"BBBMATRIX";case 832:return"BBMATRIX";case 833:return this.pushState("TEXTARG"),this.pushState("TEXTOPTARG"),this.pushState("TRYOPTARG"),"BARRAY";case 834:return"BALIGNED";case 835:return a.a="∵","OP";case 836:return a.a="ℿ","A";case 837:return a.a= +"⌅","OP";case 838:return a.a="¯","ACCENTNS";case 839:return a.a="\\","OP";case 840:return a.a="⋍","OP";case 841:return a.a="∽","OP";case 842:return a.a="‵","OPP";case 843:return a.a="϶","OP";case 844:return"TEXATOP";case 845:return a.a="≍","OP";case 846:return a.a="∗","OP";case 847:return"ARRAYOPTS";case 848:return"ARRAY";case 849:return a.a=a.a.slice(1),"F";case 850:return a.a="≊","OP";case 851:return a.a="≈","OP";case 852:return a.a="∠","OP";case 853:return a.a="⨿","OP";case 854:return a.a="Α", +"AIUG";case 855:return a.a="α","AILG";case 856:return this.pushState("TEXTARG"),"ALIGN";case 857:return a.a="ℵ","A";case 858:return"AIUL";case 859:return"AIUG";case 860:return"AILL";case 861:return"AILG";case 862:return a.a="⋰","OP";case 863:return a.a="Å","A";case 864:return"A";case 865:return a.a="$","A";case 866:return a.a="}","OPFS";case 867:return a.a="‖","OPFS";case 868:return a.a="{","OPFS";case 869:return"THICKSPACE";case 870:return"MEDSPACE";case 871:return"THINSPACE";case 872:return a.a= +"&","A";case 873:return a.a="%","A";case 874:return a.a="#","OP";case 875:return"NEGSPACE";case 876:return a.a="−","OP";case 877:return a.a="⁗","OPP";case 878:return a.a="‴","OPP";case 879:return a.a="″","OPP";case 880:return a.a="′","OPP";case 881:return"HIGH_SURROGATE";case 882:return"LOW_SURROGATE";case 883:return"BMP_CHARACTER"}},rules:[/^(?:.)/,/^(?:\$\$|\\\[|\$|\\\()/,/^(?:$)/,/^(?:\\[$\\])/,/^(?:[<&>])/,/^(?:[^])/,/^(?:\s*\[)/,/^(?:.)/,/^(?:([^\\\]]|(\\[\\\]]))+)/,/^(?:\])/,/^(?:\s*\{)/,/^(?:([^\\\}]|(\\[\\\}]))+)/, +/^(?:\})/,/^(?:\])/,/^(?:\s+)/,/^(?:\$\$|\\\]|\$|\\\))/,/^(?:\{)/,/^(?:\})/,/^(?:\^)/,/^(?:_)/,/^(?:\.)/,/^(?:&)/,/^(?:\\\\)/,/^(?:[0-9]+(?:\.[0-9]+)?|[\u0660-\u0669]+(?:\u066B[\u0660-\u0669]+)?|(?:\uD835[\uDFCE-\uDFD7])+|(?:\uD835[\uDFD8-\uDFE1])+|(?:\uD835[\uDFE2-\uDFEB])+|(?:\uD835[\uDFEC-\uDFF5])+|(?:\uD835[\uDFF6-\uDFFF])+)/,/^(?:[a-zA-Z]+)/,/^(?:\\Zeta)/,/^(?:\\zeta)/,/^(?:\\xrightleftharpoons)/,/^(?:\\xRightarrow)/,/^(?:\\xrightarrow)/,/^(?:\\xmapsto)/,/^(?:\\xleftrightharpoons)/,/^(?:\\xLeftrightarrow)/, +/^(?:\\xleftrightarrow)/,/^(?:\\xLeftarrow)/,/^(?:\\xleftarrow)/,/^(?:\\Xi)/,/^(?:\\xi)/,/^(?:\\xhookrightarrow)/,/^(?:\\xhookleftarrow)/,/^(?:\\wr)/,/^(?:\\wp)/,/^(?:\\widevec)/,/^(?:\\widetilde)/,/^(?:\\widehat)/,/^(?:\\widecheck)/,/^(?:\\widebar)/,/^(?:\\wedgeq)/,/^(?:\\Wedge)/,/^(?:\\wedge)/,/^(?:\\Vvert)/,/^(?:\\Vvdash)/,/^(?:\\Vert)/,/^(?:\\vert)/,/^(?:\\veebar)/,/^(?:\\Vee)/,/^(?:\\vee)/,/^(?:\\vec)/,/^(?:\\vdots)/,/^(?:\\VDash)/,/^(?:\\Vdash)/,/^(?:\\vDash)/,/^(?:\\vdash)/,/^(?:\\Vbar)/,/^(?:\\vartriangleright)/, +/^(?:\\vartriangleleft)/,/^(?:\\vartriangle)/,/^(?:\\vartheta)/,/^(?:\\varsupsetneqq)/,/^(?:\\varsupsetneq)/,/^(?:\\varsubsetneqq)/,/^(?:\\varsubsetneqq)/,/^(?:\\varsubsetneq)/,/^(?:\\varsigma)/,/^(?:\\varrho)/,/^(?:\\varpropto)/,/^(?:\\varpi)/,/^(?:\\varphi)/,/^(?:\\varnothing)/,/^(?:\\varkappa)/,/^(?:\\varepsilon)/,/^(?:\\Uuparrow)/,/^(?:\\upuparrows)/,/^(?:\\Upsilon)/,/^(?:\\upsilon)/,/^(?:\\Upsi)/,/^(?:\\uplus)/,/^(?:\\upint)/,/^(?:\\upharpoonright)/,/^(?:\\upharpoonleft)/,/^(?:\\Updownarrow)/, +/^(?:\\updownarrow)/,/^(?:\\updarr)/,/^(?:\\Uparrow)/,/^(?:\\uparrow)/,/^(?:\\uparr)/,/^(?:\\unrhd)/,/^(?:\\unlhd)/,/^(?:\\Union)/,/^(?:\\union)/,/^(?:\\underset)/,/^(?:\\underoverset)/,/^(?:\\underline)/,/^(?:\\underbrace)/,/^(?:\\udots)/,/^(?:\u2ADD\u0338)/,/^(?:\u2ACC\uFE00)/,/^(?:\u2ACB\uFE00)/,/^(?:\u2AB0\u0338)/,/^(?:\u2AAF\u0338)/,/^(?:\u2AA2\u0338)/,/^(?:\u2AA1\u0338)/,/^(?:\u2A7E\u0338)/,/^(?:\u2A7D\u0338)/,/^(?:\u29D0\u0338)/,/^(?:\u29CF\u0338)/,/^(?:\u2290\u0338)/,/^(?:\u228F\u0338)/,/^(?:\u228B\uFE00)/, +/^(?:\u228A\uFE00)/,/^(?:\u2283\u20D2)/,/^(?:\u2282\u20D2)/,/^(?:\u227F\u0338)/,/^(?:\u226B\u0338)/,/^(?:\u226A\u0338)/,/^(?:\u2269\uFE00)/,/^(?:\u2268\uFE00)/,/^(?:\u2266\u0338)/,/^(?:\u224F\u0338)/,/^(?:\u224E\u0338)/,/^(?:\u2242\u0338)/,/^(?:\u223D\u0331)/,/^(?:\u2237\u2248)/,/^(?:\u2237\u223C)/,/^(?:\u2237\u2212)/,/^(?:\u2236\u2248)/,/^(?:\u2236\u223C)/,/^(?:\u2212\u2237)/,/^(?:\u007C\u007C\u007C)/,/^(?:\u007C\u007C)/,/^(?:\u003E\u003D)/,/^(?:\u003D\u2237)/,/^(?:\u003D\u2237)/,/^(?:\u003D\u003D)/, +/^(?:\u003C\u003E)/,/^(?:\u003C\u003D)/,/^(?:\u003A\u003D)/,/^(?:\u002F\u003D)/,/^(?:\u002F\u002F)/,/^(?:\u002E\u002E\u002E)/,/^(?:\u002E\u002E)/,/^(?:\u002D\u003E)/,/^(?:\u002D\u003D)/,/^(?:\u002D\u002D)/,/^(?:\u002B\u003D)/,/^(?:\u002B\u002B)/,/^(?:\u002A\u003D)/,/^(?:\u002A\u002A)/,/^(?:\u0026\u0026)/,/^(?:\u0021\u003D)/,/^(?:\u0021\u0021)/,/^(?:\\twoheadrightarrowtail)/,/^(?:\\twoheadrightarrow)/,/^(?:\\twoheadleftarrow)/,/^(?:\\tripleintegral)/,/^(?:\\trianglerighteq)/,/^(?:\\triangleright)/, +/^(?:\\triangleq)/,/^(?:\\trianglelefteq)/,/^(?:\\triangleleft)/,/^(?:\\triangledown)/,/^(?:\\triangle)/,/^(?:\\towa)/,/^(?:\\tosa)/,/^(?:\\top)/,/^(?:\\tooltip)/,/^(?:\\tona)/,/^(?:\\toggle)/,/^(?:\\toea)/,/^(?:\\to)/,/^(?:\\timesb)/,/^(?:\\times)/,/^(?:\\tilde)/,/^(?:\\thinspace)/,/^(?:\\thickspace)/,/^(?:\\thicksim)/,/^(?:\\thickapprox)/,/^(?:\\Theta)/,/^(?:\\theta)/,/^(?:\\therefore)/,/^(?:\\tfrac)/,/^(?:\\textstyle)/,/^(?:\\textsize)/,/^(?:\\textquotedblright)/,/^(?:\\textquotedblleft)/,/^(?:\\textasciitilde)/, +/^(?:\\textasciigrave)/,/^(?:\\textasciicircumflex)/,/^(?:\\textasciiacute)/,/^(?:\\text)/,/^(?:\\tensor)/,/^(?:\\tbinom)/,/^(?:\\Tau)/,/^(?:\\tau)/,/^(?:\\swArrow)/,/^(?:\\swarrow)/,/^(?:\\swArr)/,/^(?:\\swarr)/,/^(?:\\surd)/,/^(?:\\supsetneqq)/,/^(?:\\supsetneq)/,/^(?:\\supseteqq)/,/^(?:\\supseteq)/,/^(?:\\Supset)/,/^(?:\\supset)/,/^(?:\\sum)/,/^(?:\\succsim)/,/^(?:\\succnsim)/,/^(?:\\succneqq)/,/^(?:\\succnapprox)/,/^(?:\\succeq)/,/^(?:\\succcurlyeq)/,/^(?:\\succapprox)/,/^(?:\\succ)/,/^(?:\\substack)/, +/^(?:\\subsetneqq)/,/^(?:\\subsetneq)/,/^(?:\\subseteqq)/,/^(?:\\subseteq)/,/^(?:\\Subset)/,/^(?:\\subset)/,/^(?:\\statusline)/,/^(?:\\star)/,/^(?:\\stackrel)/,/^(?:\\sslash)/,/^(?:\\square)/,/^(?:\\sqsupseteq)/,/^(?:\\sqsupset)/,/^(?:\\sqsubseteq)/,/^(?:\\sqsubset)/,/^(?:\\sqrt)/,/^(?:\\sqcup)/,/^(?:\\sqcap)/,/^(?:\\sphericalangle)/,/^(?:\\spadesuit)/,/^(?:\\space)/,/^(?:\\smile)/,/^(?:\\smallsmile)/,/^(?:\\smallsetminus)/,/^(?:\\smallfrown)/,/^(?:\\slash)/,/^(?:\\simeq)/,/^(?:\\sim)/,/^(?:\\Sigma)/, +/^(?:\\sigma)/,/^(?:\\shuffle)/,/^(?:\\shortparallel)/,/^(?:\\shortmid)/,/^(?:\\sharp)/,/^(?:\\setminus)/,/^(?:\\seovnearrow)/,/^(?:\\seArrow)/,/^(?:\\searrow)/,/^(?:\\seArr)/,/^(?:\\searr)/,/^(?:\\scriptsize)/,/^(?:\\scriptscriptsize)/,/^(?:\\rtimes)/,/^(?:\\Rsh)/,/^(?:\\Rrightarrow)/,/^(?:\\rrangle)/,/^(?:\\rq)/,/^(?:\\rowspan)/,/^(?:\\rowopts)/,/^(?:\\rowlines)/,/^(?:\\rowalign)/,/^(?:\\root)/,/^(?:\\rmoustache)/,/^(?:\\risingdotseq)/,/^(?:\\righttoleftarrow)/,/^(?:\\rightthreetimes)/,/^(?:\\rightsquigarrow)/, +/^(?:\\rightrightarrows)/,/^(?:\\rightleftharpoons)/,/^(?:\\rightleftarrows)/,/^(?:\\rightharpoonup)/,/^(?:\\rightharpoondown)/,/^(?:\\rightarrowtriangle)/,/^(?:\\rightarrowtail)/,/^(?:\\Rightarrow)/,/^(?:\\rightarrow)/,/^(?:\\right)/,/^(?:\\Rho)/,/^(?:\\rho)/,/^(?:\\rhd)/,/^(?:\\rfloor)/,/^(?:\\Re)/,/^(?:\\rdiagovsearrow)/,/^(?:\\rdiagovfdiag)/,/^(?:\\rceil)/,/^(?:\\rbrack)/,/^(?:\\rbrace)/,/^(?:\\rangle)/,/^(?:\\rang)/,/^(?:\\questeq)/,/^(?:\\quadrupleintegral)/,/^(?:\\quad)/,/^(?:\\qquad)/,/^(?:\\qed)/, +/^(?:\\Psi)/,/^(?:\\psi)/,/^(?:\\propto)/,/^(?:\\product)/,/^(?:\\prod)/,/^(?:\\prime)/,/^(?:\\precsim)/,/^(?:\\precnsim)/,/^(?:\\precneqq)/,/^(?:\\precnapprox)/,/^(?:\\preceq)/,/^(?:\\preccurlyeq)/,/^(?:\\precapprox)/,/^(?:\\prec)/,/^(?:\\pmod)/,/^(?:\\pm)/,/^(?:\\plusdot)/,/^(?:\\plusb)/,/^(?:\\pitchfork)/,/^(?:\\Pi)/,/^(?:\\pi)/,/^(?:\\Phi)/,/^(?:\\phi)/,/^(?:\\phantom)/,/^(?:\\Perp)/,/^(?:\\perp)/,/^(?:\\partialmeetcontraction)/,/^(?:\\partial)/,/^(?:\\parr)/,/^(?:\\parallel)/,/^(?:\\padding)/, +/^(?:\\overset)/,/^(?:\\overline)/,/^(?:\\overbrace)/,/^(?:\\over)/,/^(?:\\Otimes)/,/^(?:\\otimes)/,/^(?:\\oslash)/,/^(?:[\u007E\u00AF\u02C6\u02C7\u02C9\u02CD\u02DC\u02F7\u0302\u203E\u2044\u2190-\u2199\u219C-\u21AD\u21AF-\u21B5\u21B9\u21BC-\u21CC\u21D0-\u21DD\u21E0-\u21F0\u21F3\u21F5\u21F6\u21FD-\u21FF\u2215\u221A\u23B4\u23B5\u23DC-\u23E1\u27F0\u27F1\u27F5-\u27FF\u290A-\u2910\u2912\u2913\u2921\u2922\u294E-\u2961\u296E\u296F\u2B45\u2B46])/,/^(?:[\u2032-\u2035\u2057])/,/^(?:[\u220F-\u2211\u22C0-\u22C3\u2A00-\u2A0A\u2A10-\u2A14\u2AFC\u2AFF])/, +/^(?:\\Oplus)/,/^(?:\\oplus)/,/^(?:[\u0028\u0029\u005B\u005D\u007C\u2016\u2308-\u230B\u2329\u232A\u2772\u2773\u27E6-\u27EF\u2980\u2983-\u2998\u29FC\u29FD])/,/^(?:[\u2018\u2019\u201C\u201D])/,/^(?:\\operatorname)/,/^(?:[\u0021-\u0023\u002A-\u002C\u002F\u003A-\u0040\u0060\u00A8\u00AA\u00AC\u00B0-\u00B4\u00B7-\u00BA\u00D7\u00F7\u02CA\u02CB\u02D8-\u02DA\u02DD\u0311\u03F6\u201A\u201B\u201E-\u2022\u2026\u2036\u2037\u2043\u2061-\u2064\u20DB\u20DC\u2145\u2146\u214B\u219A\u219B\u21AE\u21B6-\u21B8\u21BA\u21BB\u21CD-\u21CF\u21DE\u21DF\u21F1\u21F2\u21F4\u21F7-\u21FC\u2200-\u2204\u2206-\u220E\u2212-\u2214\u2216-\u2219\u221B-\u221D\u221F-\u22BF\u22C4-\u22FF\u2305\u2306\u2322\u2323\u23B0\u23B1\u25A0\u25A1\u25AA\u25AB\u25AD-\u25B9\u25BC-\u25CF\u25D6\u25D7\u25E6\u2605\u2660-\u2663\u266D-\u266F\u2758\u27F2\u27F3\u2900-\u2909\u2911\u2914-\u2920\u2923-\u294D\u2962-\u296D\u2970-\u297F\u2981\u2982\u2999-\u29D9\u29DB-\u29FB\u29FE\u29FF\u2A0B-\u2A0F\u2A15-\u2ADB\u2ADD-\u2AFB\u2AFD\u2AFE])/, +/^(?:\\ominus)/,/^(?:\\omicron)/,/^(?:\\Omega)/,/^(?:\\omega)/,/^(?:\\oint)/,/^(?:\\oiint)/,/^(?:\\oiiint)/,/^(?:\\odot)/,/^(?:\\odash)/,/^(?:\\obslash)/,/^(?:\\nwovnearrow)/,/^(?:\\nwArrow)/,/^(?:\\nwarrow)/,/^(?:\\nwArr)/,/^(?:\\nwarr)/,/^(?:\\nVDash)/,/^(?:\\nVdash)/,/^(?:\\nvDash)/,/^(?:\\nvdash)/,/^(?:\u221E)/,/^(?:\\Nu)/,/^(?:\\nu)/,/^(?:\\ntrianglerighteq)/,/^(?:\\ntriangleright)/,/^(?:\\ntrianglelefteq)/,/^(?:\\ntriangleleft)/,/^(?:\\nsupseteq)/,/^(?:\\nsupset)/,/^(?:\\nsuccsim)/,/^(?:\\nsucceq)/, +/^(?:\\nsucc)/,/^(?:\\nsubseteqq)/,/^(?:\\nsubseteq)/,/^(?:\\nsubset)/,/^(?:\\nsime)/,/^(?:\\nsim)/,/^(?:\\nshortparallel)/,/^(?:\\nshortmid)/,/^(?:\\nRightarrow)/,/^(?:\\nrightarrow)/,/^(?:\\npreceq)/,/^(?:\\nprec)/,/^(?:\\nparallel)/,/^(?:\\notni)/,/^(?:\\notin)/,/^(?:\\not)/,/^(?:\\nmid)/,/^(?:\\nless)/,/^(?:\\nleqslant)/,/^(?:\\nleqq)/,/^(?:\\nleq)/,/^(?:\\nLeftrightarrow)/,/^(?:\\nleftrightarrow)/,/^(?:\\nLeftarrow)/,/^(?:\\nleftarrow)/,/^(?:\\ni)/,/^(?:\\ngtr)/,/^(?:\\ngeqslant)/,/^(?:\\ngeqq)/, +/^(?:\\ngeq)/,/^(?:\\nexists)/,/^(?:\\nequiv)/,/^(?:\\neqsim)/,/^(?:\\neq)/,/^(?:\\neovsearrow)/,/^(?:\\neovnwarrow)/,/^(?:\\negthickspace)/,/^(?:\\negspace)/,/^(?:\\negmedspace)/,/^(?:\\neg)/,/^(?:\\neArrow)/,/^(?:\\nearrow)/,/^(?:\\neArr)/,/^(?:\\nearr)/,/^(?:\\ne)/,/^(?:\\ncong)/,/^(?:\\nBumpeq)/,/^(?:\\nbumpeq)/,/^(?:\\natural)/,/^(?:\\napprox)/,/^(?:\\nabla)/,/^(?:\\multiscripts)/,/^(?:\\multimap)/,/^(?:\\Mu)/,/^(?:\\mu)/,/^(?:\\mtext)/,/^(?:\\ms)/,/^(?:\\mp)/,/^(?:\\models)/,/^(?:\\mod)/,/^(?:\\mo)/, +/^(?:\\mn)/,/^(?:\\mlcp)/,/^(?:\\minusdot)/,/^(?:\\minusb)/,/^(?:\\minus)/,/^(?:\\min)/,/^(?:\\mid)/,/^(?:\\mi)/,/^(?:\\mho)/,/^(?:\\mho)/,/^(?:\\medspace)/,/^(?:\\measuredangle)/,/^(?:\\mathtt)/,/^(?:\\mathsf)/,/^(?:\\mathscr)/,/^(?:\\mathrm)/,/^(?:\\mathrlap)/,/^(?:\\mathrel)/,/^(?:\\mathraisebox)/,/^(?:\\mathop)/,/^(?:\\mathmit)/,/^(?:\\mathllap)/,/^(?:\\mathit)/,/^(?:\\mathfrak)/,/^(?:\\mathfr)/,/^(?:\\mathclap)/,/^(?:\\mathcal)/,/^(?:\\mathbscr)/,/^(?:\\mathbit)/,/^(?:\\mathbin)/,/^(?:\\mathbf)/, +/^(?:\\mathbcal)/,/^(?:\\mathbb)/,/^(?:\\Mapsto)/,/^(?:\\mapsto)/,/^(?:\\Mapsfrom)/,/^(?:\\map)/,/^(?:\\lvertneqq)/,/^(?:\\lvertneqq)/,/^(?:\\ltimes)/,/^(?:\\lt)/,/^(?:\\Lsh)/,/^(?:\\lq)/,/^(?:\\lozenge)/,/^(?:\\lowint)/,/^(?:\\looparrowright)/,/^(?:\\looparrowleft)/,/^(?:\\Longrightarrow)/,/^(?:\\longrightarrow)/,/^(?:\\longmapsto)/,/^(?:\\Longleftrightarrow)/,/^(?:\\longleftrightarrow)/,/^(?:\\Longleftarrow)/,/^(?:\\longleftarrow)/,/^(?:\\lnsim)/,/^(?:\\lneqq)/,/^(?:\\lneq)/,/^(?:\\lnapprox)/,/^(?:\\lmoustache)/, +/^(?:\\lll)/,/^(?:\\Lleftarrow)/,/^(?:\\llangle)/,/^(?:\\ll)/,/^(?:\\lhd)/,/^(?:\\lfloor)/,/^(?:\\lesssim)/,/^(?:\\lessgtr)/,/^(?:\\lesseqqgtr)/,/^(?:\\lesseqgtr)/,/^(?:\\lessdot)/,/^(?:\\lessapprox)/,/^(?:\\less)/,/^(?:\\leqslant)/,/^(?:\\leqq)/,/^(?:\\leq)/,/^(?:\\lefttorightarrow)/,/^(?:\\leftthreetimes)/,/^(?:\\leftsquigarrow)/,/^(?:\\leftrightsquigarrow)/,/^(?:\\leftrightharpoons)/,/^(?:\\leftrightarrowtria\*)/,/^(?:\\leftrightarrows)/,/^(?:\\Leftrightarrow)/,/^(?:\\leftrightarrow)/,/^(?:\\leftleftarrows)/, +/^(?:\\leftharpoonup)/,/^(?:\\leftharpoondown)/,/^(?:\\leftarrowtriangle)/,/^(?:\\leftarrowtail)/,/^(?:\\Leftarrow)/,/^(?:\\leftarrow)/,/^(?:\\left)/,/^(?:\\le)/,/^(?:\\ldots)/,/^(?:\\lceil)/,/^(?:\\lbrack)/,/^(?:\\lbrace)/,/^(?:\\langle)/,/^(?:\\lang)/,/^(?:\\Lambda)/,/^(?:\\lambda)/,/^(?:\\kernelcontraction)/,/^(?:\\Kappa)/,/^(?:\\kappa)/,/^(?:\\jmath)/,/^(?:\\itexnum)/,/^(?:\\Iota)/,/^(?:\\iota)/,/^(?:\\invamp)/,/^(?:\\intx)/,/^(?:\\intprodr)/,/^(?:\\intprod)/,/^(?:\\Intersection)/,/^(?:\\intersection)/, +/^(?:\\interleave)/,/^(?:\\intercal)/,/^(?:\\integral)/,/^(?:\\intcup)/,/^(?:\\intcap)/,/^(?:\\intBar)/,/^(?:\\intbar)/,/^(?:\\int)/,/^(?:\\infty)/,/^(?:\\infinity)/,/^(?:\\inf)/,/^(?:\\in)/,/^(?:\\implies)/,/^(?:\\impliedby)/,/^(?:\\imath)/,/^(?:\\Im)/,/^(?:\\iint)/,/^(?:\\iiint)/,/^(?:\\iiiint)/,/^(?:\\iff)/,/^(?:\\hslash)/,/^(?:\\href)/,/^(?:\\hookrightarrow)/,/^(?:\\hookleftarrow)/,/^(?:\\hkswarow)/,/^(?:\\hksearow)/,/^(?:\\heartsuit)/,/^(?:\\hbar)/,/^(?:\\hat)/,/^(?:\\gvertneqq)/,/^(?:\\gvertneqq)/, +/^(?:\\gtrsim)/,/^(?:\\gtrless)/,/^(?:\\gtreqqless)/,/^(?:\\gtreqless)/,/^(?:\\gtrdot)/,/^(?:\\gtrapprox)/,/^(?:\\gt)/,/^(?:\\greater)/,/^(?:\\gnsim)/,/^(?:\\gneqq)/,/^(?:\\gneq)/,/^(?:\\gnapprox)/,/^(?:\\gimel)/,/^(?:\\ggg)/,/^(?:\\gg)/,/^(?:\\geqslant)/,/^(?:\\geqq)/,/^(?:\\geq)/,/^(?:\\ge)/,/^(?:\\Gamma)/,/^(?:\\gamma)/,/^(?:\\frown)/,/^(?:\\frame)/,/^(?:\\frac)/,/^(?:\\forksnot)/,/^(?:\\forks)/,/^(?:\\forall)/,/^(?:\\flat)/,/^(?:\\fdiagovrdiag)/,/^(?:\\fdiagovnearrow)/,/^(?:\\fallingdotseq)/, +/^(?:\\exists)/,/^(?:\\eth)/,/^(?:\\eth)/,/^(?:\\Eta)/,/^(?:\\eta)/,/^(?:\\equiv)/,/^(?:\\equalrows)/,/^(?:\\equalcols)/,/^(?:\\eqslantless)/,/^(?:\\eqslantgtr)/,/^(?:\\eqsim)/,/^(?:\\Eqqcolon)/,/^(?:\\eqqcolon)/,/^(?:\\Eqcolon)/,/^(?:\\Eqcolon)/,/^(?:\\Eqcolon)/,/^(?:\\Eqcolon)/,/^(?:\\eqcolon)/,/^(?:\\eqcirc)/,/^(?:\\epsilon)/,/^(?:\\end\{Vmatrix\})/,/^(?:\\end\{vmatrix\})/,/^(?:\\endtoggle)/,/^(?:\\end\{split\})/,/^(?:\\end\{smallmatrix\})/,/^(?:\\end\{pmatrix\})/,/^(?:\\end\{matrix\})/,/^(?:\\end\{gathered\})/, +/^(?:\\end\{cases\})/,/^(?:\\end\{Bmatrix\})/,/^(?:\\end\{bmatrix\})/,/^(?:\\end\{array\})/,/^(?:\\end\{aligned\})/,/^(?:\\emptyset)/,/^(?:\\empty)/,/^(?:\\embedsin)/,/^(?:\\ell)/,/^(?:\\duparr)/,/^(?:\\dualmap)/,/^(?:\\drbkarrow)/,/^(?:\\downuparrow)/,/^(?:\\downharpoonright)/,/^(?:\\downharpoonleft)/,/^(?:\\downdownarrows)/,/^(?:\\Downarrow)/,/^(?:\\downarrow)/,/^(?:\\doubleintegral)/,/^(?:\\doublebarwedge)/,/^(?:\\doublebarwedge)/,/^(?:\\dots)/,/^(?:\\dotplus)/,/^(?:\\dotminus)/,/^(?:\\doteqdot)/, +/^(?:\\Doteq)/,/^(?:\\doteq)/,/^(?:\\dot)/,/^(?:\\divideontimes)/,/^(?:\\div)/,/^(?:\\displaystyle)/,/^(?:\\disjquant)/,/^(?:\\digamma)/,/^(?:\\diamondsuit)/,/^(?:\\Diamond)/,/^(?:\\diamond)/,/^(?:\\det|\\gcd|\\liminf|\\limsup|\\lim|\\max|\\Pr|\\sup)/,/^(?:\\Delta)/,/^(?:\\delta)/,/^(?:\\Del)/,/^(?:\\degree)/,/^(?:\\Ddownarrow)/,/^(?:\\ddotseq)/,/^(?:\\ddots)/,/^(?:\\ddot)/,/^(?:\\dddot)/,/^(?:\\dddot)/,/^(?:\\ddddot)/,/^(?:\\ddddot)/,/^(?:\\ddagger)/,/^(?:\\dblcolon)/,/^(?:\\dbkarow)/,/^(?:\\Dashv)/, +/^(?:\\dashV)/,/^(?:\\dashv)/,/^(?:\\dashrightarrow)/,/^(?:\\dashleftarrow)/,/^(?:\\darr)/,/^(?:\\daleth)/,/^(?:\\dagger)/,/^(?:\\curvearrowright)/,/^(?:\\curvearrowleft)/,/^(?:\\curvearrowbotright)/,/^(?:\\curlywedge)/,/^(?:\\curlyvee)/,/^(?:\\curlyeqsucc)/,/^(?:\\curlyeqprec)/,/^(?:\\cupdot)/,/^(?:\\Cup)/,/^(?:\\cup)/,/^(?:\\coproduct)/,/^(?:\\coprod)/,/^(?:\\contourintegral)/,/^(?:\\conjquant)/,/^(?:\\conint)/,/^(?:\\cong)/,/^(?:\\complement)/,/^(?:\\colspan)/,/^(?:\\color)/,/^(?:\\Colonsim)/, +/^(?:\\colonsim)/,/^(?:\\Coloneqq)/,/^(?:\\coloneqq)/,/^(?:\\Coloneq)/,/^(?:\\coloneq)/,/^(?:\\Colonapprox)/,/^(?:\\colonapprox)/,/^(?:\\Colon)/,/^(?:\\colon)/,/^(?:\\collines)/,/^(?:\\collayout)/,/^(?:\\colalign)/,/^(?:\\clubsuit)/,/^(?:\\closure)/,/^(?:\\circleddash)/,/^(?:\\circledcirc)/,/^(?:\\circledast)/,/^(?:\\circlearrowright)/,/^(?:\\circlearrowleft)/,/^(?:\\circeq)/,/^(?:\\circ)/,/^(?:\\choose)/,/^(?:\\chi)/,/^(?:\\check)/,/^(?:\\cellopts)/,/^(?:\\cdots)/,/^(?:\\cdotp)/,/^(?:\\cdot)/,/^(?:\\Cap)/, +/^(?:\\cap)/,/^(?:\\bumpeqq)/,/^(?:\\Bumpeq)/,/^(?:\\bumpeq)/,/^(?:\\bullet)/,/^(?:\\btimes)/,/^(?:\\boxtimes)/,/^(?:\\boxplus)/,/^(?:\\boxminus)/,/^(?:\\boxed)/,/^(?:\\boxdot)/,/^(?:\\boxdiag)/,/^(?:\\boxcircle)/,/^(?:\\boxbslash)/,/^(?:\\boxast)/,/^(?:\\Box)/,/^(?:\\bowtie)/,/^(?:\\bottom)/,/^(?:\\bot)/,/^(?:\\boldsymbol)/,/^(?:\\blacktriangleright)/,/^(?:\\blacktriangleleft)/,/^(?:\\blacktriangledown)/,/^(?:\\blacktriangle)/,/^(?:\\blacksquare)/,/^(?:\\blacklozenge)/,/^(?:\\bkarow)/,/^(?:\\binom)/, +/^(?:\\bigwedge)/,/^(?:\\bigvee)/,/^(?:\\biguplus)/,/^(?:\\bigtriangleup)/,/^(?:\\bigtriangledown)/,/^(?:\\bigtimes)/,/^(?:\\bigstar)/,/^(?:\\bigsqcup)/,/^(?:\\bigsqcap)/,/^(?:\\Bigr)/,/^(?:\\bigr)/,/^(?:\\bigotimes)/,/^(?:\\bigoplus)/,/^(?:\\bigodot)/,/^(?:\\Bigl)/,/^(?:\\bigl)/,/^(?:\\biginterleave)/,/^(?:\\Biggr)/,/^(?:\\biggr)/,/^(?:\\Biggl)/,/^(?:\\biggl)/,/^(?:\\Bigg)/,/^(?:\\bigg)/,/^(?:\\bigcupdot)/,/^(?:\\bigcup)/,/^(?:\\bigcirc)/,/^(?:\\bigcap)/,/^(?:\\Big)/,/^(?:\\big)/,/^(?:\\bgcolor)/, +/^(?:\\between)/,/^(?:\\beth)/,/^(?:\\Beta)/,/^(?:\\beta)/,/^(?:\\begin\{Vmatrix\})/,/^(?:\\begin\{vmatrix\})/,/^(?:\\begintoggle)/,/^(?:\\begin\{split\})/,/^(?:\\begin\{smallmatrix\})/,/^(?:\\begin\{pmatrix\})/,/^(?:\\begin\{matrix\})/,/^(?:\\begin\{gathered\})/,/^(?:\\begin\{cases\})/,/^(?:\\begin\{Bmatrix\})/,/^(?:\\begin\{bmatrix\})/,/^(?:\\begin\{array\})/,/^(?:\\begin\{aligned\})/,/^(?:\\because)/,/^(?:\\BbbPi)/,/^(?:\\barwedge)/,/^(?:\\bar)/,/^(?:\\backslash)/,/^(?:\\backsimeq)/,/^(?:\\backsim)/, +/^(?:\\backprime)/,/^(?:\\backepsilon)/,/^(?:\\atop)/,/^(?:\\asymp)/,/^(?:\\ast)/,/^(?:\\arrayopts)/,/^(?:\\array)/,/^(?:\\arccos|\\arcsin|\\arctan|\\arg|\\cosh|\\cos|\\coth|\\cot|\\csc|\\deg|\\dim|\\exp|\\hom|\\ker|\\lg|\\ln|\\log|\\sec|\\sinh|\\sin|\\tanh|\\tan)/,/^(?:\\approxeq)/,/^(?:\\approx)/,/^(?:\\angle)/,/^(?:\\amalg)/,/^(?:\\Alpha)/,/^(?:\\alpha)/,/^(?:\\align)/,/^(?:\\aleph)/,/^(?:[\u0041-\u005A])/,/^(?:[\u0391-\u03A1\u03A3\u03A4\u03A6-\u03A9])/,/^(?:[\u0061-\u007A\u0131\u0237])/,/^(?:[\u03B1-\u03C1\u03C3-\u03C9\u03D1\u03D5\u03D6\u03F0\u03F1\u03F4\u03F5])/, +/^(?:\\adots)/,/^(?:\\AA)/,/^(?:[\u00F0\u03C2\u03D0\u03D2\u03DA-\u03DD\u03E0\u03E1\u0428\u0608\u0627-\u063A\u2102\u210A-\u210D\u210F-\u2113\u2115\u2118-\u211D\u2124\u2127\u2128\u212B-\u212D\u212F-\u2131\u2133-\u2138\u213C\u213D\u213F\u2205]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB\uDEF0\uDEF1]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDFCB])/, +/^(?:\\\$)/,/^(?:\\\})/,/^(?:\\\|)/,/^(?:\\\{)/,/^(?:\\;)/,/^(?:\\:)/,/^(?:\\,)/,/^(?:\\&)/,/^(?:\\%)/,/^(?:\\#)/,/^(?:\\!)/,/^(?:-)/,/^(?:'''')/,/^(?:''')/,/^(?:'')/,/^(?:')/,/^(?:[\uD800-\uDBFF])/,/^(?:[\uDC00-\uDFFF])/,/^(?:.)/],M:{MATH0:{rules:[14,15,16,17,18,19,20,21,22,23,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98, +99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224, +225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350, +351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476, +477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602, +603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728, +729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854, +855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883],inclusive:tb},MATH1:{rules:[14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124, +125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250, +251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376, +377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502, +503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628, +629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754, +755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880, +881,882,883],inclusive:tb},OPTARG:{rules:[13,14,15,16,17,18,19,20,21,22,23,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150, +151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276, +277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402, +403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528, +529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654, +655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780, +781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883],inclusive:tb},DOCUMENT:{rules:[1,2,3,4,5],inclusive:yb},TRYOPTARG:{rules:[6,7],inclusive:yb}, +TEXTOPTARG:{rules:[8,9],inclusive:yb},TEXTARG:{rules:[10,11,12],inclusive:yb},INITIAL:{rules:[0,14,15,16,17,18,19,20,21,22,23,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137, +138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263, +264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389, +390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515, +516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641, +642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767, +768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883],inclusive:tb}}}}();Rb.prototype=rb;rb.pa= +Rb;return new Rb}();window.TeXZilla=zb;window.TeXZilla.setDOMParser=zb.fa;window.TeXZilla.setXMLSerializer=zb.ja;window.TeXZilla.setSafeMode=zb.ia;window.TeXZilla.setItexIdentifierMode=zb.ha;window.TeXZilla.getTeXSource=zb.ca;window.TeXZilla.toMathMLString=zb.Z;window.TeXZilla.toMathML=zb.Y;window.TeXZilla.toImage=zb.na;window.TeXZilla.filterString=zb.Q;window.TeXZilla.filterElement=zb.P; +})(); diff --git a/comm/suite/editor/components/texzilla/jar.mn b/comm/suite/editor/components/texzilla/jar.mn new file mode 100644 index 0000000000..28d5a4cc3e --- /dev/null +++ b/comm/suite/editor/components/texzilla/jar.mn @@ -0,0 +1,6 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +comm.jar: + content/editor/TeXZilla.js (content/TeXZilla.js) diff --git a/comm/suite/editor/components/texzilla/moz.build b/comm/suite/editor/components/texzilla/moz.build new file mode 100644 index 0000000000..de5cd1bf81 --- /dev/null +++ b/comm/suite/editor/components/texzilla/moz.build @@ -0,0 +1,6 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +JAR_MANIFESTS += ["jar.mn"] diff --git a/comm/suite/editor/modules/editorUtilities.jsm b/comm/suite/editor/modules/editorUtilities.jsm new file mode 100644 index 0000000000..877df7d0eb --- /dev/null +++ b/comm/suite/editor/modules/editorUtilities.jsm @@ -0,0 +1,12 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var EXPORTED_SYMBOLS = ["GetNextUntitledValue"]; + +var sUntitledCount = 1; + +function GetNextUntitledValue() { + return sUntitledCount++; +} diff --git a/comm/suite/editor/moz.build b/comm/suite/editor/moz.build new file mode 100644 index 0000000000..d3b471b23d --- /dev/null +++ b/comm/suite/editor/moz.build @@ -0,0 +1,22 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += [ + "base", + "components", +] + +EXTRA_COMPONENTS += [ + "nsComposerCmdLineHandler.js", + "nsComposerCmdLineHandler.manifest", +] + +EXTRA_JS_MODULES += [ + "modules/editorUtilities.jsm", +] + +JS_PREFERENCE_PP_FILES += [ + "profile/composer.js", +] diff --git a/comm/suite/editor/nsComposerCmdLineHandler.js b/comm/suite/editor/nsComposerCmdLineHandler.js new file mode 100644 index 0000000000..2f27207b82 --- /dev/null +++ b/comm/suite/editor/nsComposerCmdLineHandler.js @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); + +const nsICommandLineHandler = Ci.nsICommandLineHandler; +const nsISupportsString = Ci.nsISupportsString; +const nsIWindowWatcher = Ci.nsIWindowWatcher; + +function nsComposerCmdLineHandler() {} +nsComposerCmdLineHandler.prototype = { + get wrappedJSObject() { + return this; + }, + + /* nsISupports */ + QueryInterface: ChromeUtils.generateQI([nsICommandLineHandler]), + + /* nsICommandLineHandler */ + handle(cmdLine) { + var args = Cc["@mozilla.org/supports-string;1"].createInstance( + nsISupportsString + ); + try { + var uristr = cmdLine.handleFlagWithParam("edit", false); + if (uristr == null) { + // Try the editor flag (used for general.startup.* prefs) + uristr = cmdLine.handleFlagWithParam("editor", false); + if (uristr == null) { + return; + } + } + + try { + args.data = cmdLine.resolveURI(uristr).spec; + } catch (e) { + return; + } + } catch (e) { + // One of the flags is present but no data, so set default arg. + args.data = "about:blank"; + } + + Services.ww.openWindow( + null, + "chrome://editor/content", + "_blank", + "chrome,dialog=no,all", + args + ); + cmdLine.preventDefault = true; + }, + + helpInfo: " -edit <url> Open Composer.\n", + + /* XPCOMUtils */ + classID: Components.ID("{f7d8db95-ab5d-4393-a796-9112fe758cfa}"), +}; + +var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsComposerCmdLineHandler]); diff --git a/comm/suite/editor/nsComposerCmdLineHandler.manifest b/comm/suite/editor/nsComposerCmdLineHandler.manifest new file mode 100644 index 0000000000..8a27624af1 --- /dev/null +++ b/comm/suite/editor/nsComposerCmdLineHandler.manifest @@ -0,0 +1,3 @@ +component {f7d8db95-ab5d-4393-a796-9112fe758cfa} nsComposerCmdLineHandler.js +contract @mozilla.org/commandlinehandler/general-startup;1?type=editor {f7d8db95-ab5d-4393-a796-9112fe758cfa} +category command-line-handler m-edit @mozilla.org/commandlinehandler/general-startup;1?type=editor diff --git a/comm/suite/editor/profile/composer.js b/comm/suite/editor/profile/composer.js new file mode 100644 index 0000000000..e123fc7771 --- /dev/null +++ b/comm/suite/editor/profile/composer.js @@ -0,0 +1,70 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Default preferences for seamonkey composer. This file + * was copied from mozilla/modules/libpref/src/init/editor.js + * + * If you're looking for the default prefs of standalone + * composer, see mozilla/composer/app/profile/all.js + */ + +pref("editor.author", ""); + +pref("editor.text_color", "#000000"); +pref("editor.link_color", "#0000FF"); +pref("editor.active_link_color", "#000088"); +pref("editor.followed_link_color", "#FF0000"); +pref("editor.background_color", "#FFFFFF"); +pref("editor.use_background_image", false); +pref("editor.default_background_image", ""); +pref("editor.use_custom_default_colors", 1); + +pref("editor.hrule.height", 2); +pref("editor.hrule.width", 100); +pref("editor.hrule.width_percent", true); +pref("editor.hrule.shading", true); +pref("editor.hrule.align", 1); // center + +pref("editor.table.maintain_structure", true); + +pref("editor.prettyprint", true); + +pref("editor.history.url_maximum", 10); + +pref("editor.publish.", ""); +pref("editor.lastFileLocation.image", ""); +pref("editor.lastFileLocation.html", ""); +pref("editor.save_associated_files", true); +pref("editor.always_show_publish_dialog", false); + +/* + * What are the entities that you want Mozilla to save using mnemonic + * names rather than numeric codes? E.g. If set, we'll output + * otherwise, we may output 0xa0 depending on the charset. + * + * "none" : don't use any entity names; only use numeric codes. + * "basic" : use entity names just for & < > " for + * interoperability/exchange with products that don't support more + * than that. + * "latin1" : use entity names for 8bit accented letters and other special + * symbols between 128 and 255. + * "html" : use entity names for 8bit accented letters, greek letters, and + * other special markup symbols as defined in HTML4. + */ +//pref("editor.encode_entity", "html"); + +#ifndef XP_MACOSX +#ifdef XP_UNIX +pref("editor.disable_spell_checker", false); +pref("editor.dont_lock_spell_files", true); +#endif +#endif + +pref("editor.CR_creates_new_p", false); + +// Pasting images from the clipboard, order of encoding preference: +// JPEG-PNG-GIF=0, PNG-JPEG-GIF=1, GIF-JPEG-PNG=2 +pref("clipboard.paste_image_type", 1); |