diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/suite/editor/base | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/suite/editor/base')
119 files changed, 13541 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"] |