From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../components/compose/content/ComposerCommands.js | 2261 ++++++++++++++++++++ 1 file changed, 2261 insertions(+) create mode 100644 comm/mail/components/compose/content/ComposerCommands.js (limited to 'comm/mail/components/compose/content/ComposerCommands.js') diff --git a/comm/mail/components/compose/content/ComposerCommands.js b/comm/mail/components/compose/content/ComposerCommands.js new file mode 100644 index 0000000000..7e9d7a992d --- /dev/null +++ b/comm/mail/components/compose/content/ComposerCommands.js @@ -0,0 +1,2261 @@ +/* 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. These commands + * are related to editing. You can fire these commands with following functions: + * goDoCommand and goDoCommandParams(If command requires any parameters). + * + * Sometimes, we want to reflect the changes in the UI also. We have two functions + * for that: pokeStyleUI and pokeMultiStateUI. The pokeStyleUI function is for those + * commands which are boolean in nature for example "cmd_bold" command, text can + * be bold or not. The pokeMultiStateUI function is for the commands which can have + * multiple values for example "cmd_fontFace" can have different values like + * arial, variable width etc. + * + * Here, some of the commands are getting executed by document.execCommand. + * Those are listed in the gCommandMap Map object. In that also, some commands + * are of type boolean and some are of multiple state. We have two functions to + * execute them: doStatefulCommand and doStyleUICommand. + * + * All commands are not executable through document.execCommand. + * In all those cases, we will use goDoCommand or goDoCommandParams. + * The goDoCommandParams function is implemented in this file. + * The goDoCOmmand function is from globalOverlay.js. For the Commands + * which can be executed by document.execCommand, we will use doStatefulCommand + * and doStyleUICommand. + */ + +/* import-globals-from ../../../../../toolkit/components/printing/content/printUtils.js */ +/* import-globals-from ../../../base/content/globalOverlay.js */ +/* import-globals-from ../../../base/content/utilityOverlay.js */ +/* import-globals-from editor.js */ +/* import-globals-from editorUtilities.js */ +/* import-globals-from MsgComposeCommands.js */ + +var gComposerJSCommandControllerID = 0; + +/** + * Used to register commands we have created manually. + */ +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_listProperties", nsListPropertiesCommand); + commandTable.registerCommand("cmd_colorProperties", nsColorPropertiesCommand); + commandTable.registerCommand("cmd_increaseFontStep", nsIncreaseFontCommand); + commandTable.registerCommand("cmd_decreaseFontStep", nsDecreaseFontCommand); + commandTable.registerCommand( + "cmd_objectProperties", + nsObjectPropertiesCommand + ); + commandTable.registerCommand( + "cmd_removeNamedAnchors", + nsRemoveNamedAnchorsCommand + ); + + 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_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_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_insertChars", nsInsertCharsCommand); +} + +/** + * Used to register the command controller in the editor document. + * + * @returns {nsIControllerCommandTable|null} - A controller used to + * register the manually created commands. + */ +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 */ + +/** + * Get the state of the given command and call the pokeStyleUI or pokeMultiStateUI + * according to the type of the command to reflect the UI changes in the editor. + * + * @param {string} command - The id of the command. + */ +function goUpdateCommandState(command) { + try { + var controller = + 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": + pokeMultiStateUI(command, params); + break; + + 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) { + console.error(e); + } +} +/* eslint-enable complexity */ + +/** + * Used in the oncommandupdate attribute of the goUpdateComposerMenuItems. + * For any commandset events fired, this function will be called. + * Used to update the UI state of the editor buttons and menulist. + * Whenever you change your selection in the editor part, i.e. if you move + * your cursor, you will find this functions getting called and + * updating the editor UI of toolbarbuttons and menulists. This is mainly + * to update the UI according to your selection in the editor part. + * + * @param {XULElement} commandset - The element to update for. + */ +function goUpdateComposerMenuItems(commandset) { + // dump("Updating commands for " + commandset.id + "\n"); + for (var i = 0; i < commandset.children.length; i++) { + var commandNode = commandset.children[i]; + var commandID = commandNode.id; + if (commandID) { + goUpdateCommand(commandID); // enable or disable + if (commandNode.hasAttribute("state")) { + goUpdateCommandState(commandID); + } + } + } +} + +/** + * Execute the command with the provided parameters. + * This is directly calling commands with multiple state attributes, which + * are not supported by document.execCommand() + * + * @param {string} command - The command ID. + * @param {string} paramValue - The parameter value. + */ +function goDoCommandParams(command, paramValue) { + try { + let params = newCommandParams(); + params.setStringValue("state_attribute", paramValue); + let controller = + document.commandDispatcher.getControllerForCommand(command); + if (controller && controller.isCommandEnabled(command)) { + if (controller instanceof Ci.nsICommandController) { + controller.doCommandWithParams(command, params); + } else { + controller.doCommand(command); + } + } + } catch (e) { + console.error(e); + } +} + +/** + * Update the UI to reflect setting a given state for a command. This + * is used for boolean type of commands. + * + * @param {string} uiID - The id of the command. + * @param {boolean} desiredState - State to set for the command. + */ +function pokeStyleUI(uiID, desiredState) { + let commandNode = document.getElementById(uiID); + let uiState = commandNode.getAttribute("state") == "true"; + if (desiredState != uiState) { + commandNode.setAttribute("state", desiredState ? "true" : "false"); + let buttonId; + switch (uiID) { + case "cmd_bold": + buttonId = "boldButton"; + break; + case "cmd_italic": + buttonId = "italicButton"; + break; + case "cmd_underline": + buttonId = "underlineButton"; + break; + case "cmd_ul": + buttonId = "ulButton"; + break; + case "cmd_ol": + buttonId = "olButton"; + break; + } + if (buttonId) { + document.getElementById(buttonId).checked = desiredState; + } + } +} + +/** + * Maps internal command names to their document.execCommand() command string. + */ +let gCommandMap = new Map([ + ["cmd_bold", "bold"], + ["cmd_italic", "italic"], + ["cmd_underline", "underline"], + ["cmd_strikethrough", "strikethrough"], + ["cmd_superscript", "superscript"], + ["cmd_subscript", "subscript"], + ["cmd_ul", "InsertUnorderedList"], + ["cmd_ol", "InsertOrderedList"], + ["cmd_fontFace", "fontName"], + + // This are currently implemented with the help of + // color selection dialog box in the editor.js. + // ["cmd_highlight", "backColor"], + // ["cmd_fontColor", "foreColor"], +]); + +/** + * Used for the boolean type commands available through + * document.execCommand(). We will also call pokeStyleUI to update + * the UI. + * + * @param {string} cmdStr - The id of the command. + */ +function doStyleUICommand(cmdStr) { + GetCurrentEditorElement().contentDocument.execCommand( + gCommandMap.get(cmdStr), + false, + null + ); + let commandNode = document.getElementById(cmdStr); + let newState = commandNode.getAttribute("state") != "true"; + pokeStyleUI(cmdStr, newState); +} + +// 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; +} + +/** + * Update the UI to reflect setting a given state for a command. This is used + * when the command state has a string value i.e. multiple state type commands. + * + * @param {string} uiID - The id of the command. + * @param {nsICommandParams} cmdParams - Command parameters object. + */ +function pokeMultiStateUI(uiID, cmdParams) { + let desiredAttrib; + if (cmdParams.getBooleanValue("state_mixed")) { + desiredAttrib = "mixed"; + } else if ( + cmdParams.getValueType("state_attribute") == 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"); + } + + let commandNode = document.getElementById(uiID); + let uiState = commandNode.getAttribute("state"); + if (desiredAttrib != uiState) { + commandNode.setAttribute("state", desiredAttrib); + switch (uiID) { + case "cmd_paragraphState": { + onParagraphFormatChange(); + break; + } + case "cmd_fontFace": { + onFontFaceChange(); + break; + } + case "cmd_fontColor": { + onFontColorChange(); + break; + } + case "cmd_backgroundColor": { + onBackgroundColorChange(); + break; + } + } + } +} + +/** + * Perform the action of the multiple states type commands available through + * document.execCommand(). + * + * @param {string} commandID - The id of the command. + * @param {string} newState - The parameter value. + * @param {boolean} updateUI - updates the UI if true. Used when + * function is called in another JavaScript function. + */ +function doStatefulCommand(commandID, newState, updateUI) { + if (commandID == "cmd_align") { + let command; + switch (newState) { + case "left": + command = "justifyLeft"; + break; + case "center": + command = "justifyCenter"; + break; + case "right": + command = "justifyRight"; + break; + case "justify": + command = "justifyFull"; + break; + } + GetCurrentEditorElement().contentDocument.execCommand(command, false, null); + } else if (commandID == "cmd_fontFace" && newState == "") { + goDoCommandParams(commandID, newState); + } else { + GetCurrentEditorElement().contentDocument.execCommand( + gCommandMap.get(commandID), + false, + newState + ); + } + + if (updateUI) { + let commandNode = document.getElementById(commandID); + commandNode.setAttribute("state", newState); + switch (commandID) { + case "cmd_fontFace": { + onFontFaceChange(); + break; + } + } + } else { + let commandNode = document.getElementById(commandID); + if (commandNode) { + commandNode.setAttribute("state", newState); + } + } +} + +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"); + }, +}; + +// ------- 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; +} + +/** + * @returns {Promise} dialogResult + */ +function PromptForSaveLocation( + aDoSaveAsText, + aEditorType, + aMIMEType, + aDocumentURLString +) { + var dialogResult = {}; + dialogResult.filepickerClick = Ci.nsIFilePicker.returnCancel; + dialogResult.resultingURI = ""; + dialogResult.resultingLocalFile = null; + + var fp = null; + try { + fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.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, Ci.nsIFilePicker.modeSave); + + // Set filters according to the type of output + if (aDoSaveAsText) { + fp.appendFilters(Ci.nsIFilePicker.filterText); + } else { + fp.appendFilters(Ci.nsIFilePicker.filterHTML); + } + fp.appendFilters(Ci.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 != Ci.nsIFilePicker.returnCancel && fp.file) { + // Allow OK and replace. + // reset urlstring to new save location + dialogResult.resultingURIString = fileHandler.getURLSpecFromActualFile( + 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. + * + * @returns 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 +// UpdateWindowTitle(); +// if (!aSaveCopy) +// editor.resetModificationCount(); +// this should cause notification to listeners that document has changed + +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(Ci.nsIWebBrowserPersist); + 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 |= + Ci.nsIWebBrowserPersist.ENCODE_FLAGS_CR_LINEBREAKS | + Ci.nsIWebBrowserPersist.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 | + Ci.nsIWebBrowserPersist.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 | + Ci.nsIWebBrowserPersist.PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS | + Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES | + Ci.nsIWebBrowserPersist.PERSIST_FLAGS_DONT_FIXUP_LINKS | + Ci.nsIWebBrowserPersist.PERSIST_FLAGS_DONT_CHANGE_FILENAMES | + Ci.nsIWebBrowserPersist.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" + ? Ci.nsIWebBrowserPersist.ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES + : Ci.nsIWebBrowserPersist.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES; + if (aMimeType == "text/plain") { + // When saving in "text/plain" format, always do formatting + outputFlags |= Ci.nsIWebBrowserPersist.ENCODE_FLAGS_FORMATTED; + } else { + // Should we prettyprint? Check the pref + if (Services.prefs.getBoolPref("editor.prettyprint")) { + outputFlags |= Ci.nsIWebBrowserPersist.ENCODE_FLAGS_FORMATTED; + } + + try { + // How much entity names should we output? Check the pref + switch (Services.prefs.getCharPref("editor.encode_entity")) { + case "basic": + outputEntity = + Ci.nsIWebBrowserPersist.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES; + break; + case "latin1": + outputEntity = + Ci.nsIWebBrowserPersist.ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES; + break; + case "html": + outputEntity = + Ci.nsIWebBrowserPersist.ENCODE_FLAGS_ENCODE_HTML_ENTITIES; + break; + case "none": + outputEntity = 0; + break; + } + } catch (e) {} + } + outputFlags |= outputEntity; + + if (aWrapColumn > 0) { + outputFlags |= Ci.nsIWebBrowserPersist.ENCODE_FLAGS_WRAP; + } + + return outputFlags; +} + +// returns number of column where to wrap +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 kErrorBindingAborted = 2152398850; +const kErrorBindingRedirected = 2152398851; +const kFileNotFound = 2152857618; + +var gEditorOutputProgressListener = { + onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { + // Use this to access onStateChange flags + var requestSpec; + try { + var channel = aRequest.QueryInterface(Ci.nsIChannel); + requestSpec = StripUsernamePasswordFromURI(channel.URI); + } catch (e) { + if (gShowDebugOutputStateChange) { + dump("***** onStateChange; NO REQUEST CHANNEL\n"); + } + } + + if (gShowDebugOutputStateChange) { + dump("\n***** onStateChange request: " + requestSpec + "\n"); + dump(" state flags: "); + + if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) { + dump(" STATE_START, "); + } + if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) { + dump(" STATE_STOP, "); + } + if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) { + dump(" STATE_IS_NETWORK "); + } + + dump(`\n * requestSpec=${requestSpec}, aStatus=${aStatus}\n`); + + DumpDebugStatus(aStatus); + } + }, + + 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(Ci.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(Ci.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(Ci.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(Ci.nsIChannel); + dump("***** onSecurityChange request: " + channel.URI.spec + "\n"); + } catch (e) {} + } + }, + + onContentBlockingEvent(aWebProgress, aRequest, aEvent) {}, + + QueryInterface: ChromeUtils.generateQI([ + "nsIWebProgressListener", + "nsISupportsWeakReference", + ]), +}; + +/* 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 */ + +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 + "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 == Ci.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) { + console.error(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); + } + } + + // 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 + goUpdateCommand("cmd_save"); + } catch (e) {} + } else { + Services.prompt.alert( + window, + GetString("SaveDocument"), + GetString("SaveFileFailed") + ); + } + return success; +} +/* eslint-enable complexity */ + +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://messenger/content/messengercompose/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) { + GetCurrentEditor().QueryInterface(Ci.nsIEditorMailSupport).rewrap(false); + }, +}; + +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://messenger/content/messengercompose/EdSpellCheck.xhtml", + "_blank", + "dialog,close,titlebar,modal,resizable", + false, + skipBlockQuotes, + true + ); + } catch (ex) {} + }, +}; + +var nsImageCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.openDialog( + "chrome://messenger/content/messengercompose/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://messenger/content/messengercompose/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://messenger/content/messengercompose/EdImageProps.xhtml", + "_blank", + "chrome,close,titlebar,modal", + null, + true + ); + } else { + window.openDialog( + "chrome://messenger/content/messengercompose/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://messenger/content/messengercompose/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) { + gMsgCompose.allowRemoteContent = true; + window.openDialog( + "chrome://messenger/content/messengercompose/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://messenger/content/messengercompose/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 nsInsertBreakAllCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + try { + GetCurrentEditor().insertHTML("
"); + } catch (e) {} + }, +}; + +var nsListPropertiesCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.openDialog( + "chrome://messenger/content/messengercompose/EdListProps.xhtml", + "_blank", + "chrome,close,titlebar,modal" + ); + }, +}; + +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": + gMsgCompose.allowRemoteContent = true; + goDoCommand("cmd_image"); + break; + case "hr": + goDoCommand("cmd_hline"); + 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) { + try { + let editor = GetCurrentEditor(); + let smileyCode = aParams.getStringValue("state_attribute"); + editor.insertHTML(smileyCode); + 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://messenger/content/messengercompose/EdAdvancedEdit.xhtml", + "_blank", + "chrome,close,titlebar,modal,resizable=yes", + "", + element + ); + } +} + +var nsColorPropertiesCommand = { + isCommandEnabled(aCommand, dummy) { + return IsDocumentEditable() && IsEditingRenderedHTML(); + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + window.openDialog( + "chrome://messenger/content/messengercompose/EdColorProps.xhtml", + "_blank", + "chrome,close,titlebar,modal", + "" + ); + UpdateDefaultColors(); + }, +}; + +var nsIncreaseFontCommand = { + isCommandEnabled(aCommand, dummy) { + if (!(IsDocumentEditable() && IsEditingRenderedHTML())) { + return false; + } + let setIndex = parseInt(getLegacyFontSize()); + return setIndex < 6; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + let setIndex = parseInt(getLegacyFontSize()); + EditorSetFontSize((setIndex + 1).toString()); + }, +}; + +var nsDecreaseFontCommand = { + isCommandEnabled(aCommand, dummy) { + if (!(IsDocumentEditable() && IsEditingRenderedHTML())) { + return false; + } + let setIndex = parseInt(getLegacyFontSize()); + return setIndex > 1; + }, + + getCommandStateParams(aCommand, aParams, aRefCon) {}, + doCommandParams(aCommand, aParams, aRefCon) {}, + + doCommand(aCommand) { + let setIndex = parseInt(getLegacyFontSize()); + EditorSetFontSize((setIndex - 1).toString()); + }, +}; + +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 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 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 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://messenger/content/messengercompose/EdConvertToTable.xhtml", + "_blank", + "chrome,close,titlebar,modal" + ); + } + }, +}; -- cgit v1.2.3