summaryrefslogtreecommitdiffstats
path: root/comm/suite/editor/base/content/ComposerCommands.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/suite/editor/base/content/ComposerCommands.js
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.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/content/ComposerCommands.js')
-rw-r--r--comm/suite/editor/base/content/ComposerCommands.js4051
1 files changed, 4051 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"
+ );
+ }
+ },
+};