summaryrefslogtreecommitdiffstats
path: root/comm/suite/editor/base
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
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/suite/editor/base')
-rw-r--r--comm/suite/editor/base/content/ComposerCommands.js4051
-rw-r--r--comm/suite/editor/base/content/EditorAllTags.css802
-rw-r--r--comm/suite/editor/base/content/EditorContent.css62
-rw-r--r--comm/suite/editor/base/content/EditorContextMenu.js122
-rw-r--r--comm/suite/editor/base/content/EditorContextMenuOverlay.xhtml171
-rw-r--r--comm/suite/editor/base/content/StructBarContextMenu.js179
-rw-r--r--comm/suite/editor/base/content/composerOverlay.xhtml28
-rw-r--r--comm/suite/editor/base/content/editingOverlay.js387
-rw-r--r--comm/suite/editor/base/content/editingOverlay.xhtml247
-rw-r--r--comm/suite/editor/base/content/editor.js3383
-rw-r--r--comm/suite/editor/base/content/editor.xhtml402
-rw-r--r--comm/suite/editor/base/content/editorApplicationOverlay.js161
-rw-r--r--comm/suite/editor/base/content/editorOverlay.xhtml1504
-rw-r--r--comm/suite/editor/base/content/editorTasksOverlay.xhtml31
-rw-r--r--comm/suite/editor/base/content/editorUtilities.js1014
-rw-r--r--comm/suite/editor/base/content/images/bringtofront-disabled.pngbin0 -> 155 bytes
-rw-r--r--comm/suite/editor/base/content/images/bringtofront.pngbin0 -> 155 bytes
-rw-r--r--comm/suite/editor/base/content/images/sendtoback-disabled.pngbin0 -> 156 bytes
-rw-r--r--comm/suite/editor/base/content/images/sendtoback.pngbin0 -> 156 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-a.pngbin0 -> 185 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-abr.pngbin0 -> 243 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-acr.pngbin0 -> 290 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-adr.pngbin0 -> 259 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-anchor.pngbin0 -> 171 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-app.pngbin0 -> 251 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-ara.pngbin0 -> 246 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-b.pngbin0 -> 177 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-bas.pngbin0 -> 240 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-bdo.pngbin0 -> 211 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-big.pngbin0 -> 213 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-blq.pngbin0 -> 294 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-body.pngbin0 -> 247 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-br.pngbin0 -> 204 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-bsf.pngbin0 -> 274 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-btn.pngbin0 -> 259 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-cit.pngbin0 -> 214 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-clg.pngbin0 -> 261 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-cod.pngbin0 -> 214 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-col.pngbin0 -> 201 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-cpt.pngbin0 -> 273 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-ctr.pngbin0 -> 249 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-dd.pngbin0 -> 188 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-del.pngbin0 -> 191 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-dfn.pngbin0 -> 210 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-dir.pngbin0 -> 200 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-div.pngbin0 -> 218 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-dl.pngbin0 -> 185 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-dt.pngbin0 -> 187 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-em.pngbin0 -> 196 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-fld.pngbin0 -> 239 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-fnt.pngbin0 -> 228 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-for.pngbin0 -> 237 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-frm.pngbin0 -> 240 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-fst.pngbin0 -> 261 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-h1.pngbin0 -> 184 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-h2.pngbin0 -> 194 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-h3.pngbin0 -> 196 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-h4.pngbin0 -> 196 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-h5.pngbin0 -> 197 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-h6.pngbin0 -> 195 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-hed.pngbin0 -> 241 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-hr.pngbin0 -> 194 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-html.pngbin0 -> 222 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-i.pngbin0 -> 154 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-ifr.pngbin0 -> 255 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-img.pngbin0 -> 214 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-inp.pngbin0 -> 235 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-ins.pngbin0 -> 207 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-isx.pngbin0 -> 278 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-kbd.pngbin0 -> 223 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-lbl.pngbin0 -> 238 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-lgn.pngbin0 -> 253 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-li.pngbin0 -> 167 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-lnk.pngbin0 -> 219 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-lst.pngbin0 -> 263 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-map.pngbin0 -> 228 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-men.pngbin0 -> 236 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-met.pngbin0 -> 230 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-nbr.pngbin0 -> 301 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-nfr.pngbin0 -> 297 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-nsc.pngbin0 -> 253 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-obj.pngbin0 -> 253 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-ol.pngbin0 -> 197 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-opg.pngbin0 -> 266 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-opt.pngbin0 -> 247 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-p.pngbin0 -> 170 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-pln.pngbin0 -> 219 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-pre.pngbin0 -> 211 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-prm.pngbin0 -> 248 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-q.pngbin0 -> 185 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-s.pngbin0 -> 181 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-scr.pngbin0 -> 257 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-slc.pngbin0 -> 249 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-sml.pngbin0 -> 245 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-smp.pngbin0 -> 246 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-spn.pngbin0 -> 246 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-stk.pngbin0 -> 261 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-stl.pngbin0 -> 245 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-stn.pngbin0 -> 277 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-sub.pngbin0 -> 221 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-sup.pngbin0 -> 218 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-tbd.pngbin0 -> 258 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-tbl.pngbin0 -> 240 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-td.pngbin0 -> 194 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-tft.pngbin0 -> 219 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-th.pngbin0 -> 189 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-thd.pngbin0 -> 253 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-tr.pngbin0 -> 197 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-tt.pngbin0 -> 179 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-ttl.pngbin0 -> 218 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-txt.pngbin0 -> 289 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-u.pngbin0 -> 164 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-ul.pngbin0 -> 182 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-userdefined.pngbin0 -> 178 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-var.pngbin0 -> 230 bytes
-rw-r--r--comm/suite/editor/base/content/images/tag-xmp.pngbin0 -> 223 bytes
-rw-r--r--comm/suite/editor/base/content/publishprefs.js867
-rw-r--r--comm/suite/editor/base/jar.mn124
-rw-r--r--comm/suite/editor/base/moz.build6
119 files changed, 13541 insertions, 0 deletions
diff --git a/comm/suite/editor/base/content/ComposerCommands.js b/comm/suite/editor/base/content/ComposerCommands.js
new file mode 100644
index 0000000000..daff0d4563
--- /dev/null
+++ b/comm/suite/editor/base/content/ComposerCommands.js
@@ -0,0 +1,4051 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Implementations of nsIControllerCommand for composer commands */
+
+// Linting is disabled in chunks of this file because it contains code that never
+// runs in Thunderbird, and references things that don't exist in Thunderbird.
+
+/* import-globals-from editor.js */
+/* import-globals-from editorUtilities.js */
+/* globals CreatePublishDataFromUrl editPage FormatDirForPublishing getTopWin
+ goPreferences nsIPromptService openComposeWindow openNewPrivateWith
+ PrintPreviewListener SavePublishDataToPrefs SavePassword savePWObj */
+
+var gComposerJSCommandControllerID = 0;
+
+function SetupHTMLEditorCommands() {
+ var commandTable = GetComposerCommandTable();
+ if (!commandTable) {
+ return;
+ }
+
+ // Include everything a text editor does
+ SetupTextEditorCommands();
+
+ // dump("Registering HTML editor commands\n");
+
+ commandTable.registerCommand("cmd_renderedHTMLEnabler", nsDummyHTMLCommand);
+
+ commandTable.registerCommand("cmd_grid", nsGridCommand);
+
+ commandTable.registerCommand("cmd_listProperties", nsListPropertiesCommand);
+ commandTable.registerCommand("cmd_pageProperties", nsPagePropertiesCommand);
+ commandTable.registerCommand("cmd_colorProperties", nsColorPropertiesCommand);
+ commandTable.registerCommand("cmd_increaseFontStep", nsIncreaseFontCommand);
+ commandTable.registerCommand("cmd_decreaseFontStep", nsDecreaseFontCommand);
+ commandTable.registerCommand(
+ "cmd_advancedProperties",
+ nsAdvancedPropertiesCommand
+ );
+ commandTable.registerCommand(
+ "cmd_objectProperties",
+ nsObjectPropertiesCommand
+ );
+ commandTable.registerCommand(
+ "cmd_removeNamedAnchors",
+ nsRemoveNamedAnchorsCommand
+ );
+ commandTable.registerCommand("cmd_editLink", nsEditLinkCommand);
+
+ commandTable.registerCommand("cmd_form", nsFormCommand);
+ commandTable.registerCommand("cmd_inputtag", nsInputTagCommand);
+ commandTable.registerCommand("cmd_inputimage", nsInputImageCommand);
+ commandTable.registerCommand("cmd_textarea", nsTextAreaCommand);
+ commandTable.registerCommand("cmd_select", nsSelectCommand);
+ commandTable.registerCommand("cmd_button", nsButtonCommand);
+ commandTable.registerCommand("cmd_label", nsLabelCommand);
+ commandTable.registerCommand("cmd_fieldset", nsFieldSetCommand);
+ commandTable.registerCommand("cmd_image", nsImageCommand);
+ commandTable.registerCommand("cmd_hline", nsHLineCommand);
+ commandTable.registerCommand("cmd_link", nsLinkCommand);
+ commandTable.registerCommand("cmd_anchor", nsAnchorCommand);
+ commandTable.registerCommand(
+ "cmd_insertHTMLWithDialog",
+ nsInsertHTMLWithDialogCommand
+ );
+ commandTable.registerCommand(
+ "cmd_insertMathWithDialog",
+ nsInsertMathWithDialogCommand
+ );
+ commandTable.registerCommand("cmd_insertBreak", nsInsertBreakCommand);
+ commandTable.registerCommand("cmd_insertBreakAll", nsInsertBreakAllCommand);
+
+ commandTable.registerCommand("cmd_table", nsInsertOrEditTableCommand);
+ commandTable.registerCommand("cmd_editTable", nsEditTableCommand);
+ commandTable.registerCommand("cmd_SelectTable", nsSelectTableCommand);
+ commandTable.registerCommand("cmd_SelectRow", nsSelectTableRowCommand);
+ commandTable.registerCommand("cmd_SelectColumn", nsSelectTableColumnCommand);
+ commandTable.registerCommand("cmd_SelectCell", nsSelectTableCellCommand);
+ commandTable.registerCommand(
+ "cmd_SelectAllCells",
+ nsSelectAllTableCellsCommand
+ );
+ commandTable.registerCommand("cmd_InsertTable", nsInsertTableCommand);
+ commandTable.registerCommand(
+ "cmd_InsertRowAbove",
+ nsInsertTableRowAboveCommand
+ );
+ commandTable.registerCommand(
+ "cmd_InsertRowBelow",
+ nsInsertTableRowBelowCommand
+ );
+ commandTable.registerCommand(
+ "cmd_InsertColumnBefore",
+ nsInsertTableColumnBeforeCommand
+ );
+ commandTable.registerCommand(
+ "cmd_InsertColumnAfter",
+ nsInsertTableColumnAfterCommand
+ );
+ commandTable.registerCommand(
+ "cmd_InsertCellBefore",
+ nsInsertTableCellBeforeCommand
+ );
+ commandTable.registerCommand(
+ "cmd_InsertCellAfter",
+ nsInsertTableCellAfterCommand
+ );
+ commandTable.registerCommand("cmd_DeleteTable", nsDeleteTableCommand);
+ commandTable.registerCommand("cmd_DeleteRow", nsDeleteTableRowCommand);
+ commandTable.registerCommand("cmd_DeleteColumn", nsDeleteTableColumnCommand);
+ commandTable.registerCommand("cmd_DeleteCell", nsDeleteTableCellCommand);
+ commandTable.registerCommand(
+ "cmd_DeleteCellContents",
+ nsDeleteTableCellContentsCommand
+ );
+ commandTable.registerCommand("cmd_JoinTableCells", nsJoinTableCellsCommand);
+ commandTable.registerCommand("cmd_SplitTableCell", nsSplitTableCellCommand);
+ commandTable.registerCommand(
+ "cmd_TableOrCellColor",
+ nsTableOrCellColorCommand
+ );
+ commandTable.registerCommand("cmd_NormalizeTable", nsNormalizeTableCommand);
+ commandTable.registerCommand("cmd_smiley", nsSetSmiley);
+ commandTable.registerCommand("cmd_ConvertToTable", nsConvertToTable);
+}
+
+function SetupTextEditorCommands() {
+ var commandTable = GetComposerCommandTable();
+ if (!commandTable) {
+ return;
+ }
+
+ // dump("Registering plain text editor commands\n");
+
+ commandTable.registerCommand("cmd_findReplace", nsFindReplaceCommand);
+ commandTable.registerCommand("cmd_find", nsFindCommand);
+ commandTable.registerCommand("cmd_findNext", nsFindAgainCommand);
+ commandTable.registerCommand("cmd_findPrev", nsFindAgainCommand);
+ commandTable.registerCommand("cmd_rewrap", nsRewrapCommand);
+ commandTable.registerCommand("cmd_spelling", nsSpellingCommand);
+ commandTable.registerCommand("cmd_validate", nsValidateCommand);
+ commandTable.registerCommand("cmd_insertChars", nsInsertCharsCommand);
+}
+
+function SetupComposerWindowCommands() {
+ // Don't need to do this if already done
+ if (gComposerWindowControllerID) {
+ return;
+ }
+
+ // Create a command controller and register commands
+ // specific to Web Composer window (file-related commands, HTML Source...)
+ // We can't use the composer controller created on the content window else
+ // we can't process commands when in HTMLSource editor
+ // IMPORTANT: For each of these commands, the doCommand method
+ // must first call SetEditMode(gPreviousNonSourceDisplayMode);
+ // to go from HTML Source mode to any other edit mode
+
+ var windowControllers = window.controllers;
+
+ if (!windowControllers) {
+ return;
+ }
+
+ var commandTable;
+ var composerController;
+ var editorController;
+ try {
+ composerController = Cc[
+ "@mozilla.org/embedcomp/base-command-controller;1"
+ ].createInstance();
+
+ editorController = composerController.QueryInterface(
+ Ci.nsIControllerContext
+ );
+
+ // Get the nsIControllerCommandTable interface we need to register commands
+ var interfaceRequestor = composerController.QueryInterface(
+ Ci.nsIInterfaceRequestor
+ );
+ commandTable = interfaceRequestor.getInterface(
+ Ci.nsIControllerCommandTable
+ );
+ } catch (e) {
+ dump("Failed to create composerController\n");
+ return;
+ }
+
+ if (!commandTable) {
+ dump("Failed to get interface for nsIControllerCommandManager\n");
+ return;
+ }
+
+ // File-related commands
+ commandTable.registerCommand("cmd_open", nsOpenCommand);
+ commandTable.registerCommand("cmd_save", nsSaveCommand);
+ commandTable.registerCommand("cmd_saveAs", nsSaveAsCommand);
+ commandTable.registerCommand("cmd_exportToText", nsExportToTextCommand);
+ commandTable.registerCommand(
+ "cmd_saveAndChangeEncoding",
+ nsSaveAndChangeEncodingCommand
+ );
+ commandTable.registerCommand("cmd_publish", nsPublishCommand);
+ commandTable.registerCommand("cmd_publishAs", nsPublishAsCommand);
+ commandTable.registerCommand("cmd_publishSettings", nsPublishSettingsCommand);
+ commandTable.registerCommand("cmd_revert", nsRevertCommand);
+ commandTable.registerCommand("cmd_openRemote", nsOpenRemoteCommand);
+ commandTable.registerCommand("cmd_preview", nsPreviewCommand);
+ commandTable.registerCommand("cmd_editSendPage", nsSendPageCommand);
+ commandTable.registerCommand("cmd_print", nsPrintCommand);
+ commandTable.registerCommand("cmd_printpreview", nsPrintPreviewCommand);
+ commandTable.registerCommand("cmd_printSetup", nsPrintSetupCommand);
+ commandTable.registerCommand("cmd_close", nsCloseCommand);
+ commandTable.registerCommand("cmd_preferences", nsPreferencesCommand);
+
+ // Edit Mode commands
+ if (GetCurrentEditorType() == "html") {
+ commandTable.registerCommand("cmd_NormalMode", nsNormalModeCommand);
+ commandTable.registerCommand("cmd_AllTagsMode", nsAllTagsModeCommand);
+ commandTable.registerCommand("cmd_HTMLSourceMode", nsHTMLSourceModeCommand);
+ commandTable.registerCommand("cmd_PreviewMode", nsPreviewModeCommand);
+ commandTable.registerCommand("cmd_FinishHTMLSource", nsFinishHTMLSource);
+ commandTable.registerCommand("cmd_CancelHTMLSource", nsCancelHTMLSource);
+ commandTable.registerCommand(
+ "cmd_updateStructToolbar",
+ nsUpdateStructToolbarCommand
+ );
+ }
+
+ windowControllers.insertControllerAt(0, editorController);
+
+ // Store the controller ID so we can be sure to get the right one later
+ gComposerWindowControllerID = windowControllers.getControllerId(
+ editorController
+ );
+}
+
+function GetComposerCommandTable() {
+ var controller;
+ if (gComposerJSCommandControllerID) {
+ try {
+ controller = window.content.controllers.getControllerById(
+ gComposerJSCommandControllerID
+ );
+ } catch (e) {}
+ }
+ if (!controller) {
+ // create it
+ controller = Cc[
+ "@mozilla.org/embedcomp/base-command-controller;1"
+ ].createInstance();
+
+ var editorController = controller.QueryInterface(Ci.nsIControllerContext);
+ editorController.setCommandContext(GetCurrentEditorElement());
+ window.content.controllers.insertControllerAt(0, controller);
+
+ // Store the controller ID so we can be sure to get the right one later
+ gComposerJSCommandControllerID = window.content.controllers.getControllerId(
+ controller
+ );
+ }
+
+ if (controller) {
+ var interfaceRequestor = controller.QueryInterface(
+ Ci.nsIInterfaceRequestor
+ );
+ return interfaceRequestor.getInterface(Ci.nsIControllerCommandTable);
+ }
+ return null;
+}
+
+/* eslint-disable complexity */
+function goUpdateCommandState(command) {
+ try {
+ var controller = top.document.commandDispatcher.getControllerForCommand(
+ command
+ );
+ if (!(controller instanceof Ci.nsICommandController)) {
+ return;
+ }
+
+ var params = newCommandParams();
+ if (!params) {
+ return;
+ }
+
+ controller.getCommandStateWithParams(command, params);
+
+ switch (command) {
+ case "cmd_bold":
+ case "cmd_italic":
+ case "cmd_underline":
+ case "cmd_var":
+ case "cmd_samp":
+ case "cmd_code":
+ case "cmd_acronym":
+ case "cmd_abbr":
+ case "cmd_cite":
+ case "cmd_strong":
+ case "cmd_em":
+ case "cmd_superscript":
+ case "cmd_subscript":
+ case "cmd_strikethrough":
+ case "cmd_tt":
+ case "cmd_nobreak":
+ case "cmd_ul":
+ case "cmd_ol":
+ pokeStyleUI(command, params.getBooleanValue("state_all"));
+ break;
+
+ case "cmd_paragraphState":
+ case "cmd_align":
+ case "cmd_highlight":
+ case "cmd_backgroundColor":
+ case "cmd_fontColor":
+ case "cmd_fontFace":
+ case "cmd_fontSize":
+ case "cmd_absPos":
+ pokeMultiStateUI(command, params);
+ break;
+
+ case "cmd_decreaseZIndex":
+ case "cmd_increaseZIndex":
+ case "cmd_indent":
+ case "cmd_outdent":
+ case "cmd_increaseFont":
+ case "cmd_decreaseFont":
+ case "cmd_increaseFontStep":
+ case "cmd_decreaseFontStep":
+ case "cmd_removeStyles":
+ case "cmd_smiley":
+ break;
+
+ default:
+ dump("no update for command: " + command + "\n");
+ }
+ } catch (e) {
+ dump(
+ "An error occurred updating the " + command + " command: \n" + e + "\n"
+ );
+ }
+}
+/* eslint-enable complexity */
+
+function goUpdateComposerMenuItems(commandset) {
+ // dump("Updating commands for " + commandset.id + "\n");
+
+ for (var i = 0; i < commandset.childNodes.length; i++) {
+ var commandNode = commandset.childNodes[i];
+ var commandID = commandNode.id;
+ if (commandID) {
+ goUpdateCommand(commandID); // enable or disable
+ if (commandNode.hasAttribute("state")) {
+ goUpdateCommandState(commandID);
+ }
+ }
+ }
+}
+
+function goDoCommandParams(command, params) {
+ try {
+ var controller = top.document.commandDispatcher.getControllerForCommand(
+ command
+ );
+ if (controller && controller.isCommandEnabled(command)) {
+ if (controller instanceof Ci.nsICommandController) {
+ controller.doCommandWithParams(command, params);
+
+ // the following two lines should be removed when we implement observers
+ if (params) {
+ controller.getCommandStateWithParams(command, params);
+ }
+ } else {
+ controller.doCommand(command);
+ }
+ ResetStructToolbar();
+ }
+ } catch (e) {
+ dump("An error occurred executing the " + command + " command\n");
+ }
+}
+
+function pokeStyleUI(uiID, aDesiredState) {
+ try {
+ var commandNode = top.document.getElementById(uiID);
+ if (!commandNode) {
+ return;
+ }
+
+ var uiState = "true" == commandNode.getAttribute("state");
+ if (aDesiredState != uiState) {
+ commandNode.setAttribute("state", aDesiredState ? "true" : "false");
+ }
+ } catch (e) {
+ dump("poking UI for " + uiID + " failed: " + e + "\n");
+ }
+}
+
+function doStyleUICommand(cmdStr) {
+ try {
+ var cmdParams = newCommandParams();
+ goDoCommandParams(cmdStr, cmdParams);
+ if (cmdParams) {
+ pokeStyleUI(cmdStr, cmdParams.getBooleanValue("state_all"));
+ }
+
+ ResetStructToolbar();
+ } catch (e) {}
+}
+
+// Copied from jsmime.js.
+function stringToTypedArray(buffer) {
+ var typedarray = new Uint8Array(buffer.length);
+ for (var i = 0; i < buffer.length; i++) {
+ typedarray[i] = buffer.charCodeAt(i);
+ }
+ return typedarray;
+}
+
+function pokeMultiStateUI(uiID, cmdParams) {
+ try {
+ var commandNode = document.getElementById(uiID);
+ if (!commandNode) {
+ return;
+ }
+
+ var isMixed = cmdParams.getBooleanValue("state_mixed");
+ var desiredAttrib;
+ if (isMixed) {
+ desiredAttrib = "mixed";
+ } else {
+ var valuetype = cmdParams.getValueType("state_attribute");
+ if (valuetype == Ci.nsICommandParams.eStringType) {
+ desiredAttrib = cmdParams.getCStringValue("state_attribute");
+ // Decode UTF-8, for example for font names in Japanese.
+ desiredAttrib = new TextDecoder("UTF-8").decode(
+ stringToTypedArray(desiredAttrib)
+ );
+ } else {
+ desiredAttrib = cmdParams.getStringValue("state_attribute");
+ }
+ }
+
+ var uiState = commandNode.getAttribute("state");
+ if (desiredAttrib != uiState) {
+ commandNode.setAttribute("state", desiredAttrib);
+ }
+ } catch (e) {}
+}
+
+function doStatefulCommand(commandID, newState) {
+ var commandNode = document.getElementById(commandID);
+ if (commandNode) {
+ commandNode.setAttribute("state", newState);
+ }
+ gContentWindow.focus(); // needed for command dispatch to work
+
+ try {
+ var cmdParams = newCommandParams();
+ if (!cmdParams) {
+ return;
+ }
+
+ cmdParams.setStringValue("state_attribute", newState);
+ goDoCommandParams(commandID, cmdParams);
+
+ pokeMultiStateUI(commandID, cmdParams);
+
+ ResetStructToolbar();
+ } catch (e) {
+ dump("error thrown in doStatefulCommand: " + e + "\n");
+ }
+}
+
+function PrintObject(obj) {
+ dump("-----" + obj + "------\n");
+ var names = "";
+ for (var i in obj) {
+ if (i == "value") {
+ names += i + ": " + obj.value + "\n";
+ } else if (i == "id") {
+ names += i + ": " + obj.id + "\n";
+ } else {
+ names += i + "\n";
+ }
+ }
+
+ dump(names + "-----------\n");
+}
+
+function PrintNodeID(id) {
+ PrintObject(document.getElementById(id));
+}
+
+var nsDummyHTMLCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // do nothing
+ dump("Hey, who's calling the dummy command?\n");
+ },
+};
+
+var nsOpenCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ // We can always do this.
+ return true;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var fileType = IsHTMLEditor() ? "html" : "text";
+ var title = GetString(IsHTMLEditor() ? "OpenHTMLFile" : "OpenTextFile");
+
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ fp.init(window, title, nsIFilePicker.modeOpen);
+
+ SetFilePickerDirectory(fp, fileType);
+
+ // Direct user to prefer HTML files and/or text files depending on whether
+ // loading into Composer or Text editor, so we call separately to control
+ // the order of the filter list.
+ if (fileType == "html") {
+ fp.appendFilters(nsIFilePicker.filterHTML);
+ }
+ fp.appendFilters(nsIFilePicker.filterText);
+ fp.appendFilters(nsIFilePicker.filterAll);
+
+ fp.open(rv => {
+ if (rv == nsIFilePicker.returnCancel) {
+ return;
+ }
+ // editPage checks for already open window and activates it.
+ if (fp.fileURL.spec) {
+ SaveFilePickerDirectory(fp, fileType);
+ editPage(fp.fileURL.spec, fileType);
+ }
+ });
+ },
+};
+
+// STRUCTURE TOOLBAR
+//
+var nsUpdateStructToolbarCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ UpdateStructToolbar();
+ return true;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+ doCommand(aCommand) {},
+};
+
+// ******* File output commands and utilities ******** //
+var nsSaveCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ // Always allow saving when editing a remote document,
+ // otherwise the document modified state would prevent that
+ // when you first open a remote file.
+ try {
+ var docUrl = GetDocumentUrl();
+ return (
+ IsDocumentEditable() &&
+ (IsDocumentModified() ||
+ IsHTMLSourceChanged() ||
+ IsUrlAboutBlank(docUrl) ||
+ GetScheme(docUrl) != "file")
+ );
+ } catch (e) {
+ return false;
+ }
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var editor = GetCurrentEditor();
+ if (editor) {
+ if (IsHTMLEditor()) {
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ }
+ SaveDocument(
+ IsUrlAboutBlank(GetDocumentUrl()),
+ false,
+ editor.contentsMIMEType
+ );
+ }
+ },
+};
+
+var nsSaveAsCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var editor = GetCurrentEditor();
+ if (editor) {
+ if (IsHTMLEditor()) {
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ }
+ SaveDocument(true, false, editor.contentsMIMEType);
+ }
+ },
+};
+
+var nsExportToTextCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ if (GetCurrentEditor()) {
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ SaveDocument(true, true, "text/plain");
+ }
+ },
+};
+
+var nsSaveAndChangeEncodingCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ window.ok = false;
+ window.exportToText = false;
+ var oldTitle = GetDocumentTitle();
+ window.openDialog(
+ "chrome://editor/content/EditorSaveAsCharset.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal,resizable=yes"
+ );
+
+ if (GetDocumentTitle() != oldTitle) {
+ UpdateWindowTitle();
+ }
+
+ if (window.ok) {
+ if (window.exportToText) {
+ SaveDocument(true, true, "text/plain");
+ } else {
+ var editor = GetCurrentEditor();
+ SaveDocument(true, false, editor ? editor.contentsMIMEType : null);
+ }
+ }
+ },
+};
+
+var nsPublishCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ if (IsDocumentEditable()) {
+ // Always allow publishing when editing a local document,
+ // otherwise the document modified state would prevent that
+ // when you first open any local file.
+ try {
+ var docUrl = GetDocumentUrl();
+ return (
+ IsDocumentModified() ||
+ IsHTMLSourceChanged() ||
+ IsUrlAboutBlank(docUrl) ||
+ GetScheme(docUrl) == "file"
+ );
+ } catch (e) {
+ return false;
+ }
+ }
+ return false;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ if (GetCurrentEditor()) {
+ let docUrl = GetDocumentUrl();
+ let filename = GetFilename(docUrl);
+ let publishData;
+
+ // First check pref to always show publish dialog
+ let showPublishDialog = Services.prefs.getBoolPref(
+ "editor.always_show_publish_dialog"
+ );
+
+ if (!showPublishDialog && filename) {
+ // Try to get publish data from the document url
+ publishData = CreatePublishDataFromUrl(docUrl);
+
+ // If none, use default publishing site? Need a pref for this
+ // if (!publishData)
+ // publishData = GetPublishDataFromSiteName(GetDefaultPublishSiteName(), filename);
+ }
+
+ if (showPublishDialog || !publishData) {
+ // Show the publish dialog
+ publishData = {};
+ window.ok = false;
+ let oldTitle = GetDocumentTitle();
+ window.openDialog(
+ "chrome://editor/content/EditorPublish.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ "",
+ "",
+ publishData
+ );
+ if (GetDocumentTitle() != oldTitle) {
+ UpdateWindowTitle();
+ }
+
+ if (!window.ok) {
+ return false;
+ }
+ }
+ if (publishData) {
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ return Publish(publishData);
+ }
+ }
+ return false;
+ },
+};
+
+var nsPublishAsCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ if (GetCurrentEditor()) {
+ SetEditMode(gPreviousNonSourceDisplayMode);
+
+ window.ok = false;
+ var publishData = {};
+ var oldTitle = GetDocumentTitle();
+ window.openDialog(
+ "chrome://editor/content/EditorPublish.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ "",
+ "",
+ publishData
+ );
+ if (GetDocumentTitle() != oldTitle) {
+ UpdateWindowTitle();
+ }
+
+ if (window.ok) {
+ return Publish(publishData);
+ }
+ }
+ return false;
+ },
+};
+
+// ------- output utilities ----- //
+
+// returns a fileExtension string
+function GetExtensionBasedOnMimeType(aMIMEType) {
+ try {
+ var mimeService = null;
+ mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+
+ var fileExtension = mimeService.getPrimaryExtension(aMIMEType, null);
+
+ // the MIME service likes to give back ".htm" for text/html files,
+ // so do a special-case fix here.
+ if (fileExtension == "htm") {
+ fileExtension = "html";
+ }
+
+ return fileExtension;
+ } catch (e) {}
+ return "";
+}
+
+function GetSuggestedFileName(aDocumentURLString, aMIMEType) {
+ var extension = GetExtensionBasedOnMimeType(aMIMEType);
+ if (extension) {
+ extension = "." + extension;
+ }
+
+ // check for existing file name we can use
+ if (aDocumentURLString && !IsUrlAboutBlank(aDocumentURLString)) {
+ try {
+ let docURI = Services.io.newURI(
+ aDocumentURLString,
+ GetCurrentEditor().documentCharacterSet
+ );
+ docURI = docURI.QueryInterface(Ci.nsIURL);
+
+ // grab the file name
+ let url = validateFileName(decodeURIComponent(docURI.fileBaseName));
+ if (url) {
+ return url + extension;
+ }
+ } catch (e) {}
+ }
+
+ // Check if there is a title we can use to generate a valid filename,
+ // if we can't, use the default filename.
+ var title =
+ validateFileName(GetDocumentTitle()) ||
+ GetString("untitledDefaultFilename");
+ return title + extension;
+}
+
+/**
+ * @return {Promise} dialogResult
+ */
+function PromptForSaveLocation(
+ aDoSaveAsText,
+ aEditorType,
+ aMIMEType,
+ aDocumentURLString
+) {
+ var dialogResult = {};
+ dialogResult.filepickerClick = nsIFilePicker.returnCancel;
+ dialogResult.resultingURI = "";
+ dialogResult.resultingLocalFile = null;
+
+ var fp = null;
+ try {
+ fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ } catch (e) {}
+ if (!fp) {
+ return dialogResult;
+ }
+
+ // determine prompt string based on type of saving we'll do
+ var promptString;
+ if (aDoSaveAsText || aEditorType == "text") {
+ promptString = GetString("SaveTextAs");
+ } else {
+ promptString = GetString("SaveDocumentAs");
+ }
+
+ fp.init(window, promptString, nsIFilePicker.modeSave);
+
+ // Set filters according to the type of output
+ if (aDoSaveAsText) {
+ fp.appendFilters(nsIFilePicker.filterText);
+ } else {
+ fp.appendFilters(nsIFilePicker.filterHTML);
+ }
+ fp.appendFilters(nsIFilePicker.filterAll);
+
+ // now let's actually set the filepicker's suggested filename
+ var suggestedFileName = GetSuggestedFileName(aDocumentURLString, aMIMEType);
+ if (suggestedFileName) {
+ fp.defaultString = suggestedFileName;
+ }
+
+ // set the file picker's current directory
+ // assuming we have information needed (like prior saved location)
+ try {
+ var fileHandler = GetFileProtocolHandler();
+
+ var isLocalFile = true;
+ try {
+ let docURI = Services.io.newURI(
+ aDocumentURLString,
+ GetCurrentEditor().documentCharacterSet
+ );
+ isLocalFile = docURI.schemeIs("file");
+ } catch (e) {}
+
+ var parentLocation = null;
+ if (isLocalFile) {
+ var fileLocation = fileHandler.getFileFromURLSpec(aDocumentURLString); // this asserts if url is not local
+ parentLocation = fileLocation.parent;
+ }
+ if (parentLocation) {
+ // Save current filepicker's default location
+ if ("gFilePickerDirectory" in window) {
+ gFilePickerDirectory = fp.displayDirectory;
+ }
+
+ fp.displayDirectory = parentLocation;
+ } else {
+ // Initialize to the last-used directory for the particular type (saved in prefs)
+ SetFilePickerDirectory(fp, aEditorType);
+ }
+ } catch (e) {}
+
+ return new Promise(resolve => {
+ fp.open(rv => {
+ dialogResult.filepickerClick = rv;
+ if (rv != nsIFilePicker.returnCancel && fp.file) {
+ // Allow OK and replace.
+ // reset urlstring to new save location
+ dialogResult.resultingURIString = fileHandler.getURLSpecFromFile(
+ fp.file
+ );
+ dialogResult.resultingLocalFile = fp.file;
+ SaveFilePickerDirectory(fp, aEditorType);
+ resolve(dialogResult);
+ } else if ("gFilePickerDirectory" in window && gFilePickerDirectory) {
+ fp.displayDirectory = gFilePickerDirectory;
+ resolve(null);
+ }
+ });
+ });
+}
+
+/**
+ * If needed, prompt for document title and set the document title to the
+ * preferred value.
+ * @return true if the title was set up successfully;
+ * false if the user cancelled the title prompt
+ */
+function PromptAndSetTitleIfNone() {
+ if (GetDocumentTitle()) {
+ // we have a title; no need to prompt!
+ return true;
+ }
+
+ let result = { value: null };
+ let captionStr = GetString("DocumentTitle");
+ let msgStr = GetString("NeedDocTitle") + "\n" + GetString("DocTitleHelp");
+ let confirmed = Services.prompt.prompt(
+ window,
+ captionStr,
+ msgStr,
+ result,
+ null,
+ { value: 0 }
+ );
+ if (confirmed) {
+ SetDocumentTitle(TrimString(result.value));
+ }
+
+ return confirmed;
+}
+
+var gPersistObj;
+
+// Don't forget to do these things after calling OutputFileWithPersistAPI:
+// we need to update the uri before notifying listeners
+// if (doUpdateURI)
+// SetDocumentURI(docURI);
+// UpdateWindowTitle();
+// if (!aSaveCopy)
+// editor.resetModificationCount();
+// this should cause notification to listeners that document has changed
+
+const webPersist = Ci.nsIWebBrowserPersist;
+function OutputFileWithPersistAPI(
+ editorDoc,
+ aDestinationLocation,
+ aRelatedFilesParentDir,
+ aMimeType
+) {
+ gPersistObj = null;
+ var editor = GetCurrentEditor();
+ try {
+ editor.forceCompositionEnd();
+ } catch (e) {}
+
+ var isLocalFile = false;
+ try {
+ aDestinationLocation.QueryInterface(Ci.nsIFile);
+ isLocalFile = true;
+ } catch (e) {
+ try {
+ var tmp = aDestinationLocation.QueryInterface(Ci.nsIURI);
+ isLocalFile = tmp.schemeIs("file");
+ } catch (e) {}
+ }
+
+ try {
+ // we should supply a parent directory if/when we turn on functionality to save related documents
+ var persistObj = Cc[
+ "@mozilla.org/embedding/browser/nsWebBrowserPersist;1"
+ ].createInstance(webPersist);
+ persistObj.progressListener = gEditorOutputProgressListener;
+
+ var wrapColumn = GetWrapColumn();
+ var outputFlags = GetOutputFlags(aMimeType, wrapColumn);
+
+ // for 4.x parity as well as improving readability of file locally on server
+ // this will always send crlf for upload (http/ftp)
+ if (!isLocalFile) {
+ // if we aren't saving locally then send both cr and lf
+ outputFlags |=
+ webPersist.ENCODE_FLAGS_CR_LINEBREAKS |
+ webPersist.ENCODE_FLAGS_LF_LINEBREAKS;
+
+ // we want to serialize the output for all remote publishing
+ // some servers can handle only one connection at a time
+ // some day perhaps we can make this user-configurable per site?
+ persistObj.persistFlags =
+ persistObj.persistFlags | webPersist.PERSIST_FLAGS_SERIALIZE_OUTPUT;
+ }
+
+ // note: we always want to set the replace existing files flag since we have
+ // already given user the chance to not replace an existing file (file picker)
+ // or the user picked an option where the file is implicitly being replaced (save)
+ persistObj.persistFlags =
+ persistObj.persistFlags |
+ webPersist.PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS |
+ webPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
+ webPersist.PERSIST_FLAGS_DONT_FIXUP_LINKS |
+ webPersist.PERSIST_FLAGS_DONT_CHANGE_FILENAMES |
+ webPersist.PERSIST_FLAGS_FIXUP_ORIGINAL_DOM;
+ persistObj.saveDocument(
+ editorDoc,
+ aDestinationLocation,
+ aRelatedFilesParentDir,
+ aMimeType,
+ outputFlags,
+ wrapColumn
+ );
+ gPersistObj = persistObj;
+ } catch (e) {
+ dump("caught an error, bail\n");
+ return false;
+ }
+
+ return true;
+}
+
+// returns output flags based on mimetype, wrapCol and prefs
+function GetOutputFlags(aMimeType, aWrapColumn) {
+ var outputFlags = 0;
+ var editor = GetCurrentEditor();
+ var outputEntity =
+ editor && editor.documentCharacterSet == "ISO-8859-1"
+ ? webPersist.ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES
+ : webPersist.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES;
+ if (aMimeType == "text/plain") {
+ // When saving in "text/plain" format, always do formatting
+ outputFlags |= webPersist.ENCODE_FLAGS_FORMATTED;
+ } else {
+ // Should we prettyprint? Check the pref
+ if (Services.prefs.getBoolPref("editor.prettyprint")) {
+ outputFlags |= webPersist.ENCODE_FLAGS_FORMATTED;
+ }
+
+ try {
+ // How much entity names should we output? Check the pref
+ switch (Services.prefs.getCharPref("editor.encode_entity")) {
+ case "basic":
+ outputEntity = webPersist.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES;
+ break;
+ case "latin1":
+ outputEntity = webPersist.ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES;
+ break;
+ case "html":
+ outputEntity = webPersist.ENCODE_FLAGS_ENCODE_HTML_ENTITIES;
+ break;
+ case "none":
+ outputEntity = 0;
+ break;
+ }
+ } catch (e) {}
+ }
+ outputFlags |= outputEntity;
+
+ if (aWrapColumn > 0) {
+ outputFlags |= webPersist.ENCODE_FLAGS_WRAP;
+ }
+
+ return outputFlags;
+}
+
+// returns number of column where to wrap
+const nsIWebBrowserPersist = Ci.nsIWebBrowserPersist;
+function GetWrapColumn() {
+ try {
+ return GetCurrentEditor().wrapWidth;
+ } catch (e) {}
+ return 0;
+}
+
+const gShowDebugOutputStateChange = false;
+const gShowDebugOutputProgress = false;
+const gShowDebugOutputStatusChange = false;
+
+const gShowDebugOutputLocationChange = false;
+const gShowDebugOutputSecurityChange = false;
+
+const nsIWebProgressListener = Ci.nsIWebProgressListener;
+const nsIChannel = Ci.nsIChannel;
+
+const kErrorBindingAborted = 2152398850;
+const kErrorBindingRedirected = 2152398851;
+const kFileNotFound = 2152857618;
+
+var gEditorOutputProgressListener = {
+ /* eslint-disable complexity */
+ onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ var editor = GetCurrentEditor();
+
+ // Use this to access onStateChange flags
+ var requestSpec;
+ try {
+ var channel = aRequest.QueryInterface(nsIChannel);
+ requestSpec = StripUsernamePasswordFromURI(channel.URI);
+ } catch (e) {
+ if (gShowDebugOutputStateChange) {
+ dump("***** onStateChange; NO REQUEST CHANNEL\n");
+ }
+ }
+
+ var pubSpec;
+ if (gPublishData) {
+ pubSpec =
+ gPublishData.publishUrl + gPublishData.docDir + gPublishData.filename;
+ }
+
+ if (gShowDebugOutputStateChange) {
+ dump("\n***** onStateChange request: " + requestSpec + "\n");
+ dump(" state flags: ");
+
+ if (aStateFlags & nsIWebProgressListener.STATE_START) {
+ dump(" STATE_START, ");
+ }
+ if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
+ dump(" STATE_STOP, ");
+ }
+ if (aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+ dump(" STATE_IS_NETWORK ");
+ }
+
+ dump(
+ `\n * requestSpec=${requestSpec}, pubSpec=${pubSpec}, aStatus=${aStatus}\n`
+ );
+
+ DumpDebugStatus(aStatus);
+ }
+ // The rest only concerns publishing, so bail out if no dialog
+ if (!gProgressDialog) {
+ return;
+ }
+
+ // Detect start of file upload of any file:
+ // (We ignore any START messages after gPersistObj says publishing is finished
+ if (
+ aStateFlags & nsIWebProgressListener.STATE_START &&
+ gPersistObj &&
+ requestSpec &&
+ gPersistObj.currentState != gPersistObj.PERSIST_STATE_FINISHED
+ ) {
+ document
+ .getElementById("navigator-throbber")
+ .setAttribute("busy", "true");
+ try {
+ // Add url to progress dialog's list showing each file uploading
+ gProgressDialog.SetProgressStatus(GetFilename(requestSpec), "busy");
+ } catch (e) {}
+ }
+
+ // Detect end of file upload of any file:
+ if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
+ document.getElementById("navigator-throbber").removeAttribute("busy");
+ // ignore aStatus == kErrorBindingAborted; check http response for possible errors
+ try {
+ // check http channel for response: 200 range is ok; other ranges are not
+ var httpChannel = aRequest.QueryInterface(Ci.nsIHttpChannel);
+ var httpResponse = httpChannel.responseStatus;
+ if (httpResponse < 200 || httpResponse >= 300) {
+ // Not a real error but enough to pass check below.
+ aStatus = httpResponse;
+ } else if (aStatus == kErrorBindingAborted) {
+ aStatus = 0;
+ }
+
+ if (gShowDebugOutputStateChange) {
+ dump("http response is: " + httpResponse + "\n");
+ }
+ } catch (e) {
+ if (aStatus == kErrorBindingAborted) {
+ aStatus = 0;
+ }
+ }
+
+ // We abort publishing for all errors except if image src file is not found
+ var abortPublishing = aStatus != 0 && aStatus != kFileNotFound;
+
+ // Notify progress dialog when we receive the STOP
+ // notification for a file if there was an error
+ // or a successful finish
+ // (Check requestSpec to be sure message is for destination url)
+ if (
+ aStatus != 0 ||
+ (requestSpec &&
+ requestSpec.startsWith(GetScheme(gPublishData.publishUrl)))
+ ) {
+ try {
+ gProgressDialog.SetProgressFinished(
+ GetFilename(requestSpec),
+ aStatus
+ );
+ } catch (e) {}
+ }
+
+ if (abortPublishing) {
+ // Cancel publishing
+ gPersistObj.cancelSave();
+
+ // Don't do any commands after failure
+ gCommandAfterPublishing = null;
+
+ // Restore original document to undo image src url adjustments
+ if (gRestoreDocumentSource) {
+ try {
+ editor.rebuildDocumentFromSource(gRestoreDocumentSource);
+
+ // Clear transaction cache since we just did a potentially
+ // very large insert and this will eat up memory
+ editor.clearUndoRedo();
+ } catch (e) {}
+ }
+
+ // Notify progress dialog that we're finished
+ // and keep open to show error
+ gProgressDialog.SetProgressFinished(null, 0);
+
+ // We don't want to change location or reset mod count, etc.
+ return;
+ }
+
+ // XXX HACK: "file://" protocol is not supported in network code
+ // (bug 151867 filed to add this support, bug 151869 filed
+ // to remove this and other code in nsIWebBrowserPersist)
+ // nsIWebBrowserPersist *does* copy the file(s), but we don't
+ // get normal onStateChange messages.
+
+ // Case 1: If images are included, we get fairly normal
+ // STATE_START/STATE_STOP & STATE_IS_NETWORK messages associated with the image files,
+ // thus we must finish HTML file progress below
+
+ // Case 2: If just HTML file is uploaded, we get STATE_START and STATE_STOP
+ // notification with a null "requestSpec", and
+ // the gPersistObj is destroyed before we get here!
+ // So create an new object so we can flow through normal processing below
+ if (
+ !requestSpec &&
+ GetScheme(gPublishData.publishUrl) == "file" &&
+ (!gPersistObj ||
+ gPersistObj.currentState ==
+ nsIWebBrowserPersist.PERSIST_STATE_FINISHED)
+ ) {
+ aStateFlags |= nsIWebProgressListener.STATE_IS_NETWORK;
+ if (!gPersistObj) {
+ gPersistObj = {
+ result: aStatus,
+ currentState: nsIWebBrowserPersist.PERSIST_STATE_FINISHED,
+ };
+ }
+ }
+
+ // STATE_IS_NETWORK signals end of publishing, as does the gPersistObj.currentState
+ if (
+ aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK &&
+ gPersistObj.currentState == nsIWebBrowserPersist.PERSIST_STATE_FINISHED
+ ) {
+ if (GetScheme(gPublishData.publishUrl) == "file") {
+ // XXX "file://" hack: We don't get notified about the HTML file, so end progress for it
+ // (This covers both "Case 1 and 2" described above)
+ gProgressDialog.SetProgressFinished(
+ gPublishData.filename,
+ gPersistObj.result
+ );
+ }
+
+ if (gPersistObj.result == 0) {
+ // All files are finished and publishing succeeded (some images may have failed)
+ try {
+ // Make a new docURI from the "browse location" in case "publish location" was FTP
+ // We need to set document uri before notifying listeners
+ var docUrl = GetDocUrlFromPublishData(gPublishData);
+ SetDocumentURI(
+ Services.io.newURI(docUrl, editor.documentCharacterSet)
+ );
+
+ UpdateWindowTitle();
+
+ // this should cause notification to listeners that doc has changed
+ editor.resetModificationCount();
+
+ // Set UI based on whether we're editing a remote or local url
+ // Why is urlstring undefined?
+ /* eslint-disable-next-line no-undef */
+ SetSaveAndPublishUI(urlstring);
+ } catch (e) {}
+
+ // Save publishData to prefs
+ if (gPublishData) {
+ if (gPublishData.savePublishData) {
+ // We published successfully, so we can safely
+ // save docDir and otherDir to prefs
+ gPublishData.saveDirs = true;
+ SavePublishDataToPrefs(gPublishData);
+ } else {
+ SavePassword(gPublishData);
+ }
+ }
+
+ // Ask progress dialog to close, but it may not
+ // if user checked checkbox to keep it open
+ gProgressDialog.RequestCloseDialog();
+ } else {
+ // We previously aborted publishing because of error:
+ // Calling gPersistObj.cancelSave() resulted in a non-zero gPersistObj.result,
+ // so notify progress dialog we're finished
+ gProgressDialog.SetProgressFinished(null, 0);
+ }
+ }
+ }
+ },
+ /* eslint-enable complexity */
+
+ onProgressChange(
+ aWebProgress,
+ aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress
+ ) {
+ if (!gPersistObj) {
+ return;
+ }
+
+ if (gShowDebugOutputProgress) {
+ dump(
+ "\n onProgressChange: gPersistObj.result=" + gPersistObj.result + "\n"
+ );
+ try {
+ var channel = aRequest.QueryInterface(nsIChannel);
+ dump("***** onProgressChange request: " + channel.URI.spec + "\n");
+ } catch (e) {}
+ dump(
+ "***** self: " +
+ aCurSelfProgress +
+ " / " +
+ aMaxSelfProgress +
+ "\n"
+ );
+ dump(
+ "***** total: " +
+ aCurTotalProgress +
+ " / " +
+ aMaxTotalProgress +
+ "\n\n"
+ );
+
+ if (gPersistObj.currentState == gPersistObj.PERSIST_STATE_READY) {
+ dump(" Persister is ready to save data\n\n");
+ } else if (gPersistObj.currentState == gPersistObj.PERSIST_STATE_SAVING) {
+ dump(" Persister is saving data.\n\n");
+ } else if (
+ gPersistObj.currentState == gPersistObj.PERSIST_STATE_FINISHED
+ ) {
+ dump(" PERSISTER HAS FINISHED SAVING DATA\n\n\n");
+ }
+ }
+ },
+
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ if (gShowDebugOutputLocationChange) {
+ dump("***** onLocationChange: " + aLocation.spec + "\n");
+ try {
+ var channel = aRequest.QueryInterface(nsIChannel);
+ dump("***** request: " + channel.URI.spec + "\n");
+ } catch (e) {}
+ }
+ },
+
+ onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
+ if (gShowDebugOutputStatusChange) {
+ dump("***** onStatusChange: " + aMessage + "\n");
+ try {
+ var channel = aRequest.QueryInterface(nsIChannel);
+ dump("***** request: " + channel.URI.spec + "\n");
+ } catch (e) {
+ dump(" couldn't get request\n");
+ }
+
+ DumpDebugStatus(aStatus);
+
+ if (gPersistObj) {
+ if (gPersistObj.currentState == gPersistObj.PERSIST_STATE_READY) {
+ dump(" Persister is ready to save data\n\n");
+ } else if (
+ gPersistObj.currentState == gPersistObj.PERSIST_STATE_SAVING
+ ) {
+ dump(" Persister is saving data.\n\n");
+ } else if (
+ gPersistObj.currentState == gPersistObj.PERSIST_STATE_FINISHED
+ ) {
+ dump(" PERSISTER HAS FINISHED SAVING DATA\n\n\n");
+ }
+ }
+ }
+ },
+
+ onSecurityChange(aWebProgress, aRequest, state) {
+ if (gShowDebugOutputSecurityChange) {
+ try {
+ var channel = aRequest.QueryInterface(nsIChannel);
+ dump("***** onSecurityChange request: " + channel.URI.spec + "\n");
+ } catch (e) {}
+ }
+ },
+
+ onContentBlockingEvent(aWebProgress, aRequest, aEvent) {},
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ "nsIPrompt",
+ "nsIAuthPrompt",
+ ]),
+
+ // nsIPrompt
+ alert(dlgTitle, text) {
+ Services.prompt.alert(
+ gProgressDialog ? gProgressDialog : window,
+ dlgTitle,
+ text
+ );
+ },
+ alertCheck(dialogTitle, text, checkBoxLabel, checkObj) {
+ Services.prompt.alert(window, dialogTitle, text);
+ },
+ confirm(dlgTitle, text) {
+ return ConfirmWithTitle(dlgTitle, text, null, null);
+ },
+ confirmCheck(dlgTitle, text, checkBoxLabel, checkObj) {
+ Services.prompt.confirmEx(
+ window,
+ dlgTitle,
+ text,
+ nsIPromptService.STD_OK_CANCEL_BUTTONS,
+ "",
+ "",
+ "",
+ checkBoxLabel,
+ checkObj
+ );
+ },
+ confirmEx(
+ dlgTitle,
+ text,
+ btnFlags,
+ btn0Title,
+ btn1Title,
+ btn2Title,
+ checkBoxLabel,
+ checkVal
+ ) {
+ return Services.prompt.confirmEx(
+ window,
+ dlgTitle,
+ text,
+ btnFlags,
+ btn0Title,
+ btn1Title,
+ btn2Title,
+ checkBoxLabel,
+ checkVal
+ );
+ },
+
+ /** ***********************************************************************
+ * gEditorOutputProgressListener needs to implement both nsIPrompt *
+ * (providing alert) and nsIAuthPrompt (providing password saving). *
+ * Unfortunately, both interfaces specify prompt/promptPassword/ *
+ * promptUsernameAndPassword, albeit with conflicting method signatures. *
+ * Luckily, though, we only make use of their nsIAuthPrompt variants, *
+ * hence we can comment out the nsIPrompt ones here to avoid JavaScript *
+ * strict mode clutter. See bug 371174 for more information. *
+ *************************************************************************
+ prompt : function(dlgTitle, text, inoutText, checkBoxLabel, checkObj)
+ {
+ return Services.prompt.prompt(window, dlgTitle, text, inoutText, checkBoxLabel, checkObj);
+ },
+ promptPassword : function(dlgTitle, text, pwObj, checkBoxLabel, savePWObj)
+ {
+ var ret = false;
+ try {
+ // Note difference with nsIAuthPrompt::promptPassword, which has
+ // just "in" savePassword param, while nsIPrompt is "inout"
+ // Initialize with user's previous preference for this site
+ if (gPublishData)
+ savePWObj.value = gPublishData.savePassword;
+
+ ret = Services.prompt.promptPassword(gProgressDialog ? gProgressDialog : window,
+ dlgTitle, text, pwObj, checkBoxLabel, savePWObj);
+
+ if (!ret)
+ setTimeout(CancelPublishing, 0);
+
+ if (ret && gPublishData)
+ UpdateUsernamePasswordFromPrompt(gPublishData, gPublishData.username, pwObj.value, savePWObj.value);
+ } catch(e) {}
+
+ return ret;
+ },
+ promptUsernameAndPassword : function(dlgTitle, text, userObj, pwObj, checkBoxLabel, savePWObj)
+ {
+ var ret = PromptUsernameAndPassword(dlgTitle, text, savePWObj.value, userObj, pwObj);
+ if (!ret)
+ setTimeout(CancelPublishing, 0);
+
+ return ret;
+ },
+ *************************************************************************/
+
+ select(dlgTitle, text, selectList, outSelection) {
+ return Services.prompt.select(
+ window,
+ dlgTitle,
+ text,
+ selectList,
+ outSelection
+ );
+ },
+
+ // nsIAuthPrompt
+ prompt(dlgTitle, text, pwrealm, savePW, defaultText, result) {
+ var ret = Services.prompt.prompt(
+ gProgressDialog ? gProgressDialog : window,
+ dlgTitle,
+ text,
+ defaultText,
+ pwrealm,
+ savePWObj
+ );
+ if (!ret) {
+ setTimeout(CancelPublishing, 0);
+ }
+ return ret;
+ },
+
+ promptUsernameAndPassword(dlgTitle, text, pwrealm, savePW, userObj, pwObj) {
+ var ret = PromptUsernameAndPassword(dlgTitle, text, savePW, userObj, pwObj);
+ if (!ret) {
+ setTimeout(CancelPublishing, 0);
+ }
+ return ret;
+ },
+
+ promptPassword(dlgTitle, text, pwrealm, savePW, pwObj) {
+ var ret = false;
+ try {
+ // Note difference with nsIPrompt::promptPassword, which has
+ // "inout" savePassword param, while nsIAuthPrompt is just "in"
+ // Also nsIAuth doesn't supply "checkBoxLabel"
+ // Initialize with user's previous preference for this site
+ var savePWObj = { value: savePW };
+ // Initialize with user's previous preference for this site
+ if (gPublishData) {
+ savePWObj.value = gPublishData.savePassword;
+ }
+
+ ret = Services.prompt.promptPassword(
+ gProgressDialog ? gProgressDialog : window,
+ dlgTitle,
+ text,
+ pwObj,
+ GetString("SavePassword"),
+ savePWObj
+ );
+
+ if (!ret) {
+ setTimeout(CancelPublishing, 0);
+ }
+
+ if (ret && gPublishData) {
+ UpdateUsernamePasswordFromPrompt(
+ gPublishData,
+ gPublishData.username,
+ pwObj.value,
+ savePWObj.value
+ );
+ }
+ } catch (e) {}
+
+ return ret;
+ },
+};
+
+function PromptUsernameAndPassword(dlgTitle, text, savePW, userObj, pwObj) {
+ // HTTP prompts us twice even if user Cancels from 1st attempt!
+ // So never put up dialog if there's no publish data
+ if (!gPublishData) {
+ return false;
+ }
+
+ var ret = false;
+ try {
+ var savePWObj = { value: savePW };
+
+ // Initialize with user's previous preference for this site
+ if (gPublishData) {
+ // HTTP put uses this dialog if either username or password is bad,
+ // so prefill username input field with the previous value for modification
+ savePWObj.value = gPublishData.savePassword;
+ if (!userObj.value) {
+ userObj.value = gPublishData.username;
+ }
+ }
+
+ ret = Services.prompt.promptUsernameAndPassword(
+ gProgressDialog ? gProgressDialog : window,
+ dlgTitle,
+ text,
+ userObj,
+ pwObj,
+ GetString("SavePassword"),
+ savePWObj
+ );
+ if (ret && gPublishData) {
+ UpdateUsernamePasswordFromPrompt(
+ gPublishData,
+ userObj.value,
+ pwObj.value,
+ savePWObj.value
+ );
+ }
+ } catch (e) {}
+
+ return ret;
+}
+
+/* eslint-disable complexity */
+function DumpDebugStatus(aStatus) {
+ // see nsError.h and netCore.h and ftpCore.h
+
+ if (aStatus == kErrorBindingAborted) {
+ dump("***** status is NS_BINDING_ABORTED\n");
+ } else if (aStatus == kErrorBindingRedirected) {
+ dump("***** status is NS_BINDING_REDIRECTED\n");
+ } else if (aStatus == 2152398859) {
+ // in netCore.h 11
+ dump("***** status is ALREADY_CONNECTED\n");
+ } else if (aStatus == 2152398860) {
+ // in netCore.h 12
+ dump("***** status is NOT_CONNECTED\n");
+ } else if (aStatus == 2152398861) {
+ // in nsISocketTransportService.idl 13
+ dump("***** status is CONNECTION_REFUSED\n");
+ } else if (aStatus == 2152398862) {
+ // in nsISocketTransportService.idl 14
+ dump("***** status is NET_TIMEOUT\n");
+ } else if (aStatus == 2152398863) {
+ // in netCore.h 15
+ dump("***** status is IN_PROGRESS\n");
+ } else if (aStatus == 2152398864) {
+ // 0x804b0010 in netCore.h 16
+ dump("***** status is OFFLINE\n");
+ } else if (aStatus == 2152398865) {
+ // in netCore.h 17
+ dump("***** status is NO_CONTENT\n");
+ } else if (aStatus == 2152398866) {
+ // in netCore.h 18
+ dump("***** status is UNKNOWN_PROTOCOL\n");
+ } else if (aStatus == 2152398867) {
+ // in netCore.h 19
+ dump("***** status is PORT_ACCESS_NOT_ALLOWED\n");
+ } else if (aStatus == 2152398868) {
+ // in nsISocketTransportService.idl 20
+ dump("***** status is NET_RESET\n");
+ } else if (aStatus == 2152398869) {
+ // in ftpCore.h 21
+ dump("***** status is FTP_LOGIN\n");
+ } else if (aStatus == 2152398870) {
+ // in ftpCore.h 22
+ dump("***** status is FTP_CWD\n");
+ } else if (aStatus == 2152398871) {
+ // in ftpCore.h 23
+ dump("***** status is FTP_PASV\n");
+ } else if (aStatus == 2152398872) {
+ // in ftpCore.h 24
+ dump("***** status is FTP_PWD\n");
+ } else if (aStatus == 2152857601) {
+ dump("***** status is UNRECOGNIZED_PATH\n");
+ } else if (aStatus == 2152857602) {
+ dump("***** status is UNRESOLABLE SYMLINK\n");
+ } else if (aStatus == 2152857604) {
+ dump("***** status is UNKNOWN_TYPE\n");
+ } else if (aStatus == 2152857605) {
+ dump("***** status is DESTINATION_NOT_DIR\n");
+ } else if (aStatus == 2152857606) {
+ dump("***** status is TARGET_DOES_NOT_EXIST\n");
+ } else if (aStatus == 2152857608) {
+ dump("***** status is ALREADY_EXISTS\n");
+ } else if (aStatus == 2152857609) {
+ dump("***** status is INVALID_PATH\n");
+ } else if (aStatus == 2152857610) {
+ dump("***** status is DISK_FULL\n");
+ } else if (aStatus == 2152857612) {
+ dump("***** status is NOT_DIRECTORY\n");
+ } else if (aStatus == 2152857613) {
+ dump("***** status is IS_DIRECTORY\n");
+ } else if (aStatus == 2152857614) {
+ dump("***** status is IS_LOCKED\n");
+ } else if (aStatus == 2152857615) {
+ dump("***** status is TOO_BIG\n");
+ } else if (aStatus == 2152857616) {
+ dump("***** status is NO_DEVICE_SPACE\n");
+ } else if (aStatus == 2152857617) {
+ dump("***** status is NAME_TOO_LONG\n");
+ } else if (aStatus == 2152857618) {
+ // 80520012
+ dump("***** status is FILE_NOT_FOUND\n");
+ } else if (aStatus == 2152857619) {
+ dump("***** status is READ_ONLY\n");
+ } else if (aStatus == 2152857620) {
+ dump("***** status is DIR_NOT_EMPTY\n");
+ } else if (aStatus == 2152857621) {
+ dump("***** status is ACCESS_DENIED\n");
+ } else if (aStatus == 2152398878) {
+ dump("***** status is ? (No connection or time out?)\n");
+ } else {
+ dump("***** status is " + aStatus + "\n");
+ }
+}
+/* eslint-enable complexity */
+
+// Update any data that the user supplied in a prompt dialog
+function UpdateUsernamePasswordFromPrompt(
+ publishData,
+ username,
+ password,
+ savePassword
+) {
+ if (!publishData) {
+ return;
+ }
+
+ // Set flag to save publish data after publishing if it changed in dialog
+ // and the "SavePassword" checkbox was checked
+ // or we already had site data for this site
+ // (Thus we don't automatically create a site until user brings up Publish As dialog)
+ publishData.savePublishData =
+ (gPublishData.username != username || gPublishData.password != password) &&
+ (savePassword || !publishData.notInSiteData);
+
+ publishData.username = username;
+ publishData.password = password;
+ publishData.savePassword = savePassword;
+}
+
+const kSupportedTextMimeTypes = [
+ "text/plain",
+ "text/css",
+ "text/rdf",
+ "text/xsl",
+ "text/javascript", // obsolete type
+ "text/ecmascript", // obsolete type
+ "application/javascript",
+ "application/ecmascript",
+ "application/x-javascript", // obsolete type
+ "text/xul", // obsolete type
+ "application/vnd.mozilla.xul+xml", // obsolete type
+ "application/xhtml+xml",
+];
+
+function IsSupportedTextMimeType(aMimeType) {
+ for (var i = 0; i < kSupportedTextMimeTypes.length; i++) {
+ if (kSupportedTextMimeTypes[i] == aMimeType) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* eslint-disable complexity */
+// throws an error or returns true if user attempted save; false if user canceled save
+async function SaveDocument(aSaveAs, aSaveCopy, aMimeType) {
+ var editor = GetCurrentEditor();
+ if (!aMimeType || !editor) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED);
+ }
+
+ var editorDoc = editor.document;
+ if (!editorDoc) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED);
+ }
+
+ // if we don't have the right editor type bail (we handle text and html)
+ var editorType = GetCurrentEditorType();
+ if (!["text", "html", "htmlmail", "textmail"].includes(editorType)) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ }
+
+ var saveAsTextFile = IsSupportedTextMimeType(aMimeType);
+
+ // check if the file is to be saved is a format we don't understand; if so, bail
+ if (
+ aMimeType != kHTMLMimeType &&
+ aMimeType != kXHTMLMimeType &&
+ !saveAsTextFile
+ ) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ }
+
+ if (saveAsTextFile) {
+ aMimeType = "text/plain";
+ }
+
+ var urlstring = GetDocumentUrl();
+ var mustShowFileDialog =
+ aSaveAs || IsUrlAboutBlank(urlstring) || urlstring == "";
+
+ // If editing a remote URL, force SaveAs dialog
+ if (!mustShowFileDialog && GetScheme(urlstring) != "file") {
+ mustShowFileDialog = true;
+ }
+
+ var doUpdateURI = false;
+ var tempLocalFile = null;
+
+ if (mustShowFileDialog) {
+ try {
+ // Prompt for title if we are saving to HTML
+ if (!saveAsTextFile && editorType == "html") {
+ var userContinuing = PromptAndSetTitleIfNone(); // not cancel
+ if (!userContinuing) {
+ return false;
+ }
+ }
+
+ var dialogResult = await PromptForSaveLocation(
+ saveAsTextFile,
+ editorType,
+ aMimeType,
+ urlstring
+ );
+ if (!dialogResult) {
+ return false;
+ }
+
+ // What is this unused 'replacing' var supposed to be doing?
+ /* eslint-disable-next-line no-unused-vars */
+ var replacing =
+ dialogResult.filepickerClick == nsIFilePicker.returnReplace;
+
+ urlstring = dialogResult.resultingURIString;
+ tempLocalFile = dialogResult.resultingLocalFile;
+
+ // update the new URL for the webshell unless we are saving a copy
+ if (!aSaveCopy) {
+ doUpdateURI = true;
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ return false;
+ }
+ } // mustShowFileDialog
+
+ var success = true;
+ try {
+ // if somehow we didn't get a local file but we did get a uri,
+ // attempt to create the localfile if it's a "file" url
+ var docURI;
+ if (!tempLocalFile) {
+ docURI = Services.io.newURI(urlstring, editor.documentCharacterSet);
+
+ if (docURI.schemeIs("file")) {
+ var fileHandler = GetFileProtocolHandler();
+ tempLocalFile = fileHandler
+ .getFileFromURLSpec(urlstring)
+ .QueryInterface(Ci.nsIFile);
+ }
+ }
+
+ // this is the location where the related files will go
+ var relatedFilesDir = null;
+
+ // Only change links or move files if pref is set
+ // and we are saving to a new location
+ if (Services.prefs.getBoolPref("editor.save_associated_files") && aSaveAs) {
+ try {
+ if (tempLocalFile) {
+ // if we are saving to the same parent directory, don't set relatedFilesDir
+ // grab old location, chop off file
+ // grab new location, chop off file, compare
+ var oldLocation = GetDocumentUrl();
+ var oldLocationLastSlash = oldLocation.lastIndexOf("/");
+ if (oldLocationLastSlash != -1) {
+ oldLocation = oldLocation.slice(0, oldLocationLastSlash);
+ }
+
+ var relatedFilesDirStr = urlstring;
+ var newLocationLastSlash = relatedFilesDirStr.lastIndexOf("/");
+ if (newLocationLastSlash != -1) {
+ relatedFilesDirStr = relatedFilesDirStr.slice(
+ 0,
+ newLocationLastSlash
+ );
+ }
+ if (
+ oldLocation == relatedFilesDirStr ||
+ IsUrlAboutBlank(oldLocation)
+ ) {
+ relatedFilesDir = null;
+ } else {
+ relatedFilesDir = tempLocalFile.parent;
+ }
+ } else {
+ var lastSlash = urlstring.lastIndexOf("/");
+ if (lastSlash != -1) {
+ var relatedFilesDirString = urlstring.slice(0, lastSlash + 1); // include last slash
+ relatedFilesDir = Services.io.newURI(
+ relatedFilesDirString,
+ editor.documentCharacterSet
+ );
+ }
+ }
+ } catch (e) {
+ relatedFilesDir = null;
+ }
+ }
+
+ let destinationLocation = tempLocalFile ? tempLocalFile : docURI;
+
+ success = OutputFileWithPersistAPI(
+ editorDoc,
+ destinationLocation,
+ relatedFilesDir,
+ aMimeType
+ );
+ } catch (e) {
+ success = false;
+ }
+
+ if (success) {
+ try {
+ if (doUpdateURI) {
+ // If a local file, we must create a new uri from nsIFile
+ if (tempLocalFile) {
+ docURI = GetFileProtocolHandler().newFileURI(tempLocalFile);
+ }
+
+ // We need to set new document uri before notifying listeners
+ SetDocumentURI(docURI);
+ }
+
+ // Update window title to show possibly different filename
+ // This also covers problem that after undoing a title change,
+ // window title loses the extra [filename] part that this adds
+ UpdateWindowTitle();
+
+ if (!aSaveCopy) {
+ editor.resetModificationCount();
+ }
+ // this should cause notification to listeners that document has changed
+
+ // Set UI based on whether we're editing a remote or local url
+ SetSaveAndPublishUI(urlstring);
+ } catch (e) {}
+ } else {
+ Services.prompt.alert(
+ window,
+ GetString("SaveDocument"),
+ GetString("SaveFileFailed")
+ );
+ }
+ return success;
+}
+/* eslint-enable complexity */
+
+function SetDocumentURI(uri) {
+ try {
+ // XXX WE'LL NEED TO GET "CURRENT" CONTENT FRAME ONCE MULTIPLE EDITORS ARE ALLOWED
+ GetCurrentEditorElement().docShell.setCurrentURI(uri);
+ } catch (e) {
+ dump("SetDocumentURI:\n" + e + "\n");
+ }
+}
+
+// ------------------------------- Publishing
+var gPublishData;
+var gProgressDialog;
+var gCommandAfterPublishing = null;
+var gRestoreDocumentSource;
+
+function Publish(publishData) {
+ if (!publishData) {
+ return false;
+ }
+
+ // Set data in global for username password requests
+ // and to do "post saving" actions after monitoring nsIWebProgressListener messages
+ // and we are sure file transfer was successful
+ gPublishData = publishData;
+
+ gPublishData.docURI = CreateURIFromPublishData(publishData, true);
+ if (!gPublishData.docURI) {
+ Services.prompt.alert(
+ window,
+ GetString("Publish"),
+ GetString("PublishFailed")
+ );
+ return false;
+ }
+
+ if (gPublishData.publishOtherFiles) {
+ gPublishData.otherFilesURI = CreateURIFromPublishData(publishData, false);
+ } else {
+ gPublishData.otherFilesURI = null;
+ }
+
+ if (gShowDebugOutputStateChange) {
+ dump(
+ "\n *** publishData: PublishUrl=" +
+ publishData.publishUrl +
+ ", BrowseUrl=" +
+ publishData.browseUrl +
+ ", Username=" +
+ publishData.username +
+ ", Dir=" +
+ publishData.docDir +
+ ", Filename=" +
+ publishData.filename +
+ "\n"
+ );
+ dump(
+ " * gPublishData.docURI.spec w/o pass=" +
+ StripPassword(gPublishData.docURI.spec) +
+ ", PublishOtherFiles=" +
+ gPublishData.publishOtherFiles +
+ "\n"
+ );
+ }
+
+ // XXX Missing username will make FTP fail
+ // and it won't call us for prompt dialog (bug 132320)
+ // (It does prompt if just password is missing)
+ // So we should do the prompt ourselves before trying to publish
+ if (GetScheme(publishData.publishUrl) == "ftp" && !publishData.username) {
+ var message = GetString("PromptFTPUsernamePassword").replace(
+ /%host%/,
+ GetHost(publishData.publishUrl)
+ );
+ var savePWobj = { value: publishData.savePassword };
+ var userObj = { value: publishData.username };
+ var pwObj = { value: publishData.password };
+ if (
+ !PromptUsernameAndPassword(
+ GetString("Prompt"),
+ message,
+ savePWobj,
+ userObj,
+ pwObj
+ )
+ ) {
+ // User canceled out of dialog.
+ return false;
+ }
+
+ // Reset data in URI objects
+ gPublishData.docURI.username = publishData.username;
+ gPublishData.docURI.password = publishData.password;
+
+ if (gPublishData.otherFilesURI) {
+ gPublishData.otherFilesURI.username = publishData.username;
+ gPublishData.otherFilesURI.password = publishData.password;
+ }
+ }
+
+ try {
+ // We launch dialog as a dependent
+ // Don't allow editing document!
+ SetDocumentEditable(false);
+
+ // Start progress monitoring
+ gProgressDialog = window.openDialog(
+ "chrome://editor/content/EditorPublishProgress.xhtml",
+ "_blank",
+ "chrome,dependent,titlebar",
+ gPublishData,
+ gPersistObj
+ );
+ } catch (e) {}
+
+ // Network transfer is often too quick for the progress dialog to be initialized
+ // and we can completely miss messages for quickly-terminated bad URLs,
+ // so we can't call OutputFileWithPersistAPI right away.
+ // StartPublishing() is called at the end of the dialog's onload method
+ return true;
+}
+
+function StartPublishing() {
+ var editor = GetCurrentEditor();
+ if (editor && gPublishData && gPublishData.docURI && gProgressDialog) {
+ gRestoreDocumentSource = null;
+
+ // Save backup document since nsIWebBrowserPersist changes image src urls
+ // but we only need to do this if publishing images and other related files
+ if (gPublishData.otherFilesURI) {
+ try {
+ gRestoreDocumentSource = editor.outputToString(
+ editor.contentsMIMEType,
+ kOutputEncodeW3CEntities
+ );
+ } catch (e) {}
+ }
+
+ OutputFileWithPersistAPI(
+ editor.document,
+ gPublishData.docURI,
+ gPublishData.otherFilesURI,
+ editor.contentsMIMEType
+ );
+ return gPersistObj;
+ }
+ return null;
+}
+
+function CancelPublishing() {
+ try {
+ gPersistObj.cancelSave();
+ gProgressDialog.SetProgressStatusCancel();
+ } catch (e) {}
+
+ // If canceling publishing do not do any commands after this
+ gCommandAfterPublishing = null;
+
+ if (gProgressDialog) {
+ // Close Progress dialog
+ // (this will call FinishPublishing())
+ gProgressDialog.CloseDialog();
+ } else {
+ FinishPublishing();
+ }
+}
+
+function FinishPublishing() {
+ SetDocumentEditable(true);
+ gProgressDialog = null;
+ gPublishData = null;
+ gRestoreDocumentSource = null;
+
+ if (gCommandAfterPublishing) {
+ // Be sure to null out the global now in case of trouble when executing command
+ var command = gCommandAfterPublishing;
+ gCommandAfterPublishing = null;
+ goDoCommand(command);
+ }
+}
+
+// Create a nsIURI object filled in with all required publishing info
+function CreateURIFromPublishData(publishData, doDocUri) {
+ if (!publishData || !publishData.publishUrl) {
+ return null;
+ }
+
+ var URI;
+ try {
+ var spec = publishData.publishUrl;
+ if (doDocUri) {
+ spec += FormatDirForPublishing(publishData.docDir) + publishData.filename;
+ } else {
+ spec += FormatDirForPublishing(publishData.otherDir);
+ }
+
+ URI = Services.io.newURI(spec, GetCurrentEditor().documentCharacterSet);
+
+ if (publishData.username) {
+ URI.username = publishData.username;
+ }
+ if (publishData.password) {
+ URI.password = publishData.password;
+ }
+ } catch (e) {}
+
+ return URI;
+}
+
+// Resolve the correct "http:" document URL when publishing via ftp
+function GetDocUrlFromPublishData(publishData) {
+ if (!publishData || !publishData.filename || !publishData.publishUrl) {
+ return "";
+ }
+
+ // If user was previously editing an "ftp" url, then keep that as the new scheme
+ var url;
+
+ // Always use the "HTTP" address if available
+ // XXX Should we do some more validation here for bad urls???
+ // Let's at least check for a scheme!
+ if (!GetScheme(publishData.browseUrl)) {
+ url = publishData.publishUrl;
+ } else {
+ url = publishData.browseUrl;
+ }
+
+ url += FormatDirForPublishing(publishData.docDir) + publishData.filename;
+
+ if (GetScheme(url) == "ftp") {
+ url = InsertUsernameIntoUrl(url, publishData.username);
+ }
+
+ return url;
+}
+
+function SetSaveAndPublishUI(urlstring) {
+ // Be sure enabled state of toolbar buttons are correct
+ goUpdateCommand("cmd_save");
+ goUpdateCommand("cmd_publish");
+}
+
+function SetDocumentEditable(isDocEditable) {
+ var editor = GetCurrentEditor();
+ if (editor && editor.document) {
+ try {
+ var flags = editor.flags;
+ editor.flags = isDocEditable
+ ? (flags &= ~Ci.nsIEditor.eEditorReadonlyMask)
+ : flags | Ci.nsIEditor.eEditorReadonlyMask;
+ } catch (e) {}
+
+ // update all commands
+ window.updateCommands("create");
+ }
+}
+
+// ****** end of save / publish **********//
+
+var nsPublishSettingsCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ if (GetCurrentEditor()) {
+ // Launch Publish Settings dialog
+
+ window.ok = window.openDialog(
+ "chrome://editor/content/EditorPublishSettings.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ ""
+ );
+ return window.ok;
+ }
+ return false;
+ },
+};
+
+var nsRevertCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return (
+ IsDocumentEditable() &&
+ IsDocumentModified() &&
+ !IsUrlAboutBlank(GetDocumentUrl())
+ );
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // Confirm with the user to abandon current changes
+ // Put the page title in the message string
+ let title = GetDocumentTitle();
+ let msg = GetString("AbandonChanges").replace(/%title%/, title);
+
+ let result = Services.prompt.confirmEx(
+ window,
+ GetString("RevertCaption"),
+ msg,
+ Services.prompt.BUTTON_TITLE_REVERT * Services.prompt.BUTTON_POS_0 +
+ Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1,
+ null,
+ null,
+ null,
+ null,
+ { value: 0 }
+ );
+
+ // Reload page if first button (Revert) was pressed
+ if (result == 0) {
+ CancelHTMLSource();
+ EditorLoadUrl(GetDocumentUrl());
+ }
+ },
+};
+
+var nsCloseCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return GetCurrentEditor() != null;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ CloseWindow();
+ },
+};
+
+async function CloseWindow() {
+ // Check to make sure document is saved. "true" means allow "Don't Save" button,
+ // so user can choose to close without saving
+ if (await CheckAndSaveDocument("cmd_close", true)) {
+ if (window.InsertCharWindow) {
+ SwitchInsertCharToAnotherEditorOrClose();
+ }
+
+ try {
+ var basewin = window
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIBaseWindow);
+ basewin.destroy();
+ } catch (e) {}
+ }
+}
+
+var nsOpenRemoteCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ // We can always do this.
+ return true;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var params = { action: "2", url: "" };
+ openDialog(
+ "chrome://communicator/content/openLocation.xhtml",
+ "_blank",
+ "chrome,modal,titlebar",
+ params
+ );
+ var win = getTopWin();
+ switch (params.action) {
+ case "0": // current window
+ win.focus();
+ win.loadURI(params.url, null, null, true);
+ break;
+ case "1": // new window
+ openDialog(
+ getBrowserURL(),
+ "_blank",
+ "all,dialog=no",
+ params.url,
+ null,
+ null,
+ null,
+ true
+ );
+ break;
+ case "2": // edit
+ editPage(params.url);
+ break;
+ case "3": // new tab
+ win.focus();
+ var browser = win.getBrowser();
+ browser.selectedTab = browser.addTab(params.url, {
+ allowThirdPartyFixup: true,
+ });
+ break;
+ case "4": // private
+ openNewPrivateWith(params.url);
+ break;
+ default:
+ break;
+ }
+ },
+};
+
+var nsPreviewCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return (
+ IsDocumentEditable() &&
+ IsHTMLEditor() &&
+ (DocumentHasBeenSaved() || IsDocumentModified())
+ );
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ async doCommand(aCommand) {
+ // Don't continue if user canceled during prompt for saving
+ // DocumentHasBeenSaved will test if we have a URL and suppress "Don't Save" button if not
+ if (!(await CheckAndSaveDocument("cmd_preview", DocumentHasBeenSaved()))) {
+ return;
+ }
+
+ // Check if we saved again just in case?
+ if (DocumentHasBeenSaved()) {
+ let browser;
+ try {
+ // Find a browser with this URL
+ let enumerator = Services.wm.getEnumerator("navigator:browser");
+
+ var documentURI = GetDocumentUrl();
+ while (enumerator.hasMoreElements()) {
+ browser = enumerator.getNext();
+ if (
+ browser &&
+ !browser.closed &&
+ documentURI == browser.getBrowser().currentURI.spec
+ ) {
+ break;
+ }
+
+ browser = null;
+ }
+ } catch (ex) {}
+
+ // If none found, open a new browser
+ if (!browser) {
+ browser = window.openDialog(
+ getBrowserURL(),
+ "_blank",
+ "chrome,all,dialog=no",
+ documentURI
+ );
+ } else {
+ try {
+ browser.BrowserReloadSkipCache();
+ browser.focus();
+ } catch (ex) {}
+ }
+ }
+ },
+};
+
+var nsSendPageCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return (
+ IsDocumentEditable() && (DocumentHasBeenSaved() || IsDocumentModified())
+ );
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ async doCommand(aCommand) {
+ // Don't continue if user canceled during prompt for saving
+ // DocumentHasBeenSaved will test if we have a URL and suppress "Don't Save" button if not
+ if (
+ !(await CheckAndSaveDocument("cmd_editSendPage", DocumentHasBeenSaved()))
+ ) {
+ return;
+ }
+
+ // Check if we saved again just in case?
+ if (DocumentHasBeenSaved()) {
+ // Launch Messenger Composer window with current page as contents
+ try {
+ openComposeWindow(GetDocumentUrl(), GetDocumentTitle());
+ } catch (ex) {
+ dump("Cannot Send Page: " + ex + "\n");
+ }
+ }
+ },
+};
+
+var nsPrintCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return true; // we can always do this
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // In editor.js
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ try {
+ let browser = GetCurrentEditorElement();
+ PrintUtils.printWindow(browser.outerWindowID, browser);
+ } catch (e) {}
+ },
+};
+
+var nsPrintPreviewCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ // We can always do this.
+ return true;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // In editor.js
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ try {
+ PrintUtils.printPreview("editor", PrintPreviewListener);
+ } catch (e) {}
+ },
+};
+
+var nsPrintSetupCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return true; // we can always do this
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // In editor.js
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ PrintUtils.showPageSetup();
+ },
+};
+
+var nsFindReplaceCommand = {
+ isCommandEnabled(aCommand, editorElement) {
+ return editorElement.getEditor(editorElement.contentWindow) != null;
+ },
+
+ getCommandStateParams(aCommand, aParams, editorElement) {},
+ doCommandParams(aCommand, aParams, editorElement) {},
+
+ doCommand(aCommand, editorElement) {
+ window.openDialog(
+ "chrome://editor/content/EdReplace.xhtml",
+ "_blank",
+ "chrome,modal,titlebar",
+ editorElement
+ );
+ },
+};
+
+var nsFindCommand = {
+ isCommandEnabled(aCommand, editorElement) {
+ return editorElement.getEditor(editorElement.contentWindow) != null;
+ },
+
+ getCommandStateParams(aCommand, aParams, editorElement) {},
+ doCommandParams(aCommand, aParams, editorElement) {},
+
+ doCommand(aCommand, editorElement) {
+ document.getElementById("FindToolbar").onFindCommand();
+ },
+};
+
+var nsFindAgainCommand = {
+ isCommandEnabled(aCommand, editorElement) {
+ // we can only do this if the search pattern is non-empty. Not sure how
+ // to get that from here
+ return editorElement.getEditor(editorElement.contentWindow) != null;
+ },
+
+ getCommandStateParams(aCommand, aParams, editorElement) {},
+ doCommandParams(aCommand, aParams, editorElement) {},
+
+ doCommand(aCommand, editorElement) {
+ let findPrev = aCommand == "cmd_findPrev";
+ document.getElementById("FindToolbar").onFindAgainCommand(findPrev);
+ },
+};
+
+var nsRewrapCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return (
+ IsDocumentEditable() &&
+ !IsInHTMLSourceMode() &&
+ GetCurrentEditor() instanceof Ci.nsIEditorMailSupport
+ );
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // We only want to respect new lines when using the web composer.
+ let respectNewLines = IsWebComposer();
+ GetCurrentEditor()
+ .QueryInterface(Ci.nsIEditorMailSupport)
+ .rewrap(respectNewLines);
+ },
+};
+
+var nsSpellingCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return (
+ IsDocumentEditable() && !IsInHTMLSourceMode() && IsSpellCheckerInstalled()
+ );
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.cancelSendMessage = false;
+ try {
+ var skipBlockQuotes =
+ window.document.documentElement.getAttribute("windowtype") ==
+ "msgcompose";
+ window.openDialog(
+ "chrome://editor/content/EdSpellCheck.xhtml",
+ "_blank",
+ "dialog,close,titlebar,modal,resizable",
+ false,
+ skipBlockQuotes,
+ true
+ );
+ } catch (ex) {}
+ },
+};
+
+// Validate using http://validator.w3.org/file-upload.html
+var URL2Validate;
+var nsValidateCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return GetCurrentEditor() != null;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ async doCommand(aCommand) {
+ // If the document hasn't been modified,
+ // then just validate the current url.
+ if (IsDocumentModified() || IsHTMLSourceChanged()) {
+ if (!(await CheckAndSaveDocument("cmd_validate", false))) {
+ return;
+ }
+
+ // Check if we saved again just in case?
+ if (!DocumentHasBeenSaved()) {
+ // user hit cancel?
+ return;
+ }
+ }
+
+ URL2Validate = GetDocumentUrl();
+ // See if it's a file:
+ var ifile;
+ try {
+ var fileHandler = GetFileProtocolHandler();
+ ifile = fileHandler.getFileFromURLSpec(URL2Validate);
+ // nsIFile throws an exception if it's not a file url
+ } catch (e) {
+ ifile = null;
+ }
+ if (ifile) {
+ URL2Validate = ifile.path;
+ var vwin = window.open(
+ "http://validator.w3.org/file-upload.html",
+ "EditorValidate"
+ );
+ // Window loads asynchronously, so pass control to the load listener:
+ vwin.addEventListener("load", this.validateFilePageLoaded);
+ } else {
+ window.open(
+ `http://validator.w3.org/check?uri=${URL2Validate}&doctype=Inline`,
+ "EditorValidate"
+ );
+ // This does the validation, no need to wait for page loaded.
+ }
+ },
+ validateFilePageLoaded(event) {
+ event.target.forms[0].uploaded_file.value = URL2Validate;
+ },
+};
+
+var nsFormCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdFormProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsInputTagCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdInputProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsInputImageCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdInputImage.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsTextAreaCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdTextAreaProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsSelectCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdSelectProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsButtonCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdButtonProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsLabelCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var tagName = "label";
+ try {
+ var editor = GetCurrentEditor();
+ // Find selected label or if start/end of selection is in label
+ var labelElement = editor.getSelectedElement(tagName);
+ if (!labelElement) {
+ labelElement = editor.getElementOrParentByTagName(
+ tagName,
+ editor.selection.anchorNode
+ );
+ }
+ if (!labelElement) {
+ labelElement = editor.getElementOrParentByTagName(
+ tagName,
+ editor.selection.focusNode
+ );
+ }
+ if (labelElement) {
+ // We only open the dialog for an existing label
+ window.openDialog(
+ "chrome://editor/content/EdLabelProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ labelElement
+ );
+ } else {
+ EditorSetTextProperty(tagName, "", "");
+ }
+ } catch (e) {}
+ },
+};
+
+var nsFieldSetCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdFieldSetProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsImageCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdImageProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsHLineCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // Inserting an HLine is different in that we don't use properties dialog
+ // unless we are editing an existing line's attributes
+ // We get the last-used attributes from the prefs and insert immediately
+
+ var tagName = "hr";
+ var editor = GetCurrentEditor();
+
+ var hLine;
+ try {
+ hLine = editor.getSelectedElement(tagName);
+ } catch (e) {
+ return;
+ }
+
+ if (hLine) {
+ // We only open the dialog for an existing HRule
+ window.openDialog(
+ "chrome://editor/content/EdHLineProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ } else {
+ try {
+ hLine = editor.createElementWithDefaults(tagName);
+
+ // We change the default attributes to those saved in the user prefs
+ let align = Services.prefs.getIntPref("editor.hrule.align");
+ if (align == 0) {
+ editor.setAttributeOrEquivalent(hLine, "align", "left", true);
+ } else if (align == 2) {
+ editor.setAttributeOrEquivalent(hLine, "align", "right", true);
+ }
+
+ // Note: Default is center (don't write attribute)
+
+ let width = Services.prefs.getIntPref("editor.hrule.width");
+ if (Services.prefs.getBoolPref("editor.hrule.width_percent")) {
+ width = width + "%";
+ }
+
+ editor.setAttributeOrEquivalent(hLine, "width", width, true);
+
+ let height = Services.prefs.getIntPref("editor.hrule.height");
+ editor.setAttributeOrEquivalent(hLine, "size", String(height), true);
+
+ if (Services.prefs.getBoolPref("editor.hrule.shading")) {
+ hLine.removeAttribute("noshade");
+ } else {
+ hLine.setAttribute("noshade", "noshade");
+ }
+
+ editor.insertElementAtSelection(hLine, true);
+ } catch (e) {}
+ }
+ },
+};
+
+var nsLinkCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // If selected element is an image, launch that dialog instead
+ // since last tab panel handles link around an image
+ var element = GetObjectForProperties();
+ if (element && element.nodeName.toLowerCase() == "img") {
+ window.openDialog(
+ "chrome://editor/content/EdImageProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ null,
+ true
+ );
+ } else {
+ window.openDialog(
+ "chrome://editor/content/EdLinkProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ }
+ },
+};
+
+var nsAnchorCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdNamedAnchorProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ ""
+ );
+ },
+};
+
+var nsInsertHTMLWithDialogCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdInsSrc.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal,resizable",
+ ""
+ );
+ },
+};
+
+var nsInsertMathWithDialogCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdInsertMath.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal,resizable",
+ ""
+ );
+ },
+};
+
+var nsInsertCharsCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ EditorFindOrCreateInsertCharWindow();
+ },
+};
+
+var nsInsertBreakCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentEditor().insertHTML("<br>");
+ } catch (e) {}
+ },
+};
+
+var nsInsertBreakAllCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentEditor().insertHTML("<br clear='all'>");
+ } catch (e) {}
+ },
+};
+
+var nsGridCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdSnapToGrid.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsListPropertiesCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdListProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsPagePropertiesCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var oldTitle = GetDocumentTitle();
+ window.openDialog(
+ "chrome://editor/content/EdPageProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ ""
+ );
+
+ // Update main window title and
+ // recent menu data in prefs if doc title changed
+ if (GetDocumentTitle() != oldTitle) {
+ UpdateWindowTitle();
+ }
+ },
+};
+
+var nsObjectPropertiesCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ var isEnabled = false;
+ if (IsDocumentEditable() && IsEditingRenderedHTML()) {
+ isEnabled =
+ GetObjectForProperties() != null ||
+ GetCurrentEditor().getSelectedElement("href") != null;
+ }
+ return isEnabled;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // Launch Object properties for appropriate selected element
+ var element = GetObjectForProperties();
+ if (element) {
+ var name = element.nodeName.toLowerCase();
+ switch (name) {
+ case "img":
+ goDoCommand("cmd_image");
+ break;
+ case "hr":
+ goDoCommand("cmd_hline");
+ break;
+ case "form":
+ goDoCommand("cmd_form");
+ break;
+ case "input":
+ var type = element.getAttribute("type");
+ if (type && type.toLowerCase() == "image") {
+ goDoCommand("cmd_inputimage");
+ } else {
+ goDoCommand("cmd_inputtag");
+ }
+ break;
+ case "textarea":
+ goDoCommand("cmd_textarea");
+ break;
+ case "select":
+ goDoCommand("cmd_select");
+ break;
+ case "button":
+ goDoCommand("cmd_button");
+ break;
+ case "label":
+ goDoCommand("cmd_label");
+ break;
+ case "fieldset":
+ goDoCommand("cmd_fieldset");
+ break;
+ case "table":
+ EditorInsertOrEditTable(false);
+ break;
+ case "td":
+ case "th":
+ EditorTableCellProperties();
+ break;
+ case "ol":
+ case "ul":
+ case "dl":
+ case "li":
+ goDoCommand("cmd_listProperties");
+ break;
+ case "a":
+ if (element.name) {
+ goDoCommand("cmd_anchor");
+ } else if (element.href) {
+ goDoCommand("cmd_link");
+ }
+ break;
+ case "math":
+ goDoCommand("cmd_insertMathWithDialog");
+ break;
+ default:
+ doAdvancedProperties(element);
+ break;
+ }
+ } else {
+ // We get a partially-selected link if asked for specifically
+ try {
+ element = GetCurrentEditor().getSelectedElement("href");
+ } catch (e) {}
+ if (element) {
+ goDoCommand("cmd_link");
+ }
+ }
+ },
+};
+
+var nsSetSmiley = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {
+ var smileyCode = aParams.getStringValue("state_attribute");
+
+ var strSml;
+ switch (smileyCode) {
+ case ":-)":
+ strSml = "s1";
+ break;
+ case ":-(":
+ strSml = "s2";
+ break;
+ case ";-)":
+ strSml = "s3";
+ break;
+ case ":-P":
+ case ":-p":
+ case ":-b":
+ strSml = "s4";
+ break;
+ case ":-D":
+ strSml = "s5";
+ break;
+ case ":-[":
+ strSml = "s6";
+ break;
+ case ":-/":
+ case ":/":
+ case ":-\\":
+ case ":\\":
+ strSml = "s7";
+ break;
+ case "=-O":
+ case "=-o":
+ strSml = "s8";
+ break;
+ case ":-*":
+ strSml = "s9";
+ break;
+ case ">:o":
+ case ">:-o":
+ strSml = "s10";
+ break;
+ case "8-)":
+ strSml = "s11";
+ break;
+ case ":-$":
+ strSml = "s12";
+ break;
+ case ":-!":
+ strSml = "s13";
+ break;
+ case "O:-)":
+ case "o:-)":
+ strSml = "s14";
+ break;
+ case ":'(":
+ strSml = "s15";
+ break;
+ case ":-X":
+ case ":-x":
+ strSml = "s16";
+ break;
+ default:
+ strSml = "";
+ break;
+ }
+
+ try {
+ var editor = GetCurrentEditor();
+ var extElement = editor.createElementWithDefaults("span");
+ extElement.setAttribute("class", "moz-smiley-" + strSml);
+
+ var intElement = editor.createElementWithDefaults("span");
+ if (!intElement) {
+ return;
+ }
+
+ var txtElement = editor.document.createTextNode(smileyCode);
+ if (!txtElement) {
+ return;
+ }
+
+ intElement.appendChild(txtElement);
+ extElement.appendChild(intElement);
+
+ editor.insertElementAtSelection(extElement, true);
+ window.content.focus();
+ } catch (e) {
+ dump("Exception occurred in smiley InsertElementAtSelection\n");
+ }
+ },
+ // This is now deprecated in favor of "doCommandParams"
+ doCommand(aCommand) {},
+};
+
+function doAdvancedProperties(element) {
+ if (element) {
+ window.openDialog(
+ "chrome://editor/content/EdAdvancedEdit.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal,resizable=yes",
+ "",
+ element
+ );
+ }
+}
+
+var nsAdvancedPropertiesCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // Launch AdvancedEdit dialog for the selected element
+ try {
+ var element = GetCurrentEditor().getSelectedElement("");
+ doAdvancedProperties(element);
+ } catch (e) {}
+ },
+};
+
+var nsColorPropertiesCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdColorProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ ""
+ );
+ UpdateDefaultColors();
+ },
+};
+
+var nsIncreaseFontCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ if (!(IsDocumentEditable() && IsEditingRenderedHTML())) {
+ return false;
+ }
+ var setIndex = getFontSizeIndex();
+ return setIndex >= 0 && setIndex < 5;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var setIndex = getFontSizeIndex();
+ if (setIndex < 0 || setIndex >= 5) {
+ return;
+ }
+ var sizes = ["x-small", "small", "medium", "large", "x-large", "xx-large"];
+ EditorSetFontSize(sizes[setIndex + 1]);
+ },
+};
+
+var nsDecreaseFontCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ if (!(IsDocumentEditable() && IsEditingRenderedHTML())) {
+ return false;
+ }
+ var setIndex = getFontSizeIndex();
+ return setIndex > 0;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var setIndex = getFontSizeIndex();
+ if (setIndex <= 0) {
+ return;
+ }
+ var sizes = ["x-small", "small", "medium", "large", "x-large", "xx-large"];
+ EditorSetFontSize(sizes[setIndex - 1]);
+ },
+};
+
+var nsRemoveNamedAnchorsCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ // We could see if there's any link in selection, but it doesn't seem worth the work!
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ EditorRemoveTextProperty("name", "");
+ window.content.focus();
+ },
+};
+
+var nsEditLinkCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ // Not really used -- this command is only in context menu, and we do enabling there
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ var element = GetCurrentEditor().getSelectedElement("href");
+ if (element) {
+ editPage(element.href);
+ }
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsNormalModeCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsHTMLEditor() && IsDocumentEditable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ SetEditMode(kDisplayModeNormal);
+ },
+};
+
+var nsAllTagsModeCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsHTMLEditor();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ SetEditMode(kDisplayModeAllTags);
+ },
+};
+
+var nsHTMLSourceModeCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsHTMLEditor();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ SetEditMode(kDisplayModeSource);
+ },
+};
+
+var nsPreviewModeCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsHTMLEditor();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ SetEditMode(kDisplayModePreview);
+ },
+};
+
+var nsInsertOrEditTableCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ if (IsInTableCell()) {
+ EditorTableCellProperties();
+ } else {
+ EditorInsertOrEditTable(true);
+ }
+ },
+};
+
+var nsEditTableCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ EditorInsertOrEditTable(false);
+ },
+};
+
+var nsSelectTableCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().selectTable();
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsSelectTableRowCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().selectTableRow();
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsSelectTableColumnCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().selectTableColumn();
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsSelectTableCellCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().selectTableCell();
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsSelectAllTableCellsCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().selectAllTableCells();
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsInsertTableCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ EditorInsertTable();
+ },
+};
+
+var nsInsertTableRowAboveCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().insertTableRow(1, false);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsInsertTableRowBelowCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().insertTableRow(1, true);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsInsertTableColumnBeforeCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().insertTableColumn(1, false);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsInsertTableColumnAfterCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().insertTableColumn(1, true);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsInsertTableCellBeforeCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().insertTableCell(1, false);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsInsertTableCellAfterCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().insertTableCell(1, true);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsDeleteTableCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().deleteTable();
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsDeleteTableRowCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var rows = GetNumberOfContiguousSelectedRows();
+ // Delete at least one row
+ if (rows == 0) {
+ rows = 1;
+ }
+
+ try {
+ var editor = GetCurrentTableEditor();
+ editor.beginTransaction();
+
+ // Loop to delete all blocks of contiguous, selected rows
+ while (rows) {
+ editor.deleteTableRow(rows);
+ rows = GetNumberOfContiguousSelectedRows();
+ }
+ } finally {
+ editor.endTransaction();
+ }
+ window.content.focus();
+ },
+};
+
+var nsDeleteTableColumnCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var columns = GetNumberOfContiguousSelectedColumns();
+ // Delete at least one column
+ if (columns == 0) {
+ columns = 1;
+ }
+
+ try {
+ var editor = GetCurrentTableEditor();
+ editor.beginTransaction();
+
+ // Loop to delete all blocks of contiguous, selected columns
+ while (columns) {
+ editor.deleteTableColumn(columns);
+ columns = GetNumberOfContiguousSelectedColumns();
+ }
+ } finally {
+ editor.endTransaction();
+ }
+ window.content.focus();
+ },
+};
+
+var nsDeleteTableCellCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().deleteTableCell(1);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsDeleteTableCellContentsCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().deleteTableCellContents();
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsNormalizeTableCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // Use nullptr to let editor find table enclosing current selection
+ try {
+ GetCurrentTableEditor().normalizeTable(null);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsJoinTableCellsCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ if (IsDocumentEditable() && IsEditingRenderedHTML()) {
+ try {
+ var editor = GetCurrentTableEditor();
+ var tagNameObj = { value: "" };
+ var countObj = { value: 0 };
+ var cell = editor.getSelectedOrParentTableElement(tagNameObj, countObj);
+
+ // We need a cell and either > 1 selected cell or a cell to the right
+ // (this cell may originate in a row spanned from above current row)
+ // Note that editor returns "td" for "th" also.
+ // (this is a pain! Editor and gecko use lowercase tagNames, JS uses uppercase!)
+ if (cell && tagNameObj.value == "td") {
+ // Selected cells
+ if (countObj.value > 1) {
+ return true;
+ }
+
+ var colSpan = cell.getAttribute("colspan");
+
+ // getAttribute returns string, we need number
+ // no attribute means colspan = 1
+ if (!colSpan) {
+ colSpan = Number(1);
+ } else {
+ colSpan = Number(colSpan);
+ }
+
+ var rowObj = { value: 0 };
+ var colObj = { value: 0 };
+ editor.getCellIndexes(cell, rowObj, colObj);
+
+ // Test if cell exists to the right of current cell
+ // (cells with 0 span should never have cells to the right
+ // if there is, user can select the 2 cells to join them)
+ return (
+ colSpan &&
+ editor.getCellAt(null, rowObj.value, colObj.value + colSpan)
+ );
+ }
+ } catch (e) {}
+ }
+ return false;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // Param: Don't merge non-contiguous cells
+ try {
+ GetCurrentTableEditor().joinTableCells(false);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsSplitTableCellCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ if (IsDocumentEditable() && IsEditingRenderedHTML()) {
+ var tagNameObj = { value: "" };
+ var countObj = { value: 0 };
+ var cell;
+ try {
+ cell = GetCurrentTableEditor().getSelectedOrParentTableElement(
+ tagNameObj,
+ countObj
+ );
+ } catch (e) {}
+
+ // We need a cell parent and there's just 1 selected cell
+ // or selection is entirely inside 1 cell
+ if (
+ cell &&
+ tagNameObj.value == "td" &&
+ countObj.value <= 1 &&
+ IsSelectionInOneCell()
+ ) {
+ var colSpan = cell.getAttribute("colspan");
+ var rowSpan = cell.getAttribute("rowspan");
+ if (!colSpan) {
+ colSpan = 1;
+ }
+ if (!rowSpan) {
+ rowSpan = 1;
+ }
+ return colSpan > 1 || rowSpan > 1 || colSpan == 0 || rowSpan == 0;
+ }
+ }
+ return false;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().splitTableCell();
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsTableOrCellColorCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ EditorSelectColor("TableOrCell");
+ },
+};
+
+var nsPreferencesCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return true;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ goPreferences("composer_pane");
+ },
+};
+
+var nsFinishHTMLSource = {
+ isCommandEnabled(aCommand, dummy) {
+ return true;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // In editor.js
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ },
+};
+
+var nsCancelHTMLSource = {
+ isCommandEnabled(aCommand, dummy) {
+ return true;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // In editor.js
+ CancelHTMLSource();
+ },
+};
+
+var nsConvertToTable = {
+ isCommandEnabled(aCommand, dummy) {
+ if (IsDocumentEditable() && IsEditingRenderedHTML()) {
+ var selection;
+ try {
+ selection = GetCurrentEditor().selection;
+ } catch (e) {}
+
+ if (selection && !selection.isCollapsed) {
+ // Don't allow if table or cell is the selection
+ var element;
+ try {
+ element = GetCurrentEditor().getSelectedElement("");
+ } catch (e) {}
+ if (element) {
+ var name = element.nodeName.toLowerCase();
+ if (
+ name == "td" ||
+ name == "th" ||
+ name == "caption" ||
+ name == "table"
+ ) {
+ return false;
+ }
+ }
+
+ // Selection start and end must be in the same cell
+ // in same cell or both are NOT in a cell
+ if (
+ GetParentTableCell(selection.focusNode) !=
+ GetParentTableCell(selection.anchorNode)
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+ return false;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ if (this.isCommandEnabled()) {
+ window.openDialog(
+ "chrome://editor/content/EdConvertToTable.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ }
+ },
+};
diff --git a/comm/suite/editor/base/content/EditorAllTags.css b/comm/suite/editor/base/content/EditorAllTags.css
new file mode 100644
index 0000000000..656dffcdee
--- /dev/null
+++ b/comm/suite/editor/base/content/EditorAllTags.css
@@ -0,0 +1,802 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Styles to alter look of things in the Editor content window
+ * for the "All Tags Edit Mode" Every HTML tag shows up as an icon.
+*/
+
+/* For "userdefined" or "unknown" tags
+ (Note that "_" must be escaped)
+*/
+
+*:not(a):not(abbr):not(acronym):not(address):not(applet):not(area):not(b):not(base):not(basefont):not(bdo):not(bgsound):not(big):not(blink):not(blockquote):not(body):not(br):not(button):not(canvas):not(caption):not(center):not(cite):not(code):not(col):not(colgroup):not(dd):not(del):not(dfn):not(dir):not(div):not(dl):not(dt):not(em):not(embed):not(fieldset):not(font):not(form):not(frame):not(frameset):not(h1):not(h2):not(h3):not(h4):not(h5):not(h6):not(head):not(hr):not(html):not(i):not(iframe):not(image):not(img):not(input):not(ins):not(isindex):not(kbd):not(keygen):not(label):not(legend):not(li):not(link):not(listing):not(map):not(marquee):not(menu):not(meta):not(multicol):not(nobr):not(noembed):not(noframes):not(noscript):not(object):not(ol):not(optgroup):not(option):not(p):not(param):not(plaintext):not(pre):not(q):not(s):not(samp):not(script):not(select):not(server):not(small):not(sound):not(spacer):not(span):not(strike):not(strong):not(style):not(sub):not(sup):not(table):not(tbody):not(td):not(textarea):not(tfoot):not(th):not(thead):not(title):not(tr):not(tt):not(u):not(ul):not(var):not(wbr):not(xmp) {
+ display: inline;
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 16px;
+ background-image: url(chrome://editor/content/images/tag-userdefined.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+a:not([\_moz_anonclass]) {
+ min-height: 16px; margin-left: 2px; margin-top: 2px;
+ padding-left: 20px;
+ background-image: url(chrome://editor/content/images/tag-a.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+abbr {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 35px;
+ background-image: url(chrome://editor/content/images/tag-abr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+
+acronym {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 57px;
+ background-image: url(chrome://editor/content/images/tag-acr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+address {
+ min-height: 44px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-adr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+applet {
+ min-height: 35px; margin-top: 2px;
+ padding-left: 47px;
+ background-image: url(chrome://editor/content/images/tag-app.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+area {
+ min-height: 35px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-ara.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+b {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 20px;
+ background-image: url(chrome://editor/content/images/tag-b.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+basefont {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 57px;
+ background-image: url(chrome://editor/content/images/tag-bsf.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+bdo {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-bdo.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+big {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-big.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+blockquote {
+ min-height: 44px; margin-left: 2px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-blq.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+body {
+ min-height: 36px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-body.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+br {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 23px;
+ background-image: url(chrome://editor/content/images/tag-br.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+button {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 57px;
+ background-image: url(chrome://editor/content/images/tag-btn.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+caption {
+ min-height: 35px; margin-top: 2px;
+ padding-left: 55px;
+ background-image: url(chrome://editor/content/images/tag-cpt.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+center {
+ min-height: 44px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-ctr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+cite {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-cit.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+code {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-cod.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+col {
+ min-height: 35px; margin-left: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-col.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+colgroup {
+ min-height: 35px; margin-left: 2px;
+ padding-left: 51px;
+ background-image: url(chrome://editor/content/images/tag-clg.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+dd {
+ min-height: 35px; margin-top: 2px;
+ padding-left: 23px;
+ background-image: url(chrome://editor/content/images/tag-dd.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+del {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-del.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+dfn {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-dfn.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+dir {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-dir.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+div {
+ min-height: 24px; margin-top: 2px;
+ /* TEMPORARY TO COMPENSATE FOR BUG */
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-div.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+input div {
+ min-height: 0px; margin-left: 0px; margin-top: 0px;
+ padding-left: 0px;
+ background-image: none;
+}
+
+dl {
+ min-height: 20px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-dl.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+dt {
+ min-height: 35px; margin-top: 2px;
+ padding-left: 23px;
+ background-image: url(chrome://editor/content/images/tag-dt.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+em {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 23px;
+ background-image: url(chrome://editor/content/images/tag-em.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+fieldset {
+ min-height: 44px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-fld.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+font {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-fnt.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+form {
+ min-height: 36px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-for.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+frame {
+ min-height: 40px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-frm.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+frameset {
+ min-height: 44px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-fst.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+h1 {
+ min-height: 20px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-h1.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+h2 {
+ min-height: 20px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-h2.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+h3 {
+ min-height: 20px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-h3.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+h4 {
+ min-height: 20px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-h4.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+h5 {
+ min-height: 20px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-h5.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+h6 {
+ min-height: 20px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-h6.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+hr {
+ min-height: 20px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-hr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+i {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 20px;
+ background-image: url(chrome://editor/content/images/tag-i.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+iframe {
+ min-height: 35px; margin-left: 2px;
+ padding-left: 47px;
+ background-image: url(chrome://editor/content/images/tag-ifr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+img:not([\_moz_anonclass]) {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-img.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+input {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-inp.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+ins {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-ins.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+isindex {
+ min-height: 40px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-isx.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+kbd {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-kbd.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+label {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-lbl.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+legend {
+ min-height: 35px; margin-top: 2px;
+ padding-left: 49px;
+ background-image: url(chrome://editor/content/images/tag-lgn.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+li {
+ min-height: 35px; margin-top: 2px;
+ padding-left: 23px;
+ background-image: url(chrome://editor/content/images/tag-li.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+listing {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 57px;
+ background-image: url(chrome://editor/content/images/tag-lst.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+map {
+ min-height: 35px; margin-left: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-map.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+menu {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-men.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+nobr {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-nbr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+noframes {
+ min-height: 44px; margin-left: 2px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-nfr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+noscript {
+ min-height: 44px; margin-left: 2px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-nsc.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+object {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 49px;
+ background-image: url(chrome://editor/content/images/tag-obj.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+ol {
+ min-height: 38px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-ol.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+optgroup {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 51px;
+ background-image: url(chrome://editor/content/images/tag-opg.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+option {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 47px;
+ background-image: url(chrome://editor/content/images/tag-opt.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+p {
+ min-height: 38px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-p.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+param {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 43px;
+ background-image: url(chrome://editor/content/images/tag-prm.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+plaintext {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 57px;
+ background-image: url(chrome://editor/content/images/tag-pln.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+pre {
+ min-height: 24px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-pre.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+q {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 20px;
+ background-image: url(chrome://editor/content/images/tag-q.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+s {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 20px;
+ background-image: url(chrome://editor/content/images/tag-s.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+samp {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-smp.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+script {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 45px;
+ background-image: url(chrome://editor/content/images/tag-scr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+select {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 47px;
+ background-image: url(chrome://editor/content/images/tag-slc.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+small {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 41px;
+ background-image: url(chrome://editor/content/images/tag-sml.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+span:not([\_moz_anonclass]) {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ /* TEMPORARY TO COMPENSATE FOR BUG */
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-spn.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+strike {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 45px;
+ background-image: url(chrome://editor/content/images/tag-stk.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+strong {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 51px;
+ background-image: url(chrome://editor/content/images/tag-stn.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+sub {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-sub.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+sup {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-sup.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+/* The background image technique is not working for
+ some table elements. Trying the "before" strategy
+*/
+
+table {
+ min-height: 40px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-tbl.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+tbody {
+ min-height: 42px; margin-left: 2px; margin-top: 1px;
+ padding-left: 17px;
+ content: url(chrome://editor/content/images/tag-tbd.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+td {
+ min-height: 22px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-td.png);
+
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+textarea {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 59px;
+ background-image: url(chrome://editor/content/images/tag-txt.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+tfoot {
+ min-height: 42px; margin-left: 2px; margin-top: 1px;
+ padding-left: 17px;
+ content: url(chrome://editor/content/images/tag-tft.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+th {
+ min-height: 22px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-th.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+thead {
+ min-height: 42px; margin-left: 2px; margin-top: 1px;
+ padding-left: 17px;
+ content: url(chrome://editor/content/images/tag-thd.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+tr {
+ min-height: 22px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-tr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+tt {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 23px;
+ background-image: url(chrome://editor/content/images/tag-tt.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+u {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 23px;
+ background-image: url(chrome://editor/content/images/tag-u.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+ul {
+ min-height: 20px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-ul.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+var {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-var.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+xmp {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-xmp.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+
+/* These are tags that we DON'T want to show icons for.
+ We have images for them in case we want to utilize them
+ for some other purpose than the "All Tags" editor mode
+
+html {
+ min-height: 36px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-html.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+head {
+ min-height: 36px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-hed.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+These are tags that are ONLY allowed as children of HEAD:
+
+title {
+ min-height: 40px; margin-left: 2px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-ttl.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+base {
+ min-height: 36px; margin-left: 2px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-bas.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+style {
+ min-height: 40px; margin-left: 2px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-stl.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+meta {
+ min-height: 36px; margin-left: 2px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-met.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+link {
+ min-height: 30px; margin-left: 2px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-lnk.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+*/
diff --git a/comm/suite/editor/base/content/EditorContent.css b/comm/suite/editor/base/content/EditorContent.css
new file mode 100644
index 0000000000..fee8af21de
--- /dev/null
+++ b/comm/suite/editor/base/content/EditorContent.css
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Styles to alter look of things in the Editor content window
+ * for the "Normal Edit Mode" These settings will be removed
+ * when we display in completely WYSIWYG "Edit Preview" mode
+ * Anything that should never change, like cursors, should be
+ * place in EditorOverride.css, instead of here.
+*/
+
+@import url(chrome://communicator/skin/smileys.css);
+
+a[name] {
+ min-height: 17px; margin-left: 2px; margin-top: 2px;
+ padding-left: 20px;
+ background-image: url(chrome://editor/content/images/tag-anchor.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+/* Force border display for empty cells
+ and tables with 0 border
+*/
+table {
+ empty-cells: show;
+}
+
+/* give a red dotted border to tables and cells with no border
+ otherwise they are invisible
+*/
+table[empty-cells],
+ table[border="0"],
+ /* next two selectors on line below for the case where tbody is omitted */
+ table[border="0"] > tr > td, table[border="0"] > tr > th,
+ table[border="0"] > thead > tr > td, table[border="0"] > tbody > tr > td, table[border="0"] > tfoot > tr > td,
+ table[border="0"] > thead > tr > th, table[border="0"] > tbody > tr > th, table[border="0"] > tfoot > tr > th,
+ table:not([border]),
+ /* next two selectors on line below for the case where tbody is omitted */
+ table:not([border]) > tr > td, table:not([border]) > tr > th,
+ table:not([border]) > thead > tr > td, table:not([border]) > tbody > tr > td, table:not([border]) > tfoot > tr > td,
+ table:not([border]) > thead > tr > th, table:not([border]) > tbody > tr > th, table:not([border]) > tfoot > tr > th
+{
+ border: 1px dotted red;
+}
+
+/* give a green dashed border to forms otherwise they are invisible
+*/
+form
+{
+ border: 2px dashed green;
+}
+/* give a green dotted border to labels otherwise they are invisible
+*/
+label
+{
+ border: 1px dotted green;
+}
+
+img {
+ -moz-force-broken-image-icon: 1;
+}
diff --git a/comm/suite/editor/base/content/EditorContextMenu.js b/comm/suite/editor/base/content/EditorContextMenu.js
new file mode 100644
index 0000000000..93dedfc0b1
--- /dev/null
+++ b/comm/suite/editor/base/content/EditorContextMenu.js
@@ -0,0 +1,122 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "InlineSpellCheckerUI", function() {
+ let tmp = {};
+ ChromeUtils.import("resource://gre/modules/InlineSpellChecker.jsm", tmp);
+ return new tmp.InlineSpellChecker();
+});
+
+// Overrides the main contentAreaContext onpopupshowing so needs to do
+// everything that does plus call Composer specific code.
+function editorContextPopupShowing(aNode)
+{
+ gContextMenu = new nsContextMenu(aNode);
+ if (gContextMenu.shouldDisplay)
+ {
+ var showExtra = top.document.commandDispatcher.focusedWindow == content;
+ gContextMenu.initEditorItems(showExtra);
+ return true;
+ }
+ return false;
+}
+
+// Extends the main nsContextMenu for Composer.
+nsContextMenu.prototype.initEditorItems = function (aShow)
+{
+ var isInLink = false;
+ var objectName;
+ var inSourceMode = IsInHTMLSourceMode();
+ var showSpell = !inSourceMode && !IsInPreviewMode() &&
+ InlineSpellCheckerUI.canSpellCheck;
+ this.showItem("spell-check-enabled", showSpell);
+ this.showItem("spell-separator", showSpell);
+
+ aShow = aShow && !inSourceMode;
+ this.hideDisabledItem("menu_pasteNoFormatting_cm", aShow);
+
+ // Only do this stuff when not in source mode or sidebar.
+ if (aShow)
+ {
+ // Setup object property command element.
+ objectName = InitObjectPropertiesMenuitem();
+ isInLink = objectName == "href";
+
+ InitRemoveStylesMenuitems("removeStylesMenuitem_cm",
+ "removeLinksMenuitem_cm",
+ "removeNamedAnchorsMenuitem_cm");
+
+ // Set appropriate text for join cells command.
+ InitJoinCellMenuitem("joinTableCells_cm");
+
+ // Update enable states for all table commands.
+ goUpdateTableMenuItems(document.getElementById("composerTableMenuItems"));
+
+ this.hideDisabledItem("context-undo", true);
+ this.hideDisabledItem("context-redo", true);
+ this.hideDisabledItem("context-cut", true);
+ this.hideDisabledItem("context-copy", true);
+ this.hideDisabledItem("context-paste", true);
+ this.hideDisabledItem("context-delete", true);
+
+ this.showItem("context-sep-undo",
+ this.shouldShowSeparator("context-sep-undo"));
+ this.showItem("context-sep-paste",
+ this.shouldShowSeparator("context-sep-paste"));
+ }
+
+ this.hideDisabledItem("objectProperties_cm", aShow);
+
+ // Show "Create Link" if not in a link and not in source mode or sidebar.
+ this.showItem("createLink_cm", aShow && !isInLink);
+
+ // Show "Edit link in new Composer" if in a link and
+ // not in source mode or sidebar.
+ this.showItem("editLink_cm", aShow && isInLink);
+
+ this.hideDisabledItem("removeStylesMenuitem_cm", aShow);
+ this.hideDisabledItem("removeLinksMenuitem_cm", aShow);
+ this.hideDisabledItem("removeNamedAnchorsMenuitem_cm", aShow);
+
+ this.hideDisabledItem("joinTableCells_cm", aShow);
+ this.hideDisabledItem("splitTableCell_cm", aShow);
+ this.hideDisabledItem("tableOrCellColor_cm", aShow);
+
+ var inCell = aShow && IsInTableCell();
+ // Remove table submenus if not in table.
+ this.showItem("tableInsertMenu_cm", inCell);
+ this.showItem("tableSelectMenu_cm", inCell);
+ this.showItem("tableDeleteMenu_cm", inCell);
+
+ this.showItem("context-sep-selectall", aShow);
+ this.showItem("context-sep-properites", aShow && !!objectName);
+ this.showItem("frame-sep", aShow && IsInTable());
+};
+
+nsContextMenu.prototype.hideDisabledItem = function(aId, aShow)
+{
+ this.showItem(aId, aShow && IsItemOrCommandEnabled(aId));
+};
+
+function IsItemOrCommandEnabled(aId)
+{
+ var item = document.getElementById(aId);
+ if (!item)
+ return false;
+
+ var command = item.getAttribute("command");
+ if (command) {
+ // If possible, query the command controller directly
+ var controller = document.commandDispatcher
+ .getControllerForCommand(command);
+ if (controller)
+ return controller.isCommandEnabled(command);
+ }
+
+ // Fall back on the inefficient observed disabled attribute
+ return item.getAttribute("disabled") != "true";
+}
diff --git a/comm/suite/editor/base/content/EditorContextMenuOverlay.xhtml b/comm/suite/editor/base/content/EditorContextMenuOverlay.xhtml
new file mode 100644
index 0000000000..36c23f0c5e
--- /dev/null
+++ b/comm/suite/editor/base/content/EditorContextMenuOverlay.xhtml
@@ -0,0 +1,171 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay SYSTEM "chrome://editor/locale/editorOverlay.dtd">
+
+<overlay id="ComposerContextMenuOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+<script src="chrome://editor/content/EditorContextMenu.js"/>
+<script src="chrome://editor/content/StructBarContextMenu.js"/>
+
+ <menupopup id="contentAreaContextMenu"
+ onpopupshowing="return event.target != this ||
+ editorContextPopupShowing(this);">
+ <menuitem id="menu_pasteNoFormatting_cm"
+ insertafter="context-paste"
+ command="cmd_pasteNoFormatting"/>
+
+ <!-- label and accesskey set at runtime from strings -->
+ <menuitem id="removeStylesMenuitem_cm"
+ insertafter="context-sep-selectall"
+ command="cmd_removeStyles"/>
+ <menuitem id="createLink_cm"
+ insertafter="removeStylesMenuitem_cm"
+ label="&createLinkCmd.label;"
+ accesskey="&createLinkCmd.accesskey;"
+ command="cmd_link"/>
+ <!-- label and accesskey set at runtime from strings -->
+ <menuitem id="removeLinksMenuitem_cm"
+ insertafter="createLink_cm"
+ command="cmd_removeLinks"/>
+ <menuitem id="removeNamedAnchorsMenuitem_cm"
+ insertafter="removeLinksMenuitem_cm"
+ label="&formatRemoveNamedAnchors.label;"
+ accesskey="&formatRemoveNamedAnchors.accesskey;"
+ command="cmd_removeNamedAnchors"/>
+
+ <!-- label and accesskey are set in InitObjectProperties -->
+ <menuitem id="objectProperties_cm"
+ insertafter="context-sep-properties"
+ command="cmd_objectProperties"/>
+ <menuitem id="editLink_cm"
+ insertafter="objectProperties_cm"
+ label="&editLinkCmd.label;"
+ accesskey="&editLinkCmd.accesskey;"
+ command="cmd_editLink"/>
+
+ <!-- Can't get submenus to load from a shared overlay -->
+ <menu id="tableInsertMenu_cm"
+ insertafter="frame-sep"
+ label="&tableInsertMenu2.label;"
+ accesskey="&tableInsertMenu2.accesskey;">
+ <menupopup>
+ <menuitem label="&insertTableCmd.label;"
+ accesskey="&insertTableCmd.accesskey;"
+ command="cmd_InsertTable"/>
+ <menuseparator/>
+ <menuitem label="&tableRowAbove.label;"
+ accesskey="&tableRowAbove.accesskey;"
+ command="cmd_InsertRowAbove"/>
+ <menuitem label="&tableRowBelow.label;"
+ accesskey="&tableRowBelow.accesskey;"
+ command="cmd_InsertRowBelow"/>
+ <menuseparator/>
+ <menuitem label="&tableColumnBefore.label;"
+ accesskey="&tableColumnBefore.accesskey;"
+ command="cmd_InsertColumnBefore"/>
+ <menuitem label="&tableColumnAfter.label;"
+ accesskey="&tableColumnAfter.accesskey;"
+ command="cmd_InsertColumnAfter"/>
+ <menuseparator/>
+ <menuitem label="&tableCellBefore.label;"
+ accesskey="&tableCellBefore.accesskey;"
+ command="cmd_InsertCellBefore"/>
+ <menuitem label="&tableCellAfter.label;"
+ accesskey="&tableCellAfter.accesskey;"
+ command="cmd_InsertCellAfter"/>
+ </menupopup>
+ </menu>
+ <menu id="tableSelectMenu_cm"
+ insertafter="tableInsertMenu_cm"
+ label="&tableSelectMenu2.label;"
+ accesskey="&tableSelectMenu2.accesskey;">
+ <menupopup>
+ <menuitem id="menu_SelectTable_cm"
+ label="&tableTable.label;"
+ accesskey="&tableTable.accesskey;"
+ command="cmd_SelectTable"/>
+ <menuitem id="menu_SelectRow_cm"
+ label="&tableRow.label;"
+ accesskey="&tableRow.accesskey;"
+ command="cmd_SelectRow"/>
+ <menuitem id="menu_SelectColumn_cm"
+ label="&tableColumn.label;"
+ accesskey="&tableColumn.accesskey;"
+ command="cmd_SelectColumn"/>
+ <menuitem id="menu_SelectCell_cm"
+ label="&tableCell.label;"
+ accesskey="&tableCell.accesskey;"
+ command="cmd_SelectCell"/>
+ <menuitem id="menu_SelectAllCells_cm"
+ label="&tableAllCells.label;"
+ accesskey="&tableAllCells.accesskey;"
+ command="cmd_SelectAllCells"/>
+ </menupopup>
+ </menu>
+ <menu id="tableDeleteMenu_cm"
+ insertafter="tableSelectMenu_cm"
+ label="&tableDeleteMenu2.label;"
+ accesskey="&tableDeleteMenu2.accesskey;">
+ <menupopup>
+ <menuitem id="menu_DeleteTable_cm"
+ label="&tableTable.label;"
+ accesskey="&tableTable.accesskey;"
+ command="cmd_DeleteTable"/>
+ <menuitem id="menu_DeleteRow_cm"
+ label="&tableRows.label;"
+ accesskey="&tableRow.accesskey;"
+ command="cmd_DeleteRow"/>
+ <menuitem id="menu_DeleteColumn_cm"
+ label="&tableColumns.label;"
+ accesskey="&tableColumn.accesskey;"
+ command="cmd_DeleteColumn"/>
+ <menuitem id="menu_DeleteCell_cm"
+ label="&tableCells.label;"
+ accesskey="&tableCell.accesskey;"
+ command="cmd_DeleteCell"/>
+ <menuitem id="menu_DeleteCellContents_cm"
+ label="&tableCellContents.label;"
+ accesskey="&tableCellContents.accesskey;"
+ command="cmd_DeleteCellContents"/>
+ </menupopup>
+ </menu>
+ <!-- menu label is set in InitTableMenu -->
+ <menuitem id="joinTableCells_cm"
+ insertafter="tableDeleteMenu_cm"
+ label="&tableJoinCells.label;"
+ accesskey="&tableJoinCells.accesskey;"
+ command="cmd_JoinTableCells"/>
+ <menuitem id="splitTableCell_cm"
+ insertafter="joinTableCells_cm"
+ label="&tableSplitCell.label;"
+ accesskey="&tableSplitCell.accesskey;"
+ command="cmd_SplitTableCell"/>
+ <menuitem id="tableOrCellColor_cm"
+ insertafter="splitTableCell_cm"
+ label="&tableOrCellColor.label;"
+ accesskey="&tableOrCellColor.accesskey;"
+ command="cmd_TableOrCellColor"/>
+ </menupopup>
+
+ <menupopup id="structToolbarContext">
+ <menuitem id="structSelect" label="&structSelect.label;"
+ accesskey="&structSelect.accesskey;"
+ oncommand="StructSelectTag()"/>
+ <menuseparator/>
+ <menuitem id="structRemoveTag" label="&structRemoveTag.label;"
+ accesskey="&structRemoveTag.accesskey;"
+ oncommand="StructRemoveTag()"/>
+ <menuitem id="structChangeTag" label="&structChangeTag.label;"
+ accesskey="&structChangeTag.accesskey;"
+ oncommand="StructChangeTag()"/>
+ <menuseparator/>
+ <menuitem id="advancedPropsTag" label="&advancedPropertiesCmd.label;"
+ accesskey="&advancedPropertiesCmd.accesskey;"
+ oncommand="OpenAdvancedProperties()"/>
+ </menupopup>
+
+</overlay>
diff --git a/comm/suite/editor/base/content/StructBarContextMenu.js b/comm/suite/editor/base/content/StructBarContextMenu.js
new file mode 100644
index 0000000000..19f5498355
--- /dev/null
+++ b/comm/suite/editor/base/content/StructBarContextMenu.js
@@ -0,0 +1,179 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gContextMenuNode;
+var gContextMenuFiringDocumentElement;
+
+function InitStructBarContextMenu(button, docElement)
+{
+ gContextMenuFiringDocumentElement = docElement;
+ gContextMenuNode = button;
+
+ var tag = docElement.nodeName.toLowerCase();
+
+ var structRemoveTag = document.getElementById("structRemoveTag");
+ var enableRemove;
+
+ switch (tag) {
+ case "body":
+ case "tbody":
+ case "thead":
+ case "tfoot":
+ case "col":
+ case "colgroup":
+ case "tr":
+ case "th":
+ case "td":
+ case "caption":
+ enableRemove = false;
+ break;
+ default:
+ enableRemove = true;
+ break;
+ }
+ SetElementEnabled(structRemoveTag, enableRemove);
+
+ var structChangeTag = document.getElementById("structChangeTag");
+ SetElementEnabled(structChangeTag, (tag != "body"));
+}
+
+function TableCellFilter(node)
+{
+ switch (node.nodeName.toLowerCase())
+ {
+ case "td":
+ case "th":
+ case "caption":
+ return NodeFilter.FILTER_ACCEPT;
+ break;
+ default:
+ return NodeFilter.FILTER_SKIP;
+ break;
+ }
+ return NodeFilter.FILTER_SKIP;
+}
+
+function StructRemoveTag()
+{
+ var editor = GetCurrentEditor();
+ if (!editor) return;
+
+ var element = gContextMenuFiringDocumentElement;
+ var offset = 0;
+ var childNodes = element.parentNode.childNodes;
+
+ while (childNodes[offset] != element) {
+ ++offset;
+ }
+
+ editor.beginTransaction();
+
+ try {
+
+ var tag = element.nodeName.toLowerCase();
+ if (tag != "table") {
+ MoveChildNodesAfterElement(editor, element, element, offset);
+ }
+ else {
+
+ var nodeIterator = document.createTreeWalker(element,
+ NodeFilter.SHOW_ELEMENT,
+ TableCellFilter,
+ true);
+ var node = nodeIterator.lastChild();
+ while (node) {
+ MoveChildNodesAfterElement(editor, node, element, offset);
+ node = nodeIterator.previousSibling();
+ }
+
+ }
+ editor.deleteNode(element);
+ }
+ catch (e) {};
+
+ editor.endTransaction();
+}
+
+function MoveChildNodesAfterElement(editor, element, targetElement, targetOffset)
+{
+ var childNodes = element.childNodes;
+ var childNodesLength = childNodes.length;
+ var i;
+ for (i = childNodesLength - 1; i >= 0; i--) {
+ var clone = childNodes.item(i).cloneNode(true);
+ editor.insertNode(clone, targetElement.parentNode, targetOffset + 1);
+ }
+}
+
+function StructChangeTag()
+{
+ var textbox = document.createXULElement("textbox");
+ textbox.setAttribute("value", gContextMenuNode.getAttribute("value"));
+ textbox.setAttribute("width", gContextMenuNode.getBoundingClientRect().width);
+ textbox.className = "struct-textbox";
+
+ gContextMenuNode.parentNode.replaceChild(textbox, gContextMenuNode);
+
+ textbox.addEventListener("keypress", OnKeyPress);
+ textbox.addEventListener("blur", ResetStructToolbar, true);
+
+ textbox.select();
+}
+
+function StructSelectTag()
+{
+ SelectFocusNodeAncestor(gContextMenuFiringDocumentElement);
+}
+
+function OpenAdvancedProperties()
+{
+ doAdvancedProperties(gContextMenuFiringDocumentElement);
+}
+
+function OnKeyPress(event)
+{
+ var editor = GetCurrentEditor();
+
+ var keyCode = event.keyCode;
+ if (keyCode == 13) {
+ var newTag = event.target.value;
+
+ var element = gContextMenuFiringDocumentElement;
+
+ var offset = 0;
+ var childNodes = element.parentNode.childNodes;
+ while (childNodes.item(offset) != element) {
+ offset++;
+ }
+
+ editor.beginTransaction();
+
+ try {
+ var newElt = editor.document.createXULElement(newTag);
+ if (newElt) {
+ childNodes = element.childNodes;
+ var childNodesLength = childNodes.length;
+ var i;
+ for (i = 0; i < childNodesLength; i++) {
+ var clone = childNodes.item(i).cloneNode(true);
+ newElt.appendChild(clone);
+ }
+ editor.insertNode(newElt, element.parentNode, offset+1);
+ editor.deleteNode(element);
+ editor.selectElement(newElt);
+
+ window.content.focus();
+ }
+ }
+ catch (e) {}
+
+ editor.endTransaction();
+
+ }
+ else if (keyCode == 27) {
+ // if the user hits Escape, we discard the changes
+ window.content.focus();
+ }
+}
diff --git a/comm/suite/editor/base/content/composerOverlay.xhtml b/comm/suite/editor/base/content/composerOverlay.xhtml
new file mode 100644
index 0000000000..1d36db50b1
--- /dev/null
+++ b/comm/suite/editor/base/content/composerOverlay.xhtml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay [
+<!ENTITY % editorDTD SYSTEM "chrome://editor/locale/editor.dtd">
+%editorDTD;
+]>
+
+<overlay id="composerOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <!-- Items in the File menu used only by Composer app -->
+ <menupopup id="menu_FilePopup">
+ <menuitem id="fileExportToText"
+ insertafter="sep_print"
+ command="cmd_exportToText"/>
+ <menuitem id="previewInBrowser"
+ label="&previewCmd.label;"
+ accesskey="&previewCmd.accesskey;"
+ insertafter="fileExportToText"
+ command="cmd_preview"/>
+ <!-- menuitem id="menu_SendPage" is merged here from mailEditorOverlay.xhtml,
+ where "position" is assumed to be just after 'previewInBrowser' -->
+ </menupopup>
+
+</overlay>
diff --git a/comm/suite/editor/base/content/editingOverlay.js b/comm/suite/editor/base/content/editingOverlay.js
new file mode 100644
index 0000000000..19b41d321b
--- /dev/null
+++ b/comm/suite/editor/base/content/editingOverlay.js
@@ -0,0 +1,387 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gUntitledString;
+
+function TextEditorOnLoad()
+{
+ var url = "about:blank";
+ // See if argument was passed.
+ if (window.arguments && window.arguments[0])
+ {
+ // Opened via window.openDialog with URL as argument.
+ url = window.arguments[0];
+ }
+ // Continue with normal startup.
+ EditorStartup(url);
+}
+
+function EditorOnLoad()
+{
+ var url = "about:blank";
+ var charset;
+ // See if argument was passed.
+ if (window.arguments)
+ {
+ if (window.arguments[0])
+ {
+ // Opened via window.openDialog with URL as argument.
+ url = window.arguments[0];
+ }
+
+ // get default character set if provided
+ if (window.arguments.length > 1 && window.arguments[1])
+ {
+ if (window.arguments[1].includes("charset="))
+ {
+ var arrayArgComponents = window.arguments[1].split("=");
+ if (arrayArgComponents)
+ charset = arrayArgComponents[1];
+ }
+ }
+ }
+
+ // XUL elements we use when switching from normal editor to edit source.
+ gContentWindowDeck = document.getElementById("ContentWindowDeck");
+ gFormatToolbar = document.getElementById("FormatToolbar");
+
+ // Continue with normal startup.
+ EditorStartup(url, charset);
+
+ // Hide Highlight button if we are in an HTML editor with CSS mode off
+ // and tell the editor if a CR in a paragraph creates a new paragraph.
+ var cmd = document.getElementById("cmd_highlight");
+ if (cmd) {
+ if (!Services.prefs.getBoolPref(kUseCssPref))
+ cmd.collapsed = true;
+ }
+
+ // Initialize our source text <editor>
+ try {
+ gSourceContentWindow = document.getElementById("content-source");
+ gSourceContentWindow.makeEditable("text", false);
+ gSourceTextEditor = gSourceContentWindow.getEditor(gSourceContentWindow.contentWindow);
+ gSourceTextEditor.enableUndo(false);
+ gSourceTextEditor.rootElement.style.fontFamily = "-moz-fixed";
+ gSourceTextEditor.rootElement.style.whiteSpace = "pre";
+ gSourceTextEditor.rootElement.style.margin = 0;
+ var controller = Cc["@mozilla.org/embedcomp/base-command-controller;1"]
+ .createInstance(Ci.nsIControllerContext);
+ controller.setCommandContext(gSourceContentWindow);
+ gSourceContentWindow.contentWindow.controllers.insertControllerAt(0, controller);
+ var commandTable = controller.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIControllerCommandTable);
+ commandTable.registerCommand("cmd_findReplace", nsFindReplaceCommand);
+ commandTable.registerCommand("cmd_find", nsFindCommand);
+ commandTable.registerCommand("cmd_findNext", nsFindAgainCommand);
+ commandTable.registerCommand("cmd_findPrev", nsFindAgainCommand);
+ } catch (e) {
+ dump("makeEditable failed: "+e+"\n");
+ }
+}
+
+function toggleAffectedChrome(aHide)
+{
+ // chrome to toggle includes:
+ // (*) menubar
+ // (*) toolbox
+ // (*) sidebar
+ // (*) statusbar
+
+ if (!gChromeState)
+ gChromeState = new Object;
+
+ var statusbar = document.getElementById("status-bar");
+
+ // sidebar states map as follows:
+ // hidden => hide/show nothing
+ // collapsed => hide/show only the splitter
+ // shown => hide/show the splitter and the box
+ if (aHide)
+ {
+ // going into print preview mode
+ gChromeState.sidebar = SidebarGetState();
+ SidebarSetState("hidden");
+
+ // deal with the Status Bar
+ gChromeState.statusbarWasHidden = statusbar.hidden;
+ statusbar.hidden = true;
+ }
+ else
+ {
+ // restoring normal mode (i.e., leaving print preview mode)
+ SidebarSetState(gChromeState.sidebar);
+
+ // restore the Status Bar
+ statusbar.hidden = gChromeState.statusbarWasHidden;
+ }
+
+ // if we are unhiding and sidebar used to be there rebuild it
+ if (!aHide && gChromeState.sidebar == "visible")
+ SidebarRebuild();
+
+ document.getElementById("EditorToolbox").hidden = aHide;
+ document.getElementById("appcontent").collapsed = aHide;
+}
+
+var PrintPreviewListener = {
+ getPrintPreviewBrowser: function () {
+ var browser = document.getElementById("ppBrowser");
+ if (!browser) {
+ browser = document.createXULElement("browser");
+ browser.setAttribute("id", "ppBrowser");
+ browser.setAttribute("flex", "1");
+ browser.setAttribute("disablehistory", "true");
+ browser.setAttribute("disablesecurity", "true");
+ browser.setAttribute("type", "content");
+ document.getElementById("sidebar-parent").
+ insertBefore(browser, document.getElementById("appcontent"));
+ }
+ return browser;
+ },
+ getSourceBrowser: function () {
+ return GetCurrentEditorElement();
+ },
+ getNavToolbox: function () {
+ return document.getElementById("EditorToolbox");
+ },
+ onEnter: function () {
+ toggleAffectedChrome(true);
+ },
+ onExit: function () {
+ document.getElementById("ppBrowser").collapsed = true;
+ toggleAffectedChrome(false);
+ }
+}
+
+function EditorStartup(aUrl, aCharset)
+{
+ gUntitledString = GetFormattedString("untitledTitle", GetNextUntitledValue());
+
+ var ds = GetCurrentEditorElement().docShell;
+ ds.useErrorPages = false;
+ var root = ds.QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem.QueryInterface(Ci.nsIDocShell);
+
+ root.QueryInterface(Ci.nsIDocShell).appType =
+ Ci.nsIDocShell.APP_TYPE_EDITOR;
+
+ // EditorSharedStartup also used by Message Composer.
+ EditorSharedStartup();
+
+ // Commands specific to the Composer Application window,
+ // (i.e., not embedded editors)
+ // such as file-related commands, HTML Source editing, Edit Modes...
+ SetupComposerWindowCommands();
+
+ gCSSPrefListener = new nsPrefListener(kUseCssPref);
+ gReturnInParagraphPrefListener = new nsPrefListener(kCRInParagraphsPref);
+ Services.obs.addObserver(EditorCanClose, "quit-application-requested");
+
+ root.charset = aCharset;
+
+ // Get url for editor content and load it. The editor gets instantiated by
+ // the editingSession when the URL has finished loading.
+ EditorLoadUrl(aUrl);
+
+ // Before and after callbacks for the customizeToolbar code.
+ var editorToolbox = getEditorToolbox();
+ editorToolbox.customizeInit = EditorToolboxCustomizeInit;
+ editorToolbox.customizeDone = EditorToolboxCustomizeDone;
+ editorToolbox.customizeChange = EditorToolboxCustomizeChange;
+}
+
+function EditorShutdown()
+{
+ Services.obs.removeObserver(EditorCanClose, "quit-application-requested");
+
+ gCSSPrefListener.shutdown();
+ gReturnInParagraphPrefListener.shutdown();
+
+ try
+ {
+ var commandManager = GetCurrentCommandManager();
+ commandManager.removeCommandObserver(gEditorDocumentObserver,
+ "obs_documentCreated");
+ commandManager.removeCommandObserver(gEditorDocumentObserver,
+ "obs_documentWillBeDestroyed");
+ commandManager.removeCommandObserver(gEditorDocumentObserver,
+ "obs_documentLocationChanged");
+ } catch (e) { dump (e); }
+}
+
+// --------------------------- File menu ---------------------------
+
+// Check for changes to document and allow saving before closing
+// This is hooked up to the OS's window close widget (e.g., "X" for Windows)
+async function EditorCanClose(aCancelQuit, aTopic, aData)
+{
+ if (aTopic == "quit-application-requested" &&
+ aCancelQuit instanceof Ci.nsISupportsPRBool &&
+ aCancelQuit.data)
+ return false;
+
+ // Returns FALSE only if user cancels save action
+
+ // "true" means allow "Don't Save" button
+ var canClose = await CheckAndSaveDocument("cmd_close", true);
+
+ // This is our only hook into closing via the "X" in the caption
+ // or "Quit" (or other paths?)
+ // so we must shift association to another
+ // editor or close any non-modal windows now
+ if (canClose && "InsertCharWindow" in window && window.InsertCharWindow)
+ SwitchInsertCharToAnotherEditorOrClose();
+
+ if (!canClose && aTopic == "quit-application-requested")
+ aCancelQuit.data = true;
+
+ return canClose;
+}
+
+function BuildRecentPagesMenu()
+{
+ var editor = GetCurrentEditor();
+ if (!editor)
+ return;
+
+ var popup = document.getElementById("menupopup_RecentFiles");
+ if (!popup || !editor.document)
+ return;
+
+ // Delete existing menu
+ while (popup.hasChildNodes())
+ popup.lastChild.remove();
+
+ // Current page is the "0" item in the list we save in prefs,
+ // but we don't include it in the menu.
+ var curUrl = StripPassword(GetDocumentUrl());
+ var historyCount = Services.prefs.getIntPref("editor.history.url_maximum", 10);
+
+ var menuIndex = 1;
+ for (var i = 0; i < historyCount; i++)
+ {
+ var url = Services.prefs.getStringPref("editor.history_url_" + i, "");
+
+ // Skip over current url
+ if (url && url != curUrl)
+ {
+ // Build the menu
+ var title = Services.prefs.getStringPref("editor.history_title_" + i, "");
+ var fileType = Services.prefs.getStringPref("editor.history_type_" + i, "");
+ AppendRecentMenuitem(popup, title, url, fileType, menuIndex);
+ menuIndex++;
+ }
+ }
+}
+
+function AppendRecentMenuitem(aPopup, aTitle, aUrl, aFileType, aIndex)
+{
+ if (!aPopup)
+ return;
+
+ var menuItem = document.createXULElement("menuitem");
+ if (!menuItem)
+ return;
+
+ var accessKey = aIndex <= 10 ? String(aIndex % 10) : " ";
+
+ // Show "title [url]" or just the URL.
+ var itemString = aTitle ? aTitle + " [" + aUrl + "]" : aUrl;
+
+ menuItem.setAttribute("label", accessKey + " " + itemString);
+ menuItem.setAttribute("crop", "center");
+ menuItem.setAttribute("tooltiptext", aUrl);
+ menuItem.setAttribute("value", aUrl);
+ menuItem.setAttribute("fileType", aFileType);
+ if (accessKey != " ")
+ menuItem.setAttribute("accesskey", accessKey);
+ aPopup.appendChild(menuItem);
+}
+
+function EditorInitFileMenu()
+{
+ // Disable "Save" menuitem when editing remote url. User should use "Save As"
+
+ var docUrl = GetDocumentUrl();
+ var scheme = GetScheme(docUrl);
+ if (scheme && scheme != "file")
+ SetElementEnabledById("menu_saveCmd", false);
+
+ // Enable recent pages submenu if there are any history entries in prefs.
+ var historyUrl = "";
+
+ if (Services.prefs.getIntPref("editor.history.url_maximum", 10))
+ {
+ historyUrl = Services.prefs.getStringPref("editor.history_url_0", "");
+
+ // See if there's more if current file is only entry in history list.
+ if (historyUrl && historyUrl == docUrl)
+ historyUrl = Services.prefs.getStringPref("editor.history_url_1", "");
+ }
+ SetElementEnabledById("menu_RecentFiles", historyUrl != "");
+}
+
+function EditorUpdateCharsetMenu(aMenuPopup)
+{
+ if (IsDocumentModified() && !IsDocumentEmpty())
+ {
+ for (var i = 0; i < aMenuPopup.childNodes.length; i++)
+ aMenuPopup.childNodes[i].setAttribute("disabled", "true");
+ }
+
+ UpdateCharsetMenu(content.document.characterSet, aMenuPopup);
+}
+
+// Zoom support.
+function getBrowser()
+{
+ return IsInHTMLSourceMode() ? gSourceContentWindow : GetCurrentEditorElement();
+}
+
+// override the site-specific zoom object in viewZoomOverlay.js
+var FullZoom = {
+ init: function() {},
+ reduce: function() { ZoomManager.reduce(); },
+ enlarge: function() { ZoomManager.enlarge(); },
+ zoom: function(aZoomValue) { ZoomManager.zoom = aZoomValue; },
+ reset: function() { ZoomManager.zoom = 1; },
+ setOther: function() { openZoomDialog(); }
+};
+
+function hideEditorUI(aHide) {
+ for (let id of ["EditModeToolbar", "content-source", "content-frame"]) {
+ let element = document.getElementById(id);
+ if (!element)
+ continue;
+
+ if (aHide) {
+ element.setAttribute("moz-collapsed", true);
+ } else {
+ element.removeAttribute("moz-collapsed");
+ }
+ }
+}
+
+function getEditorToolbox() {
+ return document.getElementById("EditorToolbox");
+}
+
+function EditorToolboxCustomizeInit() {
+ if (document.commandDispatcher.focusedWindow == content)
+ window.focus();
+ hideEditorUI(true);
+ toolboxCustomizeInit("main-menubar");
+}
+
+function EditorToolboxCustomizeDone(aToolboxChanged) {
+ toolboxCustomizeDone("main-menubar", getEditorToolbox(), aToolboxChanged);
+ hideEditorUI(false);
+ gContentWindow.focus();
+}
+
+function EditorToolboxCustomizeChange(aEvent) {
+ toolboxCustomizeChange(getEditorToolbox(), aEvent);
+}
diff --git a/comm/suite/editor/base/content/editingOverlay.xhtml b/comm/suite/editor/base/content/editingOverlay.xhtml
new file mode 100644
index 0000000000..21833857a6
--- /dev/null
+++ b/comm/suite/editor/base/content/editingOverlay.xhtml
@@ -0,0 +1,247 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xul-overlay href="chrome://communicator/content/viewZoomOverlay.xhtml"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://editor/locale/editingOverlay.dtd">
+
+<overlay id="editingOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <script src="chrome://editor/content/editingOverlay.js"/>
+
+ <stringbundleset>
+ <stringbundle id="bundle_viewZoom"/>
+ </stringbundleset>
+
+ <keyset id="editorKeys">
+ <key id="key_openRemoteEditor"
+ key="&openRemoteCmd.key;"
+ command="cmd_openRemote"
+ modifiers="accel,shift"/>
+ <key id="key_openEditor"
+ key="&openFileCmd.key;"
+ command="cmd_open"
+ modifiers="accel"/>
+ <key id="key_publish"
+ key="&publishCmd.key;"
+ command="cmd_publish"
+ modifiers="accel,shift"/>
+ <keyset id="viewZoomKeys"/>
+ </keyset>
+
+ <!-- commands updated when the editor gets created -->
+ <commandset id="commonEditorMenuItems">
+ <command id="cmd_open"
+ oncommand="goDoCommand('cmd_open');"/>
+ <command id="cmd_openRemote"
+ oncommand="goDoCommand('cmd_openRemote');"/>
+ </commandset>
+
+ <commandset id="composerSaveMenuItems"
+ commandupdater="true"
+ events="create, save"
+ oncommandupdate="goUpdateComposerMenuItems(this);">
+ <command id="cmd_save"
+ label="&saveCmd.label;"
+ oncommand="goDoCommand('cmd_save');"/>
+ <command id="cmd_saveAs"
+ oncommand="goDoCommand('cmd_saveAs');"/>
+ <command id="cmd_saveAndChangeEncoding"
+ oncommand="goDoCommand('cmd_saveAndChangeEncoding');"/>
+ <command id="cmd_publish"
+ label="&publishCmd.label;"
+ oncommand="goDoCommand('cmd_publish');"/>
+ <command id="cmd_publishAs"
+ oncommand="goDoCommand('cmd_publishAs');"/>
+ <command id="cmd_revert"
+ oncommand="goDoCommand('cmd_revert');"/>
+ </commandset>
+
+ <commandset id="composerEditMenuItems">
+ <command id="cmd_publishSettings"
+ oncommand="goDoCommand('cmd_publishSettings');"/>
+ </commandset>
+
+ <commandset id="composerMenuItems">
+ <command id="cmd_form"
+ oncommand="goDoCommand('cmd_form');"/>
+ <command id="cmd_inputtag"
+ oncommand="goDoCommand('cmd_inputtag');"/>
+ <command id="cmd_inputimage"
+ oncommand="goDoCommand('cmd_inputimage');"/>
+ <command id="cmd_textarea"
+ oncommand="goDoCommand('cmd_textarea');"/>
+ <command id="cmd_select"
+ oncommand="goDoCommand('cmd_select');"/>
+ <command id="cmd_button"
+ oncommand="goDoCommand('cmd_button');"/>
+ <command id="cmd_label"
+ oncommand="goDoCommand('cmd_label');"/>
+ <command id="cmd_fieldset"
+ oncommand="goDoCommand('cmd_fieldset');"/>
+ </commandset>
+
+ <commandset id="editorCommands">
+ <command id="cmd_CustomizeToolbars"
+ oncommand="goCustomizeToolbar(getEditorToolbox());"/>
+ <commandset id="viewZoomCommands"/>
+ </commandset>
+
+ <!-- File menu items -->
+ <menu id="menu_File">
+ <menupopup id="menu_FilePopup" onpopupshowing="EditorInitFileMenu();">
+ <menu id="menu_New" class="menu-iconic">
+ <menupopup id="menu_NewPopup">
+ <menuitem id="menu_newEditor"/>
+ <menuseparator id="sep_NewPopup"/>
+ <menuitem id="menu_newNavigator"/>
+ <menuitem id="menu_newPrivateWindow"/>
+ </menupopup>
+ </menu>
+ <menuitem id="menu_openRemote"
+ label="&openRemoteCmd.label;"
+ accesskey="&openRemoteCmd.accesskey;"
+ key="key_openRemoteEditor"
+ command="cmd_openRemote"/>
+ <menuitem id="menu_openFile"
+ label="&openFileCmd.label;"
+ accesskey="&openFileCmd.accesskey;"
+ key="key_openEditor"
+ command="cmd_open"/>
+ <menu id="menu_RecentFiles"
+ label="&fileRecentMenu.label;"
+ accesskey="&fileRecentMenu.accesskey;"
+ onpopupshowing="BuildRecentPagesMenu();">
+ <menupopup id="menupopup_RecentFiles"
+ oncommand="editPage(event.target.getAttribute('value'),
+ event.target.getAttribute('fileType'));"/>
+ <!-- menuitems appended at runtime -->
+ </menu>
+ <menuitem id="menu_close" class="menuitem-iconic"/>
+ <menuseparator id="sep_close"/>
+ <menuitem id="menu_saveCmd"
+ accesskey="&saveCmd.accesskey;"
+ key="key_save"
+ command="cmd_save"
+ class="menuitem-iconic"/>
+ <menuitem id="menu_saveAsCmd"
+ label="&saveAsCmd.label;"
+ accesskey="&saveAsCmd.accesskey;"
+ command="cmd_saveAs"
+ class="menuitem-iconic"/>
+ <menuitem id="menu_saveAsChangeEncoding"
+ label="&saveAsChangeEncodingCmd2.label;"
+ accesskey="&saveAsChangeEncodingCmd2.accesskey;"
+ command="cmd_saveAndChangeEncoding"/>
+ <menuseparator id="sep_saveCmd"/>
+ <menuitem id="menu_publish"
+ accesskey="&publishCmd.accesskey;"
+ key="key_publish"
+ command="cmd_publish"/>
+ <menuitem id="menu_publishAs"
+ label="&publishAsCmd.label;"
+ accesskey="&publishAsCmd.accesskey;"
+ command="cmd_publishAs"/>
+ <menuseparator id="sep_publishAs"/>
+ <menuitem id="menu_fileRevert"
+ label="&fileRevert.label;"
+ accesskey="&fileRevert.accesskey;"
+ command="cmd_revert"/>
+ <menuseparator id="sep_print"/>
+ <!-- menuitems are merged in here from composerOverlay.xhtml -->
+ <menuitem id="menu_printSetup"/>
+ <menuitem id="menu_printPreview" class="menuitem-iconic"/>
+ <menuitem id="menu_print" class="menuitem-iconic"/>
+ <!-- The Exit/Quit item is merged from platformGlobalOverlay.xhtml -->
+ </menupopup>
+ </menu>
+
+ <!-- Edit menu items -->
+ <menupopup id="menu_EditPopup">
+ <menuitem id="menu_inlineSpellCheck"
+ oncommand="InlineSpellCheckerUI.enabled = !InlineSpellCheckerUI.enabled"
+ class="menuitem-iconic"/>
+ <menuitem id="menu_publishSettings"
+ insertafter="sep_preferences"
+ label="&publishSettings.label;"
+ accesskey="&publishSettings.accesskey;"
+ command="cmd_publishSettings"/>
+ </menupopup>
+
+ <menupopup id="menu_View_Popup">
+ <menu id="menu_zoom" insertbefore="charsetMenu"/>
+ </menupopup>
+
+ <menupopup id="insertMenuPopup">
+ <menu id="insertFormMenu"
+ insertafter="insertTOC"
+ label="&insertFormMenu.label;"
+ accesskey="&insertFormMenu.accesskey;">
+ <menupopup id="formMenuPopup">
+ <menuitem label="&insertFormCmd.label;"
+ accesskey="&insertFormCmd.accesskey;"
+ command="cmd_form"/>
+ <menuseparator/>
+ <menuitem label="&insertInputTagCmd.label;"
+ accesskey="&insertInputTagCmd.accesskey;"
+ command="cmd_inputtag"/>
+ <menuitem label="&insertInputImageCmd.label;"
+ accesskey="&insertInputImageCmd.accesskey;"
+ command="cmd_inputimage"/>
+ <menuitem label="&insertTextAreaCmd.label;"
+ accesskey="&insertTextAreaCmd.accesskey;"
+ command="cmd_textarea"/>
+ <menuitem label="&insertSelectCmd.label;"
+ accesskey="&insertSelectCmd.accesskey;"
+ command="cmd_select"/>
+ <menuitem label="&insertButtonCmd.label;"
+ accesskey="&insertButtonCmd.accesskey;"
+ command="cmd_button"/>
+ <menuitem label="&insertLabelCmd.label;"
+ accesskey="&insertLabelCmd.accesskey;"
+ command="cmd_label"/>
+ <menuitem label="&insertFieldSetCmd.label;"
+ accesskey="&insertFieldSetCmd.accesskey;"
+ command="cmd_fieldset"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+
+ <!-- Toolbar buttons/items -->
+ <toolbarbutton id="newButton"
+ class="toolbarbutton-1"
+ label="&newToolbarCmd.label;"
+ removable="true"
+ command="cmd_newEditor"
+ tooltiptext="&newToolbarCmd.tooltip;"/>
+ <toolbarbutton id="openButton"
+ class="toolbarbutton-1"
+ label="&openToolbarCmd.label;"
+ removable="true"
+ command="cmd_open"
+ tooltiptext="&openToolbarCmd.tooltip;"/>
+ <toolbarbutton id="saveButton"
+ class="toolbarbutton-1"
+ removable="true"
+ command="cmd_save"
+ tooltiptext="&saveToolbarCmd.tooltip;"/>
+ <toolbarbutton id="publishButton"
+ class="toolbarbutton-1"
+ removable="true"
+ command="cmd_publish"
+ tooltiptext="&publishToolbarCmd.tooltip;"/>
+ <toolbarbutton id="print-button"
+ label="&printToolbarCmd.label;"
+ removable="true"
+ tooltiptext="&printToolbarCmd.tooltip;"/>
+ <!-- 'print-button' is merged in here from utilityOverlay.xhtml -->
+ <toolbarbutton id="formButton"
+ class="toolbarbutton-1"
+ removable="true"
+ label="&formToolbarCmd.label;"
+ command="cmd_form"
+ tooltiptext="&formToolbarCmd.tooltip;"/>
+</overlay>
diff --git a/comm/suite/editor/base/content/editor.js b/comm/suite/editor/base/content/editor.js
new file mode 100644
index 0000000000..34270d057a
--- /dev/null
+++ b/comm/suite/editor/base/content/editor.js
@@ -0,0 +1,3383 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../../../mail/base/content/utilityOverlay.js */
+/* import-globals-from ComposerCommands.js */
+/* import-globals-from editorUtilities.js */
+/* globals InlineSpellCheckerUI */
+
+var { GetNextUntitledValue } = ChromeUtils.import(
+ "resource:///modules/editorUtilities.jsm"
+);
+var { Async } = ChromeUtils.import("resource://services-common/async.js");
+var { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+/* Main Composer window UI control */
+
+var gComposerWindowControllerID = 0;
+var prefAuthorString = "";
+
+var kDisplayModeNormal = 0;
+var kDisplayModeAllTags = 1;
+var kDisplayModeSource = 2;
+var kDisplayModePreview = 3;
+
+const kDisplayModeMenuIDs = [
+ "viewNormalMode",
+ "viewAllTagsMode",
+ "viewSourceMode",
+ "viewPreviewMode",
+];
+const kDisplayModeTabIDS = [
+ "NormalModeButton",
+ "TagModeButton",
+ "SourceModeButton",
+ "PreviewModeButton",
+];
+const kNormalStyleSheet = "chrome://editor/content/EditorContent.css";
+const kAllTagsStyleSheet = "chrome://editor/content/EditorAllTags.css";
+const kContentEditableStyleSheet = "resource://gre/res/contenteditable.css";
+
+var kTextMimeType = "text/plain";
+var kHTMLMimeType = "text/html";
+var kXHTMLMimeType = "application/xhtml+xml";
+
+var gPreviousNonSourceDisplayMode = 1;
+var gEditorDisplayMode = -1;
+var gDocWasModified = false; // Check if clean document, if clean then unload when user "Opens"
+var gContentWindow = 0;
+var gSourceContentWindow = 0;
+var gSourceTextEditor = null;
+var gContentWindowDeck;
+var gFormatToolbar;
+var gFormatToolbarHidden = false;
+var gChromeState;
+var gColorObj = {
+ LastTextColor: "",
+ LastBackgroundColor: "",
+ LastHighlightColor: "",
+ Type: "",
+ SelectedType: "",
+ NoDefault: false,
+ Cancel: false,
+ HighlightColor: "",
+ BackgroundColor: "",
+ PageColor: "",
+ TextColor: "",
+ TableColor: "",
+ CellColor: "",
+};
+var gDefaultTextColor = "";
+var gDefaultBackgroundColor = "";
+var gCSSPrefListener;
+var gReturnInParagraphPrefListener;
+var gLocalFonts = null;
+
+var gLastFocusNode = null;
+var gLastFocusNodeWasSelected = false;
+
+// These must be kept in synch with the XUL <options> lists
+var gFontSizeNames = [
+ "xx-small",
+ "x-small",
+ "small",
+ "medium",
+ "large",
+ "x-large",
+ "xx-large",
+];
+
+var nsIFilePicker = Ci.nsIFilePicker;
+
+var kUseCssPref = "editor.use_css";
+var kCRInParagraphsPref = "editor.CR_creates_new_p";
+
+function nsPrefListener(prefName) {
+ this.startup(prefName);
+}
+
+// implements nsIObserver
+nsPrefListener.prototype = {
+ domain: "",
+ startup(prefName) {
+ this.domain = prefName;
+ try {
+ Services.prefs.addObserver(this.domain, this);
+ } catch (ex) {
+ dump("Failed to observe prefs: " + ex + "\n");
+ }
+ },
+ shutdown() {
+ try {
+ Services.prefs.removeObserver(this.domain, this);
+ } catch (ex) {
+ dump("Failed to remove pref observers: " + ex + "\n");
+ }
+ },
+ observe(subject, topic, prefName) {
+ if (!IsHTMLEditor()) {
+ return;
+ }
+ // verify that we're changing a button pref
+ if (topic != "nsPref:changed") {
+ return;
+ }
+
+ let editor = GetCurrentEditor();
+ if (prefName == kUseCssPref) {
+ let cmd = document.getElementById("cmd_highlight");
+ if (cmd) {
+ let useCSS = Services.prefs.getBoolPref(prefName);
+
+ if (useCSS && editor) {
+ let mixedObj = {};
+ let state = editor.getHighlightColorState(mixedObj);
+ cmd.setAttribute("state", state);
+ cmd.collapsed = false;
+ } else {
+ cmd.setAttribute("state", "transparent");
+ cmd.collapsed = true;
+ }
+
+ if (editor) {
+ editor.isCSSEnabled = useCSS;
+ }
+ }
+ } else if (editor && prefName == kCRInParagraphsPref) {
+ editor.returnInParagraphCreatesNewParagraph = Services.prefs.getBoolPref(
+ prefName
+ );
+ }
+ },
+};
+
+const gSourceTextListener = {
+ NotifyDocumentCreated() {},
+ NotifyDocumentWillBeDestroyed() {},
+ NotifyDocumentStateChanged(isChanged) {
+ window.updateCommands("save");
+ },
+};
+
+const gSourceTextObserver = {
+ observe(aSubject, aTopic, aData) {
+ // we currently only use this to update undo
+ window.updateCommands("undo");
+ },
+};
+
+// This should be called by all editor users when they close their window.
+function EditorCleanup() {
+ SwitchInsertCharToAnotherEditorOrClose();
+}
+
+var DocumentReloadListener = {
+ NotifyDocumentCreated() {},
+ NotifyDocumentWillBeDestroyed() {},
+
+ NotifyDocumentStateChanged(isNowDirty) {
+ var editor = GetCurrentEditor();
+ try {
+ // unregister the listener to prevent multiple callbacks
+ editor.removeDocumentStateListener(DocumentReloadListener);
+
+ var charset = editor.documentCharacterSet;
+
+ // update the META charset with the current presentation charset
+ editor.documentCharacterSet = charset;
+ } catch (e) {}
+ },
+};
+
+// implements nsIObserver
+var gEditorDocumentObserver = {
+ observe(aSubject, aTopic, aData) {
+ // Should we allow this even if NOT the focused editor?
+ var commandManager = GetCurrentCommandManager();
+ if (commandManager != aSubject) {
+ return;
+ }
+
+ var editor = GetCurrentEditor();
+ switch (aTopic) {
+ case "obs_documentCreated":
+ // Just for convenience
+ gContentWindow = window.content;
+
+ // Get state to see if document creation succeeded
+ var params = newCommandParams();
+ if (!params) {
+ return;
+ }
+
+ try {
+ commandManager.getCommandState(aTopic, gContentWindow, params);
+ var errorStringId = 0;
+ var editorStatus = params.getLongValue("state_data");
+ if (!editor && editorStatus == nsIEditingSession.eEditorOK) {
+ dump(
+ "\n ****** NO EDITOR BUT NO EDITOR ERROR REPORTED ******* \n\n"
+ );
+ editorStatus = nsIEditingSession.eEditorErrorUnknown;
+ }
+
+ switch (editorStatus) {
+ case nsIEditingSession.eEditorErrorCantEditFramesets:
+ errorStringId = "CantEditFramesetMsg";
+ break;
+ case nsIEditingSession.eEditorErrorCantEditMimeType:
+ errorStringId = "CantEditMimeTypeMsg";
+ break;
+ case nsIEditingSession.eEditorErrorUnknown:
+ errorStringId = "CantEditDocumentMsg";
+ break;
+ // Note that for "eEditorErrorFileNotFound,
+ // network code popped up an alert dialog, so we don't need to
+ }
+ if (errorStringId) {
+ Services.prompt.alert(window, "", GetString(errorStringId));
+ }
+ } catch (e) {
+ dump("EXCEPTION GETTING obs_documentCreated state " + e + "\n");
+ }
+
+ // We have a bad editor -- nsIEditingSession will rebuild an editor
+ // with a blank page, so simply abort here
+ if (editorStatus) {
+ return;
+ }
+
+ if (!("InsertCharWindow" in window)) {
+ window.InsertCharWindow = null;
+ }
+
+ try {
+ editor.QueryInterface(nsIEditorStyleSheets);
+
+ // and extra styles for showing anchors, table borders, smileys, etc
+ editor.addOverrideStyleSheet(kNormalStyleSheet);
+
+ // remove contenteditable stylesheets if they were applied by the
+ // editingSession
+ editor.removeOverrideStyleSheet(kContentEditableStyleSheet);
+ } catch (e) {}
+
+ // Things for just the Web Composer application
+ if (IsWebComposer()) {
+ InlineSpellCheckerUI.init(editor);
+ document
+ .getElementById("menu_inlineSpellCheck")
+ .setAttribute("disabled", !InlineSpellCheckerUI.canSpellCheck);
+
+ editor.returnInParagraphCreatesNewParagraph = Services.prefs.getBoolPref(
+ kCRInParagraphsPref
+ );
+
+ // Set focus to content window if not a mail composer
+ // Race conditions prevent us from setting focus here
+ // when loading a url into blank window
+ setTimeout(SetFocusOnStartup, 0);
+
+ // Call EditorSetDefaultPrefsAndDoctype first so it gets the default author before initing toolbars
+ editor.enableUndo(false);
+ EditorSetDefaultPrefsAndDoctype();
+ editor.resetModificationCount();
+ editor.enableUndo(true);
+
+ // We may load a text document into an html editor,
+ // so be sure editortype is set correctly
+ // XXX We really should use the "real" plaintext editor for this!
+ if (editor.contentsMIMEType == "text/plain") {
+ try {
+ GetCurrentEditorElement().editortype = "text";
+ } catch (e) {
+ dump(e) + "\n";
+ }
+
+ // Hide or disable UI not used for plaintext editing
+ HideItem("FormatToolbar");
+ HideItem("EditModeToolbar");
+ HideItem("formatMenu");
+ HideItem("tableMenu");
+ HideItem("menu_validate");
+ HideItem("sep_validate");
+ HideItem("previewButton");
+ HideItem("imageButton");
+ HideItem("linkButton");
+ HideItem("namedAnchorButton");
+ HideItem("hlineButton");
+ HideItem("tableButton");
+
+ HideItem("fileExportToText");
+ HideItem("previewInBrowser");
+
+ /* XXX When paste actually converts formatted rich text to pretty formatted plain text
+ and pasteNoFormatting is fixed to paste the text without formatting (what paste
+ currently does), then this item shouldn't be hidden: */
+ HideItem("menu_pasteNoFormatting");
+
+ HideItem("cmd_viewEditModeToolbar");
+
+ HideItem("viewSep1");
+ HideItem("viewNormalMode");
+ HideItem("viewAllTagsMode");
+ HideItem("viewSourceMode");
+ HideItem("viewPreviewMode");
+
+ HideItem("structSpacer");
+
+ // Hide everything in "Insert" except for "Symbols"
+ let menuPopupChildren = document.querySelectorAll(
+ '[id="insertMenuPopup"] > :not(#insertChars)'
+ );
+ for (let i = 0; i < menuPopupChildren.length; i++) {
+ menuPopupChildren.item(i).hidden = true;
+ }
+ }
+
+ // Set window title
+ UpdateWindowTitle();
+
+ // We must wait until document is created to get proper Url
+ // (Windows may load with local file paths)
+ SetSaveAndPublishUI(GetDocumentUrl());
+
+ // Start in "Normal" edit mode
+ SetDisplayMode(kDisplayModeNormal);
+ }
+
+ // Add mouse click watcher if right type of editor
+ if (IsHTMLEditor()) {
+ // Force color widgets to update
+ onFontColorChange();
+ onBackgroundColorChange();
+ }
+ break;
+
+ case "cmd_setDocumentModified":
+ window.updateCommands("save");
+ break;
+
+ case "obs_documentWillBeDestroyed":
+ dump("obs_documentWillBeDestroyed notification\n");
+ break;
+
+ case "obs_documentLocationChanged":
+ // Ignore this when editor doesn't exist,
+ // which happens once when page load starts
+ if (editor) {
+ try {
+ editor.updateBaseURL();
+ } catch (e) {
+ dump(e);
+ }
+ }
+ break;
+
+ case "cmd_bold":
+ // Update all style items
+ // cmd_bold is a proxy; see EditorSharedStartup (above) for details
+ window.updateCommands("style");
+ window.updateCommands("undo");
+ break;
+ }
+ },
+};
+
+function SetFocusOnStartup() {
+ gContentWindow.focus();
+}
+
+function EditorLoadUrl(url) {
+ try {
+ if (url) {
+ let loadURIOptions = {
+ loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE,
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ };
+ GetCurrentEditorElement().webNavigation.loadURI(url, loadURIOptions);
+ }
+ } catch (e) {
+ dump(" EditorLoadUrl failed: " + e + "\n");
+ }
+}
+
+// This should be called by all Composer types
+function EditorSharedStartup() {
+ // Just for convenience
+ gContentWindow = window.content;
+
+ // Disable DNS Prefetching on the docshell - we don't need it for composer
+ // type windows.
+ GetCurrentEditorElement().docShell.allowDNSPrefetch = false;
+
+ // Set up the mime type and register the commands.
+ if (IsHTMLEditor()) {
+ SetupHTMLEditorCommands();
+ } else {
+ SetupTextEditorCommands();
+ }
+
+ // add observer to be called when document is really done loading
+ // and is modified
+ // Note: We're really screwed if we fail to install this observer!
+ try {
+ var commandManager = GetCurrentCommandManager();
+ commandManager.addCommandObserver(
+ gEditorDocumentObserver,
+ "obs_documentCreated"
+ );
+ commandManager.addCommandObserver(
+ gEditorDocumentObserver,
+ "cmd_setDocumentModified"
+ );
+ commandManager.addCommandObserver(
+ gEditorDocumentObserver,
+ "obs_documentWillBeDestroyed"
+ );
+ commandManager.addCommandObserver(
+ gEditorDocumentObserver,
+ "obs_documentLocationChanged"
+ );
+
+ // Until nsIControllerCommandGroup-based code is implemented,
+ // we will observe just the bold command to trigger update of
+ // all toolbar style items
+ commandManager.addCommandObserver(gEditorDocumentObserver, "cmd_bold");
+ } catch (e) {
+ dump(e);
+ }
+
+ var isMac = AppConstants.platform == "macosx";
+
+ // Set platform-specific hints for how to select cells
+ // Mac uses "Cmd", all others use "Ctrl"
+ var tableKey = GetString(isMac ? "XulKeyMac" : "TableSelectKey");
+ var dragStr = tableKey + GetString("Drag");
+ var clickStr = tableKey + GetString("Click");
+
+ var delStr = GetString(isMac ? "Clear" : "Del");
+
+ SafeSetAttribute("menu_SelectCell", "acceltext", clickStr);
+ SafeSetAttribute("menu_SelectRow", "acceltext", dragStr);
+ SafeSetAttribute("menu_SelectColumn", "acceltext", dragStr);
+ SafeSetAttribute("menu_SelectAllCells", "acceltext", dragStr);
+ // And add "Del" or "Clear"
+ SafeSetAttribute("menu_DeleteCellContents", "acceltext", delStr);
+
+ // Set text for indent, outdent keybinding
+
+ // hide UI that we don't have components for
+ RemoveInapplicableUIElements();
+
+ // Use browser colors as initial values for editor's default colors
+ var BrowserColors = GetDefaultBrowserColors();
+ if (BrowserColors) {
+ gDefaultTextColor = BrowserColors.TextColor;
+ gDefaultBackgroundColor = BrowserColors.BackgroundColor;
+ }
+
+ // For new window, no default last-picked colors
+ gColorObj.LastTextColor = "";
+ gColorObj.LastBackgroundColor = "";
+ gColorObj.LastHighlightColor = "";
+}
+
+function SafeSetAttribute(nodeID, attributeName, attributeValue) {
+ var theNode = document.getElementById(nodeID);
+ if (theNode) {
+ theNode.setAttribute(attributeName, attributeValue);
+ }
+}
+
+function DocumentHasBeenSaved() {
+ var fileurl = "";
+ try {
+ fileurl = GetDocumentUrl();
+ } catch (e) {
+ return false;
+ }
+
+ if (!fileurl || IsUrlAboutBlank(fileurl)) {
+ return false;
+ }
+
+ // We have a file URL already
+ return true;
+}
+
+async function CheckAndSaveDocument(command, allowDontSave) {
+ var document;
+ try {
+ // if we don't have an editor or an document, bail
+ var editor = GetCurrentEditor();
+ document = editor.document;
+ if (!document) {
+ return true;
+ }
+ } catch (e) {
+ return true;
+ }
+
+ if (!IsDocumentModified() && !IsHTMLSourceChanged()) {
+ return true;
+ }
+
+ // call window.focus, since we need to pop up a dialog
+ // and therefore need to be visible (to prevent user confusion)
+ top.document.commandDispatcher.focusedWindow.focus();
+
+ var scheme = GetScheme(GetDocumentUrl());
+ var doPublish = scheme && scheme != "file";
+
+ var strID;
+ switch (command) {
+ case "cmd_close":
+ strID = "BeforeClosing";
+ break;
+ case "cmd_preview":
+ strID = "BeforePreview";
+ break;
+ case "cmd_editSendPage":
+ strID = "SendPageReason";
+ break;
+ case "cmd_validate":
+ strID = "BeforeValidate";
+ break;
+ }
+
+ var reasonToSave = strID ? GetString(strID) : "";
+
+ var title = document.title || GetString("untitledDefaultFilename");
+
+ var dialogTitle = GetString(doPublish ? "PublishPage" : "SaveDocument");
+ var dialogMsg = GetString(doPublish ? "PublishPrompt" : "SaveFilePrompt");
+ dialogMsg = dialogMsg
+ .replace(/%title%/, title)
+ .replace(/%reason%/, reasonToSave);
+
+ let result = { value: 0 };
+ let promptFlags =
+ Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1;
+ let button1Title = null;
+ let button3Title = null;
+
+ if (doPublish) {
+ promptFlags +=
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0;
+ button1Title = GetString("Publish");
+ button3Title = GetString("DontPublish");
+ } else {
+ promptFlags +=
+ Services.prompt.BUTTON_TITLE_SAVE * Services.prompt.BUTTON_POS_0;
+ }
+
+ // If allowing "Don't..." button, add that
+ if (allowDontSave) {
+ promptFlags += doPublish
+ ? Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_2
+ : Services.prompt.BUTTON_TITLE_DONT_SAVE * Services.prompt.BUTTON_POS_2;
+ }
+
+ result = Services.prompt.confirmEx(
+ window,
+ dialogTitle,
+ dialogMsg,
+ promptFlags,
+ button1Title,
+ null,
+ button3Title,
+ null,
+ { value: 0 }
+ );
+
+ if (result == 0) {
+ // Save, but first finish HTML source mode
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ if (doPublish) {
+ // We save the command the user wanted to do in a global
+ // and return as if user canceled because publishing is asynchronous
+ // This command will be fired when publishing finishes
+ gCommandAfterPublishing = command;
+ goDoCommand("cmd_publish");
+ return false;
+ }
+
+ // Save to local disk
+ return SaveDocument(false, false, editor.contentsMIMEType);
+ }
+
+ if (result == 2) {
+ // "Don't Save"
+ return true;
+ }
+
+ // Default or result == 1 (Cancel)
+ return false;
+}
+
+// --------------------------- View menu ---------------------------
+
+function EditorSetCharacterSet(aEvent) {
+ try {
+ var editor = GetCurrentEditor();
+ if (aEvent.target.hasAttribute("charset")) {
+ editor.documentCharacterSet = aEvent.target.getAttribute("charset");
+ }
+ var docUrl = GetDocumentUrl();
+ if (!IsUrlAboutBlank(docUrl)) {
+ // reloading the document will reverse any changes to the META charset,
+ // we need to put them back in, which is achieved by a dedicated listener
+ editor.addDocumentStateListener(DocumentReloadListener);
+ EditorLoadUrl(docUrl);
+ }
+ } catch (e) {}
+}
+
+// --------------------------- Text style ---------------------------
+
+function onParagraphFormatChange(paraMenuList, commandID) {
+ if (!paraMenuList) {
+ return;
+ }
+
+ var commandNode = document.getElementById(commandID);
+ var state = commandNode.getAttribute("state");
+
+ // force match with "normal"
+ if (state == "body") {
+ state = "";
+ }
+
+ if (state == "mixed") {
+ // Selection is the "mixed" ( > 1 style) state
+ paraMenuList.selectedItem = null;
+ paraMenuList.setAttribute("label", GetString("Mixed"));
+ } else {
+ var menuPopup = document.getElementById("ParagraphPopup");
+ var menuItems = menuPopup.childNodes;
+ for (var i = 0; i < menuItems.length; i++) {
+ var menuItem = menuItems.item(i);
+ if ("value" in menuItem && menuItem.value == state) {
+ paraMenuList.selectedItem = menuItem;
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * Selects the current font face in the menulist.
+ *
+ * @param fontFaceMenuList The menulist element containing the list of fonts.
+ * @param commandID The commandID which holds the current font name
+ * in its "state" attribute.
+ */
+function onFontFaceChange(fontFaceMenuList, commandID) {
+ var commandNode = document.getElementById(commandID);
+ var editorFont = commandNode.getAttribute("state");
+
+ // Strip quotes in font names. Experiments have shown that we only
+ // ever get double quotes around the font name, never single quotes,
+ // even if they were in the HTML source. Also single or double
+ // quotes within the font name are never returned.
+ editorFont = editorFont.replace(/"/g, "");
+
+ switch (editorFont) {
+ case "mixed":
+ // Selection is the "mixed" ( > 1 style) state.
+ fontFaceMenuList.selectedItem = null;
+ fontFaceMenuList.setAttribute("label", GetString("Mixed"));
+ return;
+ case "":
+ case "serif":
+ case "sans-serif":
+ // Generic variable width.
+ fontFaceMenuList.selectedIndex = 0;
+ return;
+ case "tt":
+ case "monospace":
+ // Generic fixed width.
+ fontFaceMenuList.selectedIndex = 1;
+ return;
+ default:
+ }
+
+ let menuPopup = fontFaceMenuList.menupopup;
+ let menuItems = menuPopup.childNodes;
+
+ const genericFamilies = [
+ "serif",
+ "sans-serif",
+ "monospace",
+ "fantasy",
+ "cursive",
+ ];
+ // Bug 1139524: Normalise before we compare: Make it lower case
+ // and replace ", " with "," so that entries like
+ // "Helvetica, Arial, sans-serif" are always recognised correctly
+ let editorFontToLower = editorFont.toLowerCase().replace(/, /g, ",");
+ let foundFont = null;
+ let exactMatch = false;
+ let usedFontsSep = menuPopup.querySelector(
+ "menuseparator.fontFaceMenuAfterUsedFonts"
+ );
+ let editorFontOptions = editorFontToLower.split(",");
+ let editorOptionsCount = editorFontOptions.length;
+ let matchedFontIndex = editorOptionsCount; // initialise to high invalid value
+
+ // The font menu has this structure:
+ // 0: Variable Width
+ // 1: Fixed Width
+ // 2: Separator
+ // 3: Helvetica, Arial (stored as Helvetica, Arial, sans-serif)
+ // 4: Times (stored as Times New Roman, Times, serif)
+ // 5: Courier (stored as Courier New, Courier, monospace)
+ // 6: Separator, "menuseparator.fontFaceMenuAfterDefaultFonts"
+ // from 7: Used Font Section (for quick selection)
+ // followed by separator, "menuseparator.fontFaceMenuAfterUsedFonts"
+ // followed by all other available fonts.
+ // The following variable keeps track of where we are when we loop over the menu.
+ let afterUsedFontSection = false;
+
+ // The menu items not only have "label" and "value", but also some other attributes:
+ // "value_parsed": Is the toLowerCase() and space-stripped value.
+ // "value_cache": Is a concatenation of all editor fonts that were ever mapped
+ // onto this menu item. This is done for optimization.
+ // "used": This item is in the used font section.
+
+ for (let i = 0; i < menuItems.length; i++) {
+ let menuItem = menuItems.item(i);
+ if (
+ menuItem.hasAttribute("label") &&
+ menuItem.hasAttribute("value_parsed")
+ ) {
+ // The element seems to represent a font <menuitem>.
+ let fontMenuValue = menuItem.getAttribute("value_parsed");
+ if (
+ fontMenuValue == editorFontToLower ||
+ (menuItem.hasAttribute("value_cache") &&
+ menuItem
+ .getAttribute("value_cache")
+ .split("|")
+ .includes(editorFontToLower))
+ ) {
+ // This menuitem contains the font we are looking for.
+ foundFont = menuItem;
+ exactMatch = true;
+ break;
+ } else if (editorOptionsCount > 1 && afterUsedFontSection) {
+ // Once we are in the list of all other available fonts,
+ // we will find the one that best matches one of the options.
+ let matchPos = editorFontOptions.indexOf(fontMenuValue);
+ if (matchPos >= 0 && matchPos < matchedFontIndex) {
+ // This menu font comes earlier in the list of options,
+ // so prefer it.
+ matchedFontIndex = matchPos;
+ foundFont = menuItem;
+ // If we matched the first option, we don't need to look for
+ // a better match.
+ if (matchPos == 0) {
+ break;
+ }
+ }
+ }
+ } else if (menuItem == usedFontsSep) {
+ // Some other element type.
+ // We have now passed the section of used fonts and are now in the list of all.
+ afterUsedFontSection = true;
+ }
+ }
+
+ if (foundFont) {
+ let defaultFontsSep = menuPopup.querySelector(
+ "menuseparator.fontFaceMenuAfterDefaultFonts"
+ );
+ if (exactMatch) {
+ if (afterUsedFontSection) {
+ // Copy the matched font into the section of used fonts.
+ // We insert after the separator following the default fonts,
+ // so right at the beginning of the used fonts section.
+ let copyItem = foundFont.cloneNode(true);
+ menuPopup.insertBefore(copyItem, defaultFontsSep.nextSibling);
+ usedFontsSep.hidden = false;
+ foundFont = copyItem;
+ foundFont.setAttribute("used", "true");
+ }
+ } else {
+ // Keep only the found font and generic families in the font string.
+ editorFont = editorFont
+ .replace(/, /g, ",")
+ .split(",")
+ .filter(
+ font =>
+ font.toLowerCase() == foundFont.getAttribute("value_parsed") ||
+ genericFamilies.includes(font)
+ )
+ .join(",");
+
+ // Check if such an item is already in the used font section.
+ if (afterUsedFontSection) {
+ foundFont = menuPopup.querySelector(
+ 'menuitem[used="true"][value_parsed="' +
+ editorFont.toLowerCase() +
+ '"]'
+ );
+ }
+ // If not, create a new entry which will be inserted into that section.
+ if (!foundFont) {
+ foundFont = createFontFaceMenuitem(editorFont, editorFont, menuPopup);
+ }
+
+ // Add the editor font string into the 'cache' attribute in the element
+ // so we can later find it quickly without building the reduced string again.
+ let fontCache = "";
+ if (foundFont.hasAttribute("value_cache")) {
+ fontCache = foundFont.getAttribute("value_cache");
+ }
+ foundFont.setAttribute(
+ "value_cache",
+ fontCache + "|" + editorFontToLower
+ );
+
+ // If we created a new item, set it up and insert.
+ if (!foundFont.hasAttribute("used")) {
+ foundFont.setAttribute("used", "true");
+ usedFontsSep.hidden = false;
+ menuPopup.insertBefore(foundFont, defaultFontsSep.nextSibling);
+ }
+ }
+ } else {
+ // The editor encountered a font that is not installed on this system.
+ // Add it to the font menu now, in the used-fonts section right at the
+ // bottom before the separator of the section.
+ let fontLabel = GetFormattedString("NotInstalled", editorFont);
+ foundFont = createFontFaceMenuitem(fontLabel, editorFont, menuPopup);
+ foundFont.setAttribute("used", "true");
+ usedFontsSep.hidden = false;
+ menuPopup.insertBefore(foundFont, usedFontsSep);
+ }
+ fontFaceMenuList.selectedItem = foundFont;
+}
+
+/**
+ * Clears the used fonts list from all the font face menulists.
+ */
+function ClearUsedFonts() {
+ let userFontSeps = document.querySelectorAll(
+ "menuseparator.fontFaceMenuAfterDefaultFonts"
+ );
+ for (let userFontSep of userFontSeps) {
+ while (true) {
+ let nextNode = userFontSep.nextSibling;
+ if (nextNode.tagName != "menuseparator") {
+ nextNode.remove();
+ } else if (nextNode.classList.contains("fontFaceMenuAfterUsedFonts")) {
+ nextNode.hidden = true;
+ break;
+ }
+ }
+ }
+}
+
+function EditorSelectFontSize() {
+ var select = document.getElementById("FontSizeSelect");
+ if (select) {
+ if (select.selectedIndex == -1) {
+ return;
+ }
+
+ EditorSetFontSize(gFontSizeNames[select.selectedIndex]);
+ }
+}
+
+function onFontSizeChange(fontSizeMenulist, commandID) {
+ // If we don't match anything, set to "0 (normal)"
+ var newIndex = 2;
+ var size = fontSizeMenulist.getAttribute("size");
+ if (size == "mixed") {
+ // No single type selected
+ newIndex = -1;
+ } else {
+ for (var i = 0; i < gFontSizeNames.length; i++) {
+ if (gFontSizeNames[i] == size) {
+ newIndex = i;
+ break;
+ }
+ }
+ }
+ if (fontSizeMenulist.selectedIndex != newIndex) {
+ fontSizeMenulist.selectedIndex = newIndex;
+ }
+}
+
+function EditorSetFontSize(size) {
+ if (size == "0" || size == "normal" || size == "medium") {
+ EditorRemoveTextProperty("font", "size");
+ // Also remove big and small,
+ // else it will seem like size isn't changing correctly
+ EditorRemoveTextProperty("small", "");
+ EditorRemoveTextProperty("big", "");
+ } else {
+ // Temp: convert from new CSS size strings to old HTML size strings
+ switch (size) {
+ case "xx-small":
+ case "x-small":
+ size = "-2";
+ break;
+ case "small":
+ size = "-1";
+ break;
+ case "large":
+ size = "+1";
+ break;
+ case "x-large":
+ size = "+2";
+ break;
+ case "xx-large":
+ size = "+3";
+ break;
+ }
+ EditorSetTextProperty("font", "size", size);
+ }
+ gContentWindow.focus();
+}
+
+function initFontFaceMenu(menuPopup) {
+ initLocalFontFaceMenu(menuPopup);
+
+ if (menuPopup) {
+ var children = menuPopup.childNodes;
+ if (!children) {
+ return;
+ }
+
+ var mixed = { value: false };
+ var editorFont = GetCurrentEditor().getFontFaceState(mixed);
+
+ // Strip quotes in font names. Experiments have shown that we only
+ // ever get double quotes around the font name, never single quotes,
+ // even if they were in the HTML source. Also single or double
+ // quotes within the font name are never returned.
+ editorFont = editorFont.replace(/"/g, "");
+
+ if (!mixed.value) {
+ switch (editorFont) {
+ case "":
+ case "serif":
+ case "sans-serif":
+ // Generic variable width.
+ editorFont = "";
+ break;
+ case "tt":
+ case "monospace":
+ // Generic fixed width.
+ editorFont = "tt";
+ break;
+ default:
+ editorFont = editorFont.toLowerCase().replace(/, /g, ","); // bug 1139524
+ }
+ }
+
+ var editorFontOptions = editorFont.split(",");
+ var matchedOption = editorFontOptions.length; // initialise to high invalid value
+ for (var i = 0; i < children.length; i++) {
+ var menuItem = children[i];
+ if (menuItem.localName == "menuitem") {
+ var matchFound = false;
+ if (!mixed.value) {
+ var menuFont = menuItem
+ .getAttribute("value")
+ .toLowerCase()
+ .replace(/, /g, ",");
+
+ // First compare the entire font string to match items that contain commas.
+ if (menuFont == editorFont) {
+ menuItem.setAttribute("checked", "true");
+ break;
+ } else if (editorFontOptions.length > 1) {
+ // Next compare the individual options.
+ var matchPos = editorFontOptions.indexOf(menuFont);
+ if (matchPos >= 0 && matchPos < matchedOption) {
+ // This menu font comes earlier in the list of options,
+ // so prefer it.
+ menuItem.setAttribute("checked", "true");
+
+ // If we matched the first option, we don't need to look for
+ // a better match.
+ if (matchPos == 0) {
+ break;
+ }
+
+ matchedOption = matchPos;
+ matchFound = true;
+ }
+ }
+ }
+
+ // In case this item doesn't match, make sure we've cleared the checkmark.
+ if (!matchFound) {
+ menuItem.removeAttribute("checked");
+ }
+ }
+ }
+ }
+}
+
+// Number of fixed font face menuitems, these are:
+// Variable Width
+// Fixed Width
+// ==separator
+// Helvetica, Arial
+// Times
+// Courier
+// ==separator
+// ==separator
+const kFixedFontFaceMenuItems = 8;
+
+function initLocalFontFaceMenu(menuPopup) {
+ if (!gLocalFonts) {
+ // Build list of all local fonts once per editor
+ try {
+ var enumerator = Cc["@mozilla.org/gfx/fontenumerator;1"].getService(
+ Ci.nsIFontEnumerator
+ );
+ gLocalFonts = enumerator.EnumerateAllFonts();
+ } catch (e) {}
+ }
+
+ // Don't use radios for menulists.
+ let useRadioMenuitems = menuPopup.parentNode.localName == "menu";
+ menuPopup.setAttribute("useRadios", useRadioMenuitems);
+ if (menuPopup.childNodes.length == kFixedFontFaceMenuItems) {
+ if (gLocalFonts.length == 0) {
+ menuPopup.querySelector(".fontFaceMenuAfterDefaultFonts").hidden = true;
+ }
+ for (let i = 0; i < gLocalFonts.length; ++i) {
+ // Remove Linux system generic fonts that collide with CSS generic fonts.
+ if (
+ gLocalFonts[i] != "" &&
+ gLocalFonts[i] != "serif" &&
+ gLocalFonts[i] != "sans-serif" &&
+ gLocalFonts[i] != "monospace"
+ ) {
+ let itemNode = createFontFaceMenuitem(
+ gLocalFonts[i],
+ gLocalFonts[i],
+ menuPopup
+ );
+ menuPopup.appendChild(itemNode);
+ }
+ }
+ }
+}
+
+/**
+ * Creates a menuitem element for the font faces menulist. Returns the menuitem
+ * but does not add it automatically to the menupopup.
+ *
+ * @param aFontLabel Label to be displayed for the item.
+ * @param aFontName The font face value to be used for the item.
+ * Will be used in <font face="value"> in the edited document.
+ * @param aMenuPopup The menupopup for which this menuitem is created.
+ */
+function createFontFaceMenuitem(aFontLabel, aFontName, aMenuPopup) {
+ let itemNode = document.createXULElement("menuitem");
+ itemNode.setAttribute("label", aFontLabel);
+ itemNode.setAttribute("value", aFontName);
+ itemNode.setAttribute(
+ "value_parsed",
+ aFontName.toLowerCase().replace(/, /g, ",")
+ );
+ itemNode.setAttribute("tooltiptext", aFontLabel);
+ if (aMenuPopup.getAttribute("useRadios") == "true") {
+ itemNode.setAttribute("type", "radio");
+ itemNode.setAttribute("observes", "cmd_renderedHTMLEnabler");
+ }
+ return itemNode;
+}
+
+/**
+ * Helper function
+ */
+function getFontSizeIndex() {
+ var firstHas = { value: false };
+ var anyHas = { value: false };
+ var allHas = { value: false };
+
+ var fontSize = EditorGetTextProperty(
+ "font",
+ "size",
+ null,
+ firstHas,
+ anyHas,
+ allHas
+ );
+
+ // If the element has no size attribute and no size was found at all,
+ // we assume "medium" size. This is highly problematic since
+ // CSS sizes are not recognised and will show as "medium" as well.
+ // Currently we can't distinguish between "no attribute" which
+ // can imply "medium" and "CSS attribute present" which should not
+ // imply "medium".
+ if (!anyHas.value) {
+ return 2;
+ }
+
+ // Mixed selection.
+ if (!allHas.value) {
+ return -1;
+ }
+
+ switch (fontSize) {
+ case "-3":
+ case "-2":
+ case "0":
+ case "1":
+ // x-small.
+ return 0;
+ case "-1":
+ case "2":
+ // small.
+ return 1;
+ case "3":
+ // medium.
+ return 2;
+ case "+1":
+ case "4":
+ // large.
+ return 3;
+ case "+2":
+ case "5":
+ // x-large.
+ return 4;
+ case "+3":
+ case "+4":
+ case "6":
+ case "7":
+ // xx-large.
+ return 5;
+ }
+
+ // We shouldn't get here. All the selection has a value we don't understand.
+ return -1;
+}
+
+function initFontSizeMenu(menuPopup, fullMenu) {
+ if (menuPopup) {
+ var children = menuPopup.childNodes;
+ if (!children) {
+ return;
+ }
+
+ // Fixed size items start after menu separator depending on whether it is
+ // a full menu.
+ var menuIndex = fullMenu ? 3 : 0;
+
+ var setIndex = getFontSizeIndex();
+ if (setIndex >= 0) {
+ children[menuIndex + setIndex].setAttribute("checked", true);
+ } else {
+ // In case of mixed, clear all items.
+ for (var i = menuIndex; i < children.length; i++) {
+ children[i].setAttribute("checked", false);
+ }
+ }
+
+ // Some configurations might not have the "small/big" indicator as
+ // last item. If there is no indicator, we are done.
+ if (!menuPopup.lastChild.id.includes("smallBigInfo")) {
+ return;
+ }
+
+ // While it would be better to show the number of levels,
+ // at least this tells user if either of them are set.
+ var firstHas = { value: false };
+ var anyHas = { value: false };
+ var allHas = { value: false };
+
+ // Show "small"/"big" indicator.
+ var htmlInfo = "";
+ EditorGetTextProperty("small", "", "", firstHas, anyHas, allHas);
+ if (anyHas.value) {
+ htmlInfo = "<small>";
+ }
+ EditorGetTextProperty("big", "", "", firstHas, anyHas, allHas);
+ if (anyHas.value) {
+ htmlInfo += "<big>";
+ }
+
+ if (htmlInfo) {
+ menuPopup.lastChild.hidden = false;
+ menuPopup.lastChild.setAttribute("label", "HTML: " + htmlInfo);
+ menuPopup.lastChild.setAttribute("checked", true);
+ } else {
+ menuPopup.lastChild.hidden = true;
+ }
+ }
+}
+
+function onHighlightColorChange() {
+ ChangeButtonColor("cmd_highlight", "HighlightColorButton", "transparent");
+}
+
+function onFontColorChange() {
+ ChangeButtonColor("cmd_fontColor", "TextColorButton", gDefaultTextColor);
+}
+
+function onBackgroundColorChange() {
+ ChangeButtonColor(
+ "cmd_backgroundColor",
+ "BackgroundColorButton",
+ gDefaultBackgroundColor
+ );
+}
+
+/* Helper function that changes the button color.
+ * commandID - The ID of the command element.
+ * id - The ID of the button needing to be changed.
+ * defaultColor - The default color the button gets set to.
+ */
+function ChangeButtonColor(commandID, id, defaultColor) {
+ var commandNode = document.getElementById(commandID);
+ if (commandNode) {
+ var color = commandNode.getAttribute("state");
+ var button = document.getElementById(id);
+ if (button) {
+ button.setAttribute("color", color);
+
+ // No color or a mixed color - get color set on page or other defaults.
+ if (!color || color == "mixed") {
+ color = defaultColor;
+ }
+
+ button.setAttribute("style", "background-color:" + color + " !important");
+ }
+ }
+}
+
+// Call this when user changes text and/or background colors of the page
+function UpdateDefaultColors() {
+ var BrowserColors = GetDefaultBrowserColors();
+ var bodyelement = GetBodyElement();
+ var defTextColor = gDefaultTextColor;
+ var defBackColor = gDefaultBackgroundColor;
+
+ if (bodyelement) {
+ var color = bodyelement.getAttribute("text");
+ if (color) {
+ gDefaultTextColor = color;
+ } else if (BrowserColors) {
+ gDefaultTextColor = BrowserColors.TextColor;
+ }
+
+ color = bodyelement.getAttribute("bgcolor");
+ if (color) {
+ gDefaultBackgroundColor = color;
+ } else if (BrowserColors) {
+ gDefaultBackgroundColor = BrowserColors.BackgroundColor;
+ }
+ }
+
+ // Trigger update on toolbar
+ if (defTextColor != gDefaultTextColor) {
+ goUpdateCommandState("cmd_fontColor");
+ onFontColorChange();
+ }
+ if (defBackColor != gDefaultBackgroundColor) {
+ goUpdateCommandState("cmd_backgroundColor");
+ onBackgroundColorChange();
+ }
+}
+
+function GetBackgroundElementWithColor() {
+ var editor = GetCurrentTableEditor();
+ if (!editor) {
+ return null;
+ }
+
+ gColorObj.Type = "";
+ gColorObj.PageColor = "";
+ gColorObj.TableColor = "";
+ gColorObj.CellColor = "";
+ gColorObj.BackgroundColor = "";
+ gColorObj.SelectedType = "";
+
+ var tagNameObj = { value: "" };
+ var element;
+ try {
+ element = editor.getSelectedOrParentTableElement(tagNameObj, { value: 0 });
+ } catch (e) {}
+
+ if (element && tagNameObj && tagNameObj.value) {
+ gColorObj.BackgroundColor = GetHTMLOrCSSStyleValue(
+ element,
+ "bgcolor",
+ "background-color"
+ );
+ gColorObj.BackgroundColor = ConvertRGBColorIntoHEXColor(
+ gColorObj.BackgroundColor
+ );
+ if (tagNameObj.value.toLowerCase() == "td") {
+ gColorObj.Type = "Cell";
+ gColorObj.CellColor = gColorObj.BackgroundColor;
+
+ // Get any color that might be on parent table
+ var table = GetParentTable(element);
+ gColorObj.TableColor = GetHTMLOrCSSStyleValue(
+ table,
+ "bgcolor",
+ "background-color"
+ );
+ gColorObj.TableColor = ConvertRGBColorIntoHEXColor(gColorObj.TableColor);
+ } else {
+ gColorObj.Type = "Table";
+ gColorObj.TableColor = gColorObj.BackgroundColor;
+ }
+ gColorObj.SelectedType = gColorObj.Type;
+ } else {
+ let IsCSSPrefChecked = Services.prefs.getBoolPref(kUseCssPref);
+ if (IsCSSPrefChecked && IsHTMLEditor()) {
+ let selection = editor.selection;
+ if (selection) {
+ element = selection.focusNode;
+ while (!editor.nodeIsBlock(element)) {
+ element = element.parentNode;
+ }
+ } else {
+ element = GetBodyElement();
+ }
+ } else {
+ element = GetBodyElement();
+ }
+ if (element) {
+ gColorObj.Type = "Page";
+ gColorObj.BackgroundColor = GetHTMLOrCSSStyleValue(
+ element,
+ "bgcolor",
+ "background-color"
+ );
+ if (gColorObj.BackgroundColor == "") {
+ gColorObj.BackgroundColor = "transparent";
+ } else {
+ gColorObj.BackgroundColor = ConvertRGBColorIntoHEXColor(
+ gColorObj.BackgroundColor
+ );
+ }
+ gColorObj.PageColor = gColorObj.BackgroundColor;
+ }
+ }
+ return element;
+}
+
+function SetSmiley(smileyText) {
+ try {
+ GetCurrentEditor().insertText(smileyText);
+ gContentWindow.focus();
+ } catch (e) {}
+}
+
+/* eslint-disable complexity */
+function EditorSelectColor(colorType, mouseEvent) {
+ var editor = GetCurrentEditor();
+ if (!editor || !gColorObj) {
+ return;
+ }
+
+ // Shift + mouse click automatically applies last color, if available
+ var useLastColor = mouseEvent
+ ? mouseEvent.button == 0 && mouseEvent.shiftKey
+ : false;
+ var element;
+ var table;
+ var currentColor = "";
+ var commandNode;
+
+ if (!colorType) {
+ colorType = "";
+ }
+
+ if (colorType == "Text") {
+ gColorObj.Type = colorType;
+
+ // Get color from command node state
+ commandNode = document.getElementById("cmd_fontColor");
+ currentColor = commandNode.getAttribute("state");
+ currentColor = ConvertRGBColorIntoHEXColor(currentColor);
+ gColorObj.TextColor = currentColor;
+
+ if (useLastColor && gColorObj.LastTextColor) {
+ gColorObj.TextColor = gColorObj.LastTextColor;
+ } else {
+ useLastColor = false;
+ }
+ } else if (colorType == "Highlight") {
+ gColorObj.Type = colorType;
+
+ // Get color from command node state
+ commandNode = document.getElementById("cmd_highlight");
+ currentColor = commandNode.getAttribute("state");
+ currentColor = ConvertRGBColorIntoHEXColor(currentColor);
+ gColorObj.HighlightColor = currentColor;
+
+ if (useLastColor && gColorObj.LastHighlightColor) {
+ gColorObj.HighlightColor = gColorObj.LastHighlightColor;
+ } else {
+ useLastColor = false;
+ }
+ } else {
+ element = GetBackgroundElementWithColor();
+ if (!element) {
+ return;
+ }
+
+ // Get the table if we found a cell
+ if (gColorObj.Type == "Table") {
+ table = element;
+ } else if (gColorObj.Type == "Cell") {
+ table = GetParentTable(element);
+ }
+
+ // Save to avoid resetting if not necessary
+ currentColor = gColorObj.BackgroundColor;
+
+ if (colorType == "TableOrCell" || colorType == "Cell") {
+ if (gColorObj.Type == "Cell") {
+ gColorObj.Type = colorType;
+ } else if (gColorObj.Type != "Table") {
+ return;
+ }
+ } else if (colorType == "Table" && gColorObj.Type == "Page") {
+ return;
+ }
+
+ if (colorType == "" && gColorObj.Type == "Cell") {
+ // Using empty string for requested type means
+ // we can let user select cell or table
+ gColorObj.Type = "TableOrCell";
+ }
+
+ if (useLastColor && gColorObj.LastBackgroundColor) {
+ gColorObj.BackgroundColor = gColorObj.LastBackgroundColor;
+ } else {
+ useLastColor = false;
+ }
+ }
+ // Save the type we are really requesting
+ colorType = gColorObj.Type;
+
+ if (!useLastColor) {
+ // Avoid the JS warning
+ gColorObj.NoDefault = false;
+
+ // Launch the ColorPicker dialog
+ // TODO: Figure out how to position this under the color buttons on the toolbar
+ window.openDialog(
+ "chrome://editor/content/EdColorPicker.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ "",
+ gColorObj
+ );
+
+ // User canceled the dialog
+ if (gColorObj.Cancel) {
+ return;
+ }
+ }
+
+ if (gColorObj.Type == "Text") {
+ if (currentColor != gColorObj.TextColor) {
+ if (gColorObj.TextColor) {
+ EditorSetTextProperty("font", "color", gColorObj.TextColor);
+ } else {
+ EditorRemoveTextProperty("font", "color");
+ }
+ }
+ // Update the command state (this will trigger color button update)
+ goUpdateCommandState("cmd_fontColor");
+ } else if (gColorObj.Type == "Highlight") {
+ if (currentColor != gColorObj.HighlightColor) {
+ if (gColorObj.HighlightColor) {
+ EditorSetTextProperty("font", "bgcolor", gColorObj.HighlightColor);
+ } else {
+ EditorRemoveTextProperty("font", "bgcolor");
+ }
+ }
+ // Update the command state (this will trigger color button update)
+ goUpdateCommandState("cmd_highlight");
+ } else if (element) {
+ if (gColorObj.Type == "Table") {
+ // Set background on a table
+ // Note that we shouldn't trust "currentColor" because of "TableOrCell" behavior
+ if (table) {
+ var bgcolor = table.getAttribute("bgcolor");
+ if (bgcolor != gColorObj.BackgroundColor) {
+ try {
+ if (gColorObj.BackgroundColor) {
+ editor.setAttributeOrEquivalent(
+ table,
+ "bgcolor",
+ gColorObj.BackgroundColor,
+ false
+ );
+ } else {
+ editor.removeAttributeOrEquivalent(table, "bgcolor", false);
+ }
+ } catch (e) {}
+ }
+ }
+ } else if (currentColor != gColorObj.BackgroundColor && IsHTMLEditor()) {
+ editor.beginTransaction();
+ try {
+ editor.setBackgroundColor(gColorObj.BackgroundColor);
+
+ if (gColorObj.Type == "Page" && gColorObj.BackgroundColor) {
+ // Set all page colors not explicitly set,
+ // else you can end up with unreadable pages
+ // because viewer's default colors may not be same as page author's
+ var bodyelement = GetBodyElement();
+ if (bodyelement) {
+ var defColors = GetDefaultBrowserColors();
+ if (defColors) {
+ if (!bodyelement.getAttribute("text")) {
+ editor.setAttributeOrEquivalent(
+ bodyelement,
+ "text",
+ defColors.TextColor,
+ false
+ );
+ }
+
+ // The following attributes have no individual CSS declaration counterparts
+ // Getting rid of them in favor of CSS implies CSS rules management
+ if (!bodyelement.getAttribute("link")) {
+ editor.setAttribute(bodyelement, "link", defColors.LinkColor);
+ }
+
+ if (!bodyelement.getAttribute("alink")) {
+ editor.setAttribute(
+ bodyelement,
+ "alink",
+ defColors.ActiveLinkColor
+ );
+ }
+
+ if (!bodyelement.getAttribute("vlink")) {
+ editor.setAttribute(
+ bodyelement,
+ "vlink",
+ defColors.VisitedLinkColor
+ );
+ }
+ }
+ }
+ }
+ } catch (e) {}
+
+ editor.endTransaction();
+ }
+
+ goUpdateCommandState("cmd_backgroundColor");
+ }
+ gContentWindow.focus();
+}
+/* eslint-enable complexity */
+
+function GetParentTable(element) {
+ var node = element;
+ while (node) {
+ if (node.nodeName.toLowerCase() == "table") {
+ return node;
+ }
+
+ node = node.parentNode;
+ }
+ return node;
+}
+
+function GetParentTableCell(element) {
+ var node = element;
+ while (node) {
+ if (
+ node.nodeName.toLowerCase() == "td" ||
+ node.nodeName.toLowerCase() == "th"
+ ) {
+ return node;
+ }
+
+ node = node.parentNode;
+ }
+ return node;
+}
+
+function EditorDblClick(event) {
+ // We check event.explicitOriginalTarget here because .target will never
+ // be a textnode (bug 193689)
+ if (event.explicitOriginalTarget) {
+ // Only bring up properties if clicked on an element or selected link
+ var element;
+ try {
+ element = event.explicitOriginalTarget;
+ } catch (e) {}
+
+ // We use "href" instead of "a" to not be fooled by named anchor
+ if (!element) {
+ try {
+ element = GetCurrentEditor().getSelectedElement("href");
+ } catch (e) {}
+ }
+
+ // Don't fire for body/p and other block elements.
+ // It's common that people try to double-click
+ // to select a word, but the click hits an empty area.
+ if (
+ element &&
+ ![
+ "body",
+ "p",
+ "h1",
+ "h2",
+ "h3",
+ "h4",
+ "h5",
+ "h6",
+ "blockquote",
+ "div",
+ "pre",
+ ].includes(element.nodeName.toLowerCase())
+ ) {
+ goDoCommand("cmd_objectProperties");
+ event.preventDefault();
+ }
+ }
+}
+
+function EditorClick(event) {
+ // For Web Composer: In Show All Tags Mode,
+ // single click selects entire element,
+ // except for body and table elements
+ if (gEditorDisplayMode == kDisplayModeAllTags) {
+ try {
+ // We check event.explicitOriginalTarget here because .target will never
+ // be a textnode (bug 193689)
+ var element = event.explicitOriginalTarget;
+ var name = element.localName;
+ if (!["body", "caption", "table", "td", "th", "tr"].includes(name)) {
+ GetCurrentEditor().selectElement(event.explicitOriginalTarget);
+ event.preventDefault();
+ }
+ } catch (e) {}
+ }
+}
+
+/* TODO: We need an oncreate hook to do enabling/disabling for the
+ Format menu. There should be code like this for the
+ object-specific "Properties" item
+*/
+// For property dialogs, we want the selected element,
+// but will accept a parent link, list, or table cell if inside one
+function GetObjectForProperties() {
+ var editor = GetCurrentEditor();
+ if (!editor || !IsHTMLEditor()) {
+ return null;
+ }
+
+ var element;
+ try {
+ element = editor.getSelectedElement("");
+ } catch (e) {}
+ if (element) {
+ if (element.namespaceURI == "http://www.w3.org/1998/Math/MathML") {
+ // If the object is a MathML element, we collapse the selection on it and
+ // we return its <math> ancestor. Hence the math dialog will be used.
+ GetCurrentEditor().selection.collapse(element, 0);
+ } else {
+ return element;
+ }
+ }
+
+ // Find nearest parent of selection anchor node
+ // that is a link, list, table cell, or table
+
+ var anchorNode;
+ var node;
+ try {
+ anchorNode = editor.selection.anchorNode;
+ if (anchorNode.firstChild) {
+ // Start at actual selected node
+ var offset = editor.selection.anchorOffset;
+ // Note: If collapsed, offset points to element AFTER caret,
+ // thus node may be null
+ node = anchorNode.childNodes.item(offset);
+ }
+ if (!node) {
+ node = anchorNode;
+ }
+ } catch (e) {}
+
+ while (node) {
+ if (node.nodeName) {
+ var nodeName = node.nodeName.toLowerCase();
+
+ // Done when we hit the body or #text.
+ if (nodeName == "body" || nodeName == "#text") {
+ break;
+ }
+
+ if (
+ (nodeName == "a" && node.href) ||
+ nodeName == "ol" ||
+ nodeName == "ul" ||
+ nodeName == "dl" ||
+ nodeName == "td" ||
+ nodeName == "th" ||
+ nodeName == "table" ||
+ nodeName == "math"
+ ) {
+ return node;
+ }
+ }
+ node = node.parentNode;
+ }
+ return null;
+}
+
+function SetEditMode(mode) {
+ if (!IsHTMLEditor()) {
+ return;
+ }
+
+ var bodyElement = GetBodyElement();
+ if (!bodyElement) {
+ dump("SetEditMode: We don't have a body node!\n");
+ return;
+ }
+
+ // must have editor if here!
+ var editor = GetCurrentEditor();
+ var inlineSpellCheckItem = document.getElementById("menu_inlineSpellCheck");
+
+ // Switch the UI mode before inserting contents
+ // so user can't type in source window while new window is being filled
+ var previousMode = gEditorDisplayMode;
+ if (!SetDisplayMode(mode)) {
+ return;
+ }
+
+ if (mode == kDisplayModeSource) {
+ // Display the DOCTYPE as a non-editable string above edit area
+ var domdoc;
+ try {
+ domdoc = editor.document;
+ } catch (e) {
+ dump(e + "\n");
+ }
+ if (domdoc) {
+ var doctypeNode = document.getElementById("doctype-text");
+ var dt = domdoc.doctype;
+ if (doctypeNode) {
+ if (dt) {
+ doctypeNode.collapsed = false;
+ var doctypeText = "<!DOCTYPE " + domdoc.doctype.name;
+ if (dt.publicId) {
+ doctypeText += ' PUBLIC "' + domdoc.doctype.publicId;
+ }
+ if (dt.systemId) {
+ doctypeText += ' "' + dt.systemId;
+ }
+ doctypeText += '">';
+ doctypeNode.setAttribute("value", doctypeText);
+ } else {
+ doctypeNode.collapsed = true;
+ }
+ }
+ }
+ // Get the entire document's source string
+
+ var flags =
+ editor.documentCharacterSet == "ISO-8859-1"
+ ? kOutputEncodeLatin1Entities
+ : kOutputEncodeBasicEntities;
+ try {
+ let encodeEntity = Services.prefs.getCharPref("editor.encode_entity");
+ switch (encodeEntity) {
+ case "basic":
+ flags = kOutputEncodeBasicEntities;
+ break;
+ case "latin1":
+ flags = kOutputEncodeLatin1Entities;
+ break;
+ case "html":
+ flags = kOutputEncodeHTMLEntities;
+ break;
+ case "none":
+ flags = 0;
+ break;
+ }
+ } catch (e) {}
+
+ if (Services.prefs.getBoolPref("editor.prettyprint")) {
+ flags |= kOutputFormatted;
+ }
+
+ flags |= kOutputLFLineBreak;
+ var source = editor.outputToString(editor.contentsMIMEType, flags);
+ var start = source.search(/<html/i);
+ if (start == -1) {
+ start = 0;
+ }
+ gSourceTextEditor.insertText(source.slice(start));
+ gSourceTextEditor.resetModificationCount();
+ gSourceTextEditor.addDocumentStateListener(gSourceTextListener);
+ gSourceTextEditor.enableUndo(true);
+ gSourceContentWindow.commandManager.addCommandObserver(
+ gSourceTextObserver,
+ "cmd_undo"
+ );
+ gSourceContentWindow.contentWindow.focus();
+ goDoCommand("cmd_moveTop");
+ } else if (previousMode == kDisplayModeSource) {
+ // Only rebuild document if a change was made in source window
+ if (IsHTMLSourceChanged()) {
+ // Disable spell checking when rebuilding source
+ InlineSpellCheckerUI.enabled = false;
+ inlineSpellCheckItem.removeAttribute("checked");
+
+ // Reduce the undo count so we don't use too much memory
+ // during multiple uses of source window
+ // (reinserting entire doc caches all nodes)
+ editor.clearUndoRedo();
+
+ editor.beginTransaction();
+ try {
+ // We are coming from edit source mode,
+ // so transfer that back into the document
+ source = gSourceTextEditor
+ .outputToString(kTextMimeType, kOutputLFLineBreak)
+ .trim();
+ if (editor.contentsMIMEType != kXHTMLMimeType) {
+ editor.rebuildDocumentFromSource(source); // This is undoable
+ } else {
+ /* eslint-disable-next-line no-unsanitized/method */
+ var fragment = editor.document
+ .createRange()
+ .createContextualFragment(source);
+ GetBodyElement().remove();
+ editor.document.replaceChild(
+ fragment.firstChild,
+ editor.document.documentElement
+ );
+ // We touched the DOM tree without a transaction here so that we
+ // broke undoable transactions. However, we cleared all undoable
+ // things above. Therefore nothing must be in the undo stack.
+ }
+
+ // Get the text for the <title> from the newly-parsed document
+ // (must do this for proper conversion of "escaped" characters)
+ let titleNode = editor.document.querySelector("title");
+ SetDocumentTitle(titleNode ? titleNode.textContent : "");
+ } catch (ex) {
+ dump(ex);
+ }
+ // If the MIME type is kXHTMLMimeType, we don't put any undoable
+ // transaction. Then, this endTransaction() call does not allow to
+ // live empty transaction. Therefore, the unnecessary empty transaction
+ // will be cleared here automatically.
+ editor.endTransaction();
+ }
+
+ // Clear out the string buffers
+ gSourceContentWindow.commandManager.removeCommandObserver(
+ gSourceTextObserver,
+ "cmd_undo"
+ );
+ gSourceTextEditor.removeDocumentStateListener(gSourceTextListener);
+ gSourceTextEditor.enableUndo(false);
+ gSourceTextEditor.selectAll();
+ gSourceTextEditor.deleteSelection(
+ gSourceTextEditor.eNone,
+ gSourceTextEditor.eStrip
+ );
+ gSourceTextEditor.resetModificationCount();
+
+ gContentWindow.focus();
+ // goDoCommand("cmd_moveTop");
+ }
+
+ switch (mode) {
+ case kDisplayModePreview:
+ // Disable spell checking when previewing
+ InlineSpellCheckerUI.enabled = false;
+ inlineSpellCheckItem.removeAttribute("checked");
+ inlineSpellCheckItem.setAttribute("disabled", true);
+ break;
+ case kDisplayModeSource:
+ inlineSpellCheckItem.setAttribute("disabled", true);
+ goSetCommandEnabled("cmd_pasteQuote", false);
+ break;
+ default:
+ inlineSpellCheckItem.setAttribute(
+ "disabled",
+ !InlineSpellCheckerUI.canSpellCheck
+ );
+ break;
+ }
+}
+
+function CancelHTMLSource() {
+ // Don't convert source text back into the DOM document
+ gSourceTextEditor.resetModificationCount();
+ SetDisplayMode(gPreviousNonSourceDisplayMode);
+}
+
+function SetDisplayMode(mode) {
+ if (!IsHTMLEditor()) {
+ return false;
+ }
+
+ // Already in requested mode:
+ // return false to indicate we didn't switch
+ if (mode == gEditorDisplayMode) {
+ return false;
+ }
+
+ var previousMode = gEditorDisplayMode;
+ gEditorDisplayMode = mode;
+
+ ResetStructToolbar();
+ if (mode == kDisplayModeSource) {
+ // Switch to the sourceWindow (second in the deck)
+ gContentWindowDeck.selectedIndex = 1;
+
+ // Hide the formatting toolbar if not already hidden
+ gFormatToolbarHidden = gFormatToolbar.hidden;
+ gFormatToolbar.hidden = true;
+ gFormatToolbar.setAttribute("hideinmenu", "true");
+
+ gSourceContentWindow.contentWindow.focus();
+ } else {
+ // Save the last non-source mode so we can cancel source editing easily
+ gPreviousNonSourceDisplayMode = mode;
+
+ // Load/unload appropriate override style sheet
+ try {
+ var editor = GetCurrentEditor();
+ editor.QueryInterface(nsIEditorStyleSheets);
+ editor instanceof Ci.nsIHTMLObjectResizer;
+
+ switch (mode) {
+ case kDisplayModePreview:
+ // Disable all extra "edit mode" style sheets
+ editor.enableStyleSheet(kNormalStyleSheet, false);
+ editor.enableStyleSheet(kAllTagsStyleSheet, false);
+ editor.objectResizingEnabled = true;
+ break;
+
+ case kDisplayModeNormal:
+ editor.addOverrideStyleSheet(kNormalStyleSheet);
+ // Disable ShowAllTags mode
+ editor.enableStyleSheet(kAllTagsStyleSheet, false);
+ editor.objectResizingEnabled = true;
+ break;
+
+ case kDisplayModeAllTags:
+ editor.addOverrideStyleSheet(kNormalStyleSheet);
+ editor.addOverrideStyleSheet(kAllTagsStyleSheet);
+ // don't allow resizing in AllTags mode because the visible tags
+ // change the computed size of images and tables...
+ if (editor.resizedObject) {
+ editor.hideResizers();
+ }
+ editor.objectResizingEnabled = false;
+ break;
+ }
+ } catch (e) {}
+
+ // Switch to the normal editor (first in the deck)
+ gContentWindowDeck.selectedIndex = 0;
+
+ // Restore menus and toolbars
+ if (previousMode == kDisplayModeSource) {
+ gFormatToolbar.hidden = gFormatToolbarHidden;
+ gFormatToolbar.removeAttribute("hideinmenu");
+ }
+
+ gContentWindow.focus();
+ }
+
+ // update commands to disable or re-enable stuff
+ window.updateCommands("mode_switch");
+
+ // Set the selected tab at bottom of window:
+ // (Note: Setting "selectedIndex = mode" won't redraw tabs when menu is used.)
+ document.getElementById(
+ "EditModeTabs"
+ ).selectedItem = document.getElementById(kDisplayModeTabIDS[mode]);
+
+ // Uncheck previous menuitem and set new check since toolbar may have been used
+ if (previousMode >= 0) {
+ document
+ .getElementById(kDisplayModeMenuIDs[previousMode])
+ .setAttribute("checked", "false");
+ }
+ document
+ .getElementById(kDisplayModeMenuIDs[mode])
+ .setAttribute("checked", "true");
+
+ return true;
+}
+
+function UpdateWindowTitle() {
+ try {
+ var filename = "";
+ var windowTitle = "";
+ var title = GetDocumentTitle();
+
+ // Append just the 'leaf' filename to the Doc. Title for the window caption
+ var docUrl = GetDocumentUrl();
+ if (docUrl && !IsUrlAboutBlank(docUrl)) {
+ var scheme = GetScheme(docUrl);
+ filename = GetFilename(docUrl);
+ if (filename) {
+ windowTitle = " [" + scheme + ":/.../" + filename + "]";
+ }
+
+ var fileType = IsHTMLEditor() ? "html" : "text";
+ // Save changed title in the recent pages data in prefs
+ SaveRecentFilesPrefs(title, fileType);
+ }
+
+ // Set window title with " - Composer" or " - Text Editor" appended.
+ var xulWin = document.documentElement;
+
+ document.title =
+ (title || filename || window.gUntitledString) +
+ windowTitle +
+ xulWin.getAttribute("titlemenuseparator") +
+ xulWin.getAttribute("titlemodifier");
+ } catch (e) {
+ dump(e);
+ }
+}
+
+function SaveRecentFilesPrefs(aTitle, aFileType) {
+ var curUrl = StripPassword(GetDocumentUrl());
+ var historyCount = Services.prefs.getIntPref("editor.history.url_maximum");
+
+ var titleArray = [];
+ var urlArray = [];
+ var typeArray = [];
+
+ if (historyCount && !IsUrlAboutBlank(curUrl) && GetScheme(curUrl) != "data") {
+ titleArray.push(aTitle);
+ urlArray.push(curUrl);
+ typeArray.push(aFileType);
+ }
+
+ for (let i = 0; i < historyCount && urlArray.length < historyCount; i++) {
+ let url = Services.prefs.getStringPref("editor.history_url_" + i, "");
+
+ // Continue if URL pref is missing because
+ // a URL not found during loading may have been removed
+
+ // Skip over current an "data" URLs
+ if (url && url != curUrl && GetScheme(url) != "data") {
+ let title = Services.prefs.getStringPref("editor.history_title_" + i, "");
+ let fileType = Services.prefs.getStringPref(
+ "editor.history_type_" + i,
+ ""
+ );
+ titleArray.push(title);
+ urlArray.push(url);
+ typeArray.push(fileType);
+ }
+ }
+
+ // Resave the list back to prefs in the new order
+ for (let i = 0; i < urlArray.length; i++) {
+ SetStringPref("editor.history_title_" + i, titleArray[i]);
+ SetStringPref("editor.history_url_" + i, urlArray[i]);
+ SetStringPref("editor.history_type_" + i, typeArray[i]);
+ }
+}
+
+function EditorInitFormatMenu() {
+ try {
+ InitObjectPropertiesMenuitem();
+ InitRemoveStylesMenuitems(
+ "removeStylesMenuitem",
+ "removeLinksMenuitem",
+ "removeNamedAnchorsMenuitem"
+ );
+ } catch (ex) {}
+}
+
+function InitObjectPropertiesMenuitem() {
+ // Set strings and enable for the [Object] Properties item
+ // Note that we directly do the enabling instead of
+ // using goSetCommandEnabled since we already have the command.
+ var cmd = document.getElementById("cmd_objectProperties");
+ if (!cmd) {
+ return null;
+ }
+
+ var element;
+ var menuStr = GetString("AdvancedProperties");
+ var name;
+
+ if (IsEditingRenderedHTML()) {
+ element = GetObjectForProperties();
+ }
+
+ if (element && element.nodeName) {
+ var objStr = "";
+ cmd.removeAttribute("disabled");
+ name = element.nodeName.toLowerCase();
+ switch (name) {
+ case "img":
+ // Check if img is enclosed in link
+ // (use "href" to not be fooled by named anchor)
+ try {
+ if (GetCurrentEditor().getElementOrParentByTagName("href", element)) {
+ objStr = GetString("ImageAndLink");
+ // Return "href" so it is detected as a link.
+ name = "href";
+ }
+ } catch (e) {}
+
+ if (objStr == "") {
+ objStr = GetString("Image");
+ }
+ break;
+ case "hr":
+ objStr = GetString("HLine");
+ break;
+ case "table":
+ objStr = GetString("Table");
+ break;
+ case "th":
+ name = "td";
+ // Falls through
+ case "td":
+ objStr = GetString("TableCell");
+ break;
+ case "ol":
+ case "ul":
+ case "dl":
+ objStr = GetString("List");
+ break;
+ case "li":
+ objStr = GetString("ListItem");
+ break;
+ case "form":
+ objStr = GetString("Form");
+ break;
+ case "input":
+ var type = element.getAttribute("type");
+ if (type && type.toLowerCase() == "image") {
+ objStr = GetString("InputImage");
+ } else {
+ objStr = GetString("InputTag");
+ }
+ break;
+ case "textarea":
+ objStr = GetString("TextArea");
+ break;
+ case "select":
+ objStr = GetString("Select");
+ break;
+ case "button":
+ objStr = GetString("Button");
+ break;
+ case "label":
+ objStr = GetString("Label");
+ break;
+ case "fieldset":
+ objStr = GetString("FieldSet");
+ break;
+ case "a":
+ if (element.name) {
+ objStr = GetString("NamedAnchor");
+ name = "anchor";
+ } else if (element.href) {
+ objStr = GetString("Link");
+ name = "href";
+ }
+ break;
+ }
+ if (objStr) {
+ menuStr = GetString("ObjectProperties").replace(/%obj%/, objStr);
+ }
+ } else {
+ // We show generic "Properties" string, but disable the command.
+ cmd.setAttribute("disabled", "true");
+ }
+ cmd.setAttribute("label", menuStr);
+ cmd.setAttribute("accesskey", GetString("ObjectPropertiesAccessKey"));
+ return name;
+}
+
+function InitParagraphMenu() {
+ var mixedObj = { value: null };
+ var state;
+ try {
+ state = GetCurrentEditor().getParagraphState(mixedObj);
+ } catch (e) {}
+ var IDSuffix;
+
+ // PROBLEM: When we get blockquote, it masks other styles contained by it
+ // We need a separate method to get blockquote state
+
+ // We use "x" as uninitialized paragraph state
+ if (!state || state == "x") {
+ // No paragraph container.
+ IDSuffix = "bodyText";
+ } else {
+ IDSuffix = state;
+ }
+
+ // Set "radio" check on one item, but...
+ var menuItem = document.getElementById("menu_" + IDSuffix);
+ menuItem.setAttribute("checked", "true");
+
+ // ..."bodyText" is returned if mixed selection, so remove checkmark
+ if (mixedObj.value) {
+ menuItem.setAttribute("checked", "false");
+ }
+}
+
+function GetListStateString() {
+ try {
+ var editor = GetCurrentEditor();
+
+ var mixedObj = { value: null };
+ var hasOL = { value: false };
+ var hasUL = { value: false };
+ var hasDL = { value: false };
+ editor.getListState(mixedObj, hasOL, hasUL, hasDL);
+
+ if (mixedObj.value) {
+ return "mixed";
+ }
+ if (hasOL.value) {
+ return "ol";
+ }
+ if (hasUL.value) {
+ return "ul";
+ }
+
+ if (hasDL.value) {
+ var hasLI = { value: false };
+ var hasDT = { value: false };
+ var hasDD = { value: false };
+ editor.getListItemState(mixedObj, hasLI, hasDT, hasDD);
+ if (mixedObj.value) {
+ return "mixed";
+ }
+ if (hasLI.value) {
+ return "li";
+ }
+ if (hasDT.value) {
+ return "dt";
+ }
+ if (hasDD.value) {
+ return "dd";
+ }
+ }
+ } catch (e) {}
+
+ // return "noList" if we aren't in a list at all
+ return "noList";
+}
+
+function InitListMenu() {
+ if (!IsHTMLEditor()) {
+ return;
+ }
+
+ var IDSuffix = GetListStateString();
+
+ // Set enable state for the "None" menuitem
+ goSetCommandEnabled("cmd_removeList", IDSuffix != "noList");
+
+ // Set "radio" check on one item, but...
+ // we won't find a match if it's "mixed"
+ var menuItem = document.getElementById("menu_" + IDSuffix);
+ if (menuItem) {
+ menuItem.setAttribute("checked", "true");
+ }
+}
+
+function GetAlignmentString() {
+ var mixedObj = { value: null };
+ var alignObj = { value: null };
+ try {
+ GetCurrentEditor().getAlignment(mixedObj, alignObj);
+ } catch (e) {}
+
+ if (mixedObj.value) {
+ return "mixed";
+ }
+ if (alignObj.value == nsIHTMLEditor.eLeft) {
+ return "left";
+ }
+ if (alignObj.value == nsIHTMLEditor.eCenter) {
+ return "center";
+ }
+ if (alignObj.value == nsIHTMLEditor.eRight) {
+ return "right";
+ }
+ if (alignObj.value == nsIHTMLEditor.eJustify) {
+ return "justify";
+ }
+
+ // return "left" if we got here
+ return "left";
+}
+
+function InitAlignMenu() {
+ if (!IsHTMLEditor()) {
+ return;
+ }
+
+ var IDSuffix = GetAlignmentString();
+
+ // we won't find a match if it's "mixed"
+ var menuItem = document.getElementById("menu_" + IDSuffix);
+ if (menuItem) {
+ menuItem.setAttribute("checked", "true");
+ }
+}
+
+function EditorSetDefaultPrefsAndDoctype() {
+ var editor = GetCurrentEditor();
+
+ var domdoc;
+ try {
+ domdoc = editor.document;
+ } catch (e) {
+ dump(e + "\n");
+ }
+ if (!domdoc) {
+ dump("EditorSetDefaultPrefsAndDoctype: EDITOR DOCUMENT NOT FOUND\n");
+ return;
+ }
+
+ // Insert a doctype element
+ // if it is missing from existing doc
+ if (!domdoc.doctype) {
+ var newdoctype = domdoc.implementation.createDocumentType(
+ "HTML",
+ "-//W3C//DTD HTML 4.01 Transitional//EN",
+ ""
+ );
+ if (newdoctype) {
+ domdoc.insertBefore(newdoctype, domdoc.firstChild);
+ }
+ }
+
+ // search for head; we'll need this for meta tag additions
+ let headelement = domdoc.querySelector("head");
+ if (!headelement) {
+ headelement = domdoc.createElement("head");
+ if (headelement) {
+ domdoc.insertAfter(headelement, domdoc.firstChild);
+ }
+ }
+
+ /* only set default prefs for new documents */
+ if (!IsUrlAboutBlank(GetDocumentUrl())) {
+ return;
+ }
+
+ // search for author meta tag.
+ // if one is found, don't do anything.
+ // if not, create one and make it a child of the head tag
+ // and set its content attribute to the value of the editor.author preference.
+
+ if (domdoc.querySelector("meta")) {
+ // we should do charset first since we need to have charset before
+ // hitting other 8-bit char in other meta tags
+ // grab charset pref and make it the default charset
+ var element;
+ var prefCharsetString = Services.prefs.getCharPref(
+ "intl.charset.fallback.override"
+ );
+ if (prefCharsetString) {
+ editor.documentCharacterSet = prefCharsetString;
+ }
+
+ // let's start by assuming we have an author in case we don't have the pref
+
+ var prefAuthorString = null;
+ let authorFound = domdoc.querySelector('meta[name="author"]');
+ try {
+ prefAuthorString = Services.prefs.getStringPref("editor.author");
+ } catch (ex) {}
+ if (
+ prefAuthorString &&
+ prefAuthorString != 0 &&
+ !authorFound &&
+ headelement
+ ) {
+ // create meta tag with 2 attributes
+ element = domdoc.createElement("meta");
+ if (element) {
+ element.setAttribute("name", "author");
+ element.setAttribute("content", prefAuthorString);
+ headelement.appendChild(element);
+ }
+ }
+ }
+
+ // add title tag if not present
+ if (headelement && !editor.document.querySelector("title")) {
+ var titleElement = domdoc.createElement("title");
+ if (titleElement) {
+ headelement.appendChild(titleElement);
+ }
+ }
+
+ // find body node
+ var bodyelement = GetBodyElement();
+ if (bodyelement) {
+ if (Services.prefs.getBoolPref("editor.use_custom_colors")) {
+ let text_color = Services.prefs.getCharPref("editor.text_color");
+ let background_color = Services.prefs.getCharPref(
+ "editor.background_color"
+ );
+
+ // add the color attributes to the body tag.
+ // and use them for the default text and background colors if not empty
+ editor.setAttributeOrEquivalent(bodyelement, "text", text_color, true);
+ gDefaultTextColor = text_color;
+ editor.setAttributeOrEquivalent(
+ bodyelement,
+ "bgcolor",
+ background_color,
+ true
+ );
+ gDefaultBackgroundColor = background_color;
+ bodyelement.setAttribute(
+ "link",
+ Services.prefs.getCharPref("editor.link_color")
+ );
+ bodyelement.setAttribute(
+ "alink",
+ Services.prefs.getCharPref("editor.active_link_color")
+ );
+ bodyelement.setAttribute(
+ "vlink",
+ Services.prefs.getCharPref("editor.followed_link_color")
+ );
+ }
+ // Default image is independent of Custom colors???
+ try {
+ let background_image = Services.prefs.getCharPref(
+ "editor.default_background_image"
+ );
+ if (background_image) {
+ editor.setAttributeOrEquivalent(
+ bodyelement,
+ "background",
+ background_image,
+ true
+ );
+ }
+ } catch (e) {
+ dump("BACKGROUND EXCEPTION: " + e + "\n");
+ }
+ }
+ // auto-save???
+}
+
+function GetBodyElement() {
+ try {
+ return GetCurrentEditor().rootElement;
+ } catch (ex) {
+ dump("no body tag found?!\n");
+ // better have one, how can we blow things up here?
+ }
+ return null;
+}
+
+// --------------------------- Logging stuff ---------------------------
+
+function EditorGetNodeFromOffsets(offsets) {
+ var node = null;
+ try {
+ node = GetCurrentEditor().document;
+
+ for (var i = 0; i < offsets.length; i++) {
+ node = node.childNodes[offsets[i]];
+ }
+ } catch (e) {}
+ return node;
+}
+
+function EditorSetSelectionFromOffsets(selRanges) {
+ try {
+ var editor = GetCurrentEditor();
+ var selection = editor.selection;
+ selection.removeAllRanges();
+
+ var rangeArr, start, end, node, offset;
+ for (var i = 0; i < selRanges.length; i++) {
+ rangeArr = selRanges[i];
+ start = rangeArr[0];
+ end = rangeArr[1];
+
+ var range = editor.document.createRange();
+
+ node = EditorGetNodeFromOffsets(start[0]);
+ offset = start[1];
+
+ range.setStart(node, offset);
+
+ node = EditorGetNodeFromOffsets(end[0]);
+ offset = end[1];
+
+ range.setEnd(node, offset);
+
+ selection.addRange(range);
+ }
+ } catch (e) {}
+}
+
+// --------------------------------------------------------------------
+function initFontStyleMenu(menuPopup) {
+ for (var i = 0; i < menuPopup.childNodes.length; i++) {
+ var menuItem = menuPopup.childNodes[i];
+ var theStyle = menuItem.getAttribute("state");
+ if (theStyle) {
+ menuItem.setAttribute("checked", theStyle);
+ }
+ }
+}
+
+// --------------------------------------------------------------------
+function onButtonUpdate(button, commmandID) {
+ var commandNode = document.getElementById(commmandID);
+ var state = commandNode.getAttribute("state");
+ button.checked = state == "true";
+}
+
+// --------------------------------------------------------------------
+function onStateButtonUpdate(button, commmandID, onState) {
+ var commandNode = document.getElementById(commmandID);
+ var state = commandNode.getAttribute("state");
+
+ button.checked = state == onState;
+}
+
+// --------------------------- Status calls ---------------------------
+function getColorAndSetColorWell(ColorPickerID, ColorWellID) {
+ var colorWell;
+ if (ColorWellID) {
+ colorWell = document.getElementById(ColorWellID);
+ }
+
+ var colorPicker = document.getElementById(ColorPickerID);
+ if (colorPicker) {
+ // Extract color from colorPicker and assign to colorWell.
+ var color = colorPicker.getAttribute("color");
+
+ if (colorWell && color) {
+ // Use setAttribute so colorwell can be a XUL element, such as button
+ colorWell.setAttribute("style", "background-color: " + color);
+ }
+ }
+ return color;
+}
+
+// -----------------------------------------------------------------------------------
+function IsSpellCheckerInstalled() {
+ return true; // Always installed.
+}
+
+// -----------------------------------------------------------------------------------
+function IsFindInstalled() {
+ return (
+ "@mozilla.org/embedcomp/rangefind;1" in Cc &&
+ "@mozilla.org/find/find_service;1" in Cc
+ );
+}
+
+// -----------------------------------------------------------------------------------
+function RemoveInapplicableUIElements() {
+ // For items that are in their own menu block, remove associated separator
+ // (we can't use "hidden" since class="hide-in-IM" CSS rule interferes)
+
+ // if no find, remove find ui
+ if (!IsFindInstalled()) {
+ HideItem("menu_find");
+ HideItem("menu_findnext");
+ HideItem("menu_replace");
+ HideItem("menu_find");
+ RemoveItem("sep_find");
+ }
+
+ // if no spell checker, remove spell checker ui
+ if (!IsSpellCheckerInstalled()) {
+ HideItem("spellingButton");
+ HideItem("menu_checkspelling");
+ RemoveItem("sep_checkspelling");
+ }
+
+ // Remove menu items (from overlay shared with HTML editor) in non-HTML.
+ if (!IsHTMLEditor()) {
+ HideItem("insertAnchor");
+ HideItem("insertImage");
+ HideItem("insertHline");
+ HideItem("insertTable");
+ HideItem("insertHTML");
+ HideItem("insertFormMenu");
+ HideItem("fileExportToText");
+ HideItem("viewEditModeToolbar");
+ }
+}
+
+function HideItem(id) {
+ var item = document.getElementById(id);
+ if (item) {
+ item.hidden = true;
+ }
+}
+
+function RemoveItem(id) {
+ var item = document.getElementById(id);
+ if (item) {
+ item.remove();
+ }
+}
+
+// Command Updating Strategy:
+// Don't update on on selection change, only when menu is displayed,
+// with this "oncreate" handler:
+function EditorInitTableMenu() {
+ try {
+ InitJoinCellMenuitem("menu_JoinTableCells");
+ } catch (ex) {}
+
+ // Set enable states for all table commands
+ goUpdateTableMenuItems(document.getElementById("composerTableMenuItems"));
+}
+
+function InitJoinCellMenuitem(id) {
+ // Change text on the "Join..." item depending if we
+ // are joining selected cells or just cell to right
+ // TODO: What to do about normal selection that crosses
+ // table border? Try to figure out all cells
+ // included in the selection?
+ var menuText;
+ var menuItem = document.getElementById(id);
+ if (!menuItem) {
+ return;
+ }
+
+ // Use "Join selected cells if there's more than 1 cell selected
+ var numSelected;
+ var foundElement;
+
+ try {
+ var tagNameObj = {};
+ var countObj = { value: 0 };
+ foundElement = GetCurrentTableEditor().getSelectedOrParentTableElement(
+ tagNameObj,
+ countObj
+ );
+ numSelected = countObj.value;
+ } catch (e) {}
+ if (foundElement && numSelected > 1) {
+ menuText = GetString("JoinSelectedCells");
+ } else {
+ menuText = GetString("JoinCellToRight");
+ }
+
+ menuItem.setAttribute("label", menuText);
+ menuItem.setAttribute("accesskey", GetString("JoinCellAccesskey"));
+}
+
+function InitRemoveStylesMenuitems(
+ removeStylesId,
+ removeLinksId,
+ removeNamedAnchorsId
+) {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ return;
+ }
+
+ // Change wording of menuitems depending on selection
+ var stylesItem = document.getElementById(removeStylesId);
+ var linkItem = document.getElementById(removeLinksId);
+
+ var isCollapsed = editor.selection.isCollapsed;
+ if (stylesItem) {
+ stylesItem.setAttribute(
+ "label",
+ isCollapsed ? GetString("StopTextStyles") : GetString("RemoveTextStyles")
+ );
+ stylesItem.setAttribute(
+ "accesskey",
+ GetString("RemoveTextStylesAccesskey")
+ );
+ }
+ if (linkItem) {
+ linkItem.setAttribute(
+ "label",
+ isCollapsed ? GetString("StopLinks") : GetString("RemoveLinks")
+ );
+ linkItem.setAttribute("accesskey", GetString("RemoveLinksAccesskey"));
+ // Note: disabling text style is a pain since there are so many - forget it!
+
+ // Disable if not in a link, but always allow "Remove"
+ // if selection isn't collapsed since we only look at anchor node
+ try {
+ SetElementEnabled(
+ linkItem,
+ !isCollapsed || editor.getElementOrParentByTagName("href", null)
+ );
+ } catch (e) {}
+ }
+ // Disable if selection is collapsed
+ SetElementEnabledById(removeNamedAnchorsId, !isCollapsed);
+}
+
+function goUpdateTableMenuItems(commandset) {
+ var editor = GetCurrentTableEditor();
+ if (!editor) {
+ dump("goUpdateTableMenuItems: too early, not initialized\n");
+ return;
+ }
+
+ var enabled = false;
+ var enabledIfTable = false;
+
+ var flags = editor.flags;
+ if (!(flags & Ci.nsIEditor.eEditorReadonlyMask) && IsEditingRenderedHTML()) {
+ var tagNameObj = { value: "" };
+ var element;
+ try {
+ element = editor.getSelectedOrParentTableElement(tagNameObj, {
+ value: 0,
+ });
+ } catch (e) {}
+
+ if (element) {
+ // Value when we need to have a selected table or inside a table
+ enabledIfTable = true;
+
+ // All others require being inside a cell or selected cell
+ enabled = tagNameObj.value == "td";
+ }
+ }
+
+ // Loop through command nodes
+ for (var i = 0; i < commandset.childNodes.length; i++) {
+ var commandID = commandset.childNodes[i].getAttribute("id");
+ if (commandID) {
+ if (
+ commandID == "cmd_InsertTable" ||
+ commandID == "cmd_JoinTableCells" ||
+ commandID == "cmd_SplitTableCell" ||
+ commandID == "cmd_ConvertToTable"
+ ) {
+ // Call the update method in the command class
+ goUpdateCommand(commandID);
+ } else if (
+ commandID == "cmd_DeleteTable" ||
+ commandID == "cmd_NormalizeTable" ||
+ commandID == "cmd_editTable" ||
+ commandID == "cmd_TableOrCellColor" ||
+ commandID == "cmd_SelectTable"
+ ) {
+ // Directly set with the values calculated here
+ goSetCommandEnabled(commandID, enabledIfTable);
+ } else {
+ goSetCommandEnabled(commandID, enabled);
+ }
+ }
+ }
+}
+
+// -----------------------------------------------------------------------------------
+// Helpers for inserting and editing tables:
+
+function IsInTable() {
+ var editor = GetCurrentEditor();
+ try {
+ var flags = editor.flags;
+ return (
+ IsHTMLEditor() &&
+ !(flags & Ci.nsIEditor.eEditorReadonlyMask) &&
+ IsEditingRenderedHTML() &&
+ null != editor.getElementOrParentByTagName("table", null)
+ );
+ } catch (e) {}
+ return false;
+}
+
+function IsInTableCell() {
+ try {
+ var editor = GetCurrentEditor();
+ var flags = editor.flags;
+ return (
+ IsHTMLEditor() &&
+ !(flags & Ci.nsIEditor.eEditorReadonlyMask) &&
+ IsEditingRenderedHTML() &&
+ null != editor.getElementOrParentByTagName("td", null)
+ );
+ } catch (e) {}
+ return false;
+}
+
+function IsSelectionInOneCell() {
+ try {
+ var editor = GetCurrentEditor();
+ var selection = editor.selection;
+
+ if (selection.rangeCount == 1) {
+ // We have a "normal" single-range selection
+ if (
+ !selection.isCollapsed &&
+ selection.anchorNode != selection.focusNode
+ ) {
+ // Check if both nodes are within the same cell
+ var anchorCell = editor.getElementOrParentByTagName(
+ "td",
+ selection.anchorNode
+ );
+ var focusCell = editor.getElementOrParentByTagName(
+ "td",
+ selection.focusNode
+ );
+ return (
+ focusCell != null && anchorCell != null && focusCell == anchorCell
+ );
+ }
+ // Collapsed selection or anchor == focus (thus must be in 1 cell)
+ return true;
+ }
+ } catch (e) {}
+ return false;
+}
+
+// Call this with insertAllowed = true to allow inserting if not in existing table,
+// else use false to do nothing if not in a table
+function EditorInsertOrEditTable(insertAllowed) {
+ if (IsInTable()) {
+ // Edit properties of existing table
+ window.openDialog(
+ "chrome://editor/content/EdTableProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ "",
+ "TablePanel"
+ );
+ gContentWindow.focus();
+ } else if (insertAllowed) {
+ try {
+ if (GetCurrentEditor().selection.isCollapsed) {
+ // If we have a caret, insert a blank table...
+ EditorInsertTable();
+ } else {
+ // Else convert the selection into a table.
+ goDoCommand("cmd_ConvertToTable");
+ }
+ } catch (e) {}
+ }
+}
+
+function EditorInsertTable() {
+ // Insert a new table
+ window.openDialog(
+ "chrome://editor/content/EdInsertTable.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ ""
+ );
+ gContentWindow.focus();
+}
+
+function EditorTableCellProperties() {
+ if (!IsHTMLEditor()) {
+ return;
+ }
+
+ try {
+ var cell = GetCurrentEditor().getElementOrParentByTagName("td", null);
+ if (cell) {
+ // Start Table Properties dialog on the "Cell" panel
+ window.openDialog(
+ "chrome://editor/content/EdTableProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ "",
+ "CellPanel"
+ );
+ gContentWindow.focus();
+ }
+ } catch (e) {}
+}
+
+function GetNumberOfContiguousSelectedRows() {
+ if (!IsHTMLEditor()) {
+ return 0;
+ }
+
+ var rows = 0;
+ try {
+ var editor = GetCurrentTableEditor();
+ var rowObj = { value: 0 };
+ var colObj = { value: 0 };
+ var cell = editor.getFirstSelectedCellInTable(rowObj, colObj);
+ if (!cell) {
+ return 0;
+ }
+
+ // We have at least one row
+ rows++;
+
+ var lastIndex = rowObj.value;
+ do {
+ cell = editor.getNextSelectedCell({ value: 0 });
+ if (cell) {
+ editor.getCellIndexes(cell, rowObj, colObj);
+ var index = rowObj.value;
+ if (index == lastIndex + 1) {
+ lastIndex = index;
+ rows++;
+ }
+ }
+ } while (cell);
+ } catch (e) {}
+
+ return rows;
+}
+
+function GetNumberOfContiguousSelectedColumns() {
+ if (!IsHTMLEditor()) {
+ return 0;
+ }
+
+ var columns = 0;
+ try {
+ var editor = GetCurrentTableEditor();
+ var colObj = { value: 0 };
+ var rowObj = { value: 0 };
+ var cell = editor.getFirstSelectedCellInTable(rowObj, colObj);
+ if (!cell) {
+ return 0;
+ }
+
+ // We have at least one column
+ columns++;
+
+ var lastIndex = colObj.value;
+ do {
+ cell = editor.getNextSelectedCell({ value: 0 });
+ if (cell) {
+ editor.getCellIndexes(cell, rowObj, colObj);
+ var index = colObj.value;
+ if (index == lastIndex + 1) {
+ lastIndex = index;
+ columns++;
+ }
+ }
+ } while (cell);
+ } catch (e) {}
+
+ return columns;
+}
+
+function EditorOnFocus() {
+ // Current window already has the InsertCharWindow
+ if ("InsertCharWindow" in window && window.InsertCharWindow) {
+ return;
+ }
+
+ // Find window with an InsertCharsWindow and switch association to this one
+ var windowWithDialog = FindEditorWithInsertCharDialog();
+ if (windowWithDialog) {
+ // Switch the dialog to current window
+ // this sets focus to dialog, so bring focus back to editor window
+ if (SwitchInsertCharToThisWindow(windowWithDialog)) {
+ top.document.commandDispatcher.focusedWindow.focus();
+ }
+ }
+}
+
+function SwitchInsertCharToThisWindow(windowWithDialog) {
+ if (
+ windowWithDialog &&
+ "InsertCharWindow" in windowWithDialog &&
+ windowWithDialog.InsertCharWindow
+ ) {
+ // Move dialog association to the current window
+ window.InsertCharWindow = windowWithDialog.InsertCharWindow;
+ windowWithDialog.InsertCharWindow = null;
+
+ // Switch the dialog's opener to current window's
+ window.InsertCharWindow.opener = window;
+
+ // Bring dialog to the foreground
+ window.InsertCharWindow.focus();
+ return true;
+ }
+ return false;
+}
+
+function FindEditorWithInsertCharDialog() {
+ try {
+ // Find window with an InsertCharsWindow and switch association to this one
+ let enumerator = Services.wm.getEnumerator(null);
+
+ while (enumerator.hasMoreElements()) {
+ var tempWindow = enumerator.getNext();
+
+ if (
+ !tempWindow.closed &&
+ tempWindow != window &&
+ "InsertCharWindow" in tempWindow &&
+ tempWindow.InsertCharWindow
+ ) {
+ return tempWindow;
+ }
+ }
+ } catch (e) {}
+ return null;
+}
+
+function EditorFindOrCreateInsertCharWindow() {
+ if ("InsertCharWindow" in window && window.InsertCharWindow) {
+ window.InsertCharWindow.focus();
+ } else {
+ // Since we switch the dialog during EditorOnFocus(),
+ // this should really never be found, but it's good to be sure
+ var windowWithDialog = FindEditorWithInsertCharDialog();
+ if (windowWithDialog) {
+ SwitchInsertCharToThisWindow(windowWithDialog);
+ } else {
+ // The dialog will set window.InsertCharWindow to itself
+ window.openDialog(
+ "chrome://editor/content/EdInsertChars.xhtml",
+ "_blank",
+ "chrome,close,titlebar",
+ ""
+ );
+ }
+ }
+}
+
+// Find another HTML editor window to associate with the InsertChar dialog
+// or close it if none found (May be a mail composer)
+function SwitchInsertCharToAnotherEditorOrClose() {
+ if ("InsertCharWindow" in window && window.InsertCharWindow) {
+ var enumerator;
+ try {
+ enumerator = Services.wm.getEnumerator(null);
+ } catch (e) {}
+ if (!enumerator) {
+ return;
+ }
+
+ // TODO: Fix this to search for command controllers and look for "cmd_InsertChars"
+ // For now, detect just Web Composer and HTML Mail Composer
+ while (enumerator.hasMoreElements()) {
+ var tempWindow = enumerator.getNext();
+ if (
+ !tempWindow.closed &&
+ tempWindow != window &&
+ tempWindow != window.InsertCharWindow &&
+ "GetCurrentEditor" in tempWindow &&
+ tempWindow.GetCurrentEditor()
+ ) {
+ tempWindow.InsertCharWindow = window.InsertCharWindow;
+ window.InsertCharWindow = null;
+ tempWindow.InsertCharWindow.opener = tempWindow;
+ return;
+ }
+ }
+ // Didn't find another editor - close the dialog
+ window.InsertCharWindow.close();
+ }
+}
+
+function ResetStructToolbar() {
+ gLastFocusNode = null;
+ UpdateStructToolbar();
+}
+
+function newCommandListener(element) {
+ return function() {
+ return SelectFocusNodeAncestor(element);
+ };
+}
+
+function newContextmenuListener(button, element) {
+ /* globals InitStructBarContextMenu */ // SeaMonkey only.
+ return function() {
+ return InitStructBarContextMenu(button, element);
+ };
+}
+
+function UpdateStructToolbar() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ return;
+ }
+
+ var mixed = GetSelectionContainer();
+ if (!mixed) {
+ return;
+ }
+ var element = mixed.node;
+ var oneElementSelected = mixed.oneElementSelected;
+
+ if (!element) {
+ return;
+ }
+
+ if (
+ element == gLastFocusNode &&
+ oneElementSelected == gLastFocusNodeWasSelected
+ ) {
+ return;
+ }
+
+ gLastFocusNode = element;
+ gLastFocusNodeWasSelected = mixed.oneElementSelected;
+
+ var toolbar = document.getElementById("structToolbar");
+ if (!toolbar) {
+ return;
+ }
+ // We need to leave the <label> to flex the buttons to the left.
+ for (let node of toolbar.querySelectorAll("toolbarbutton,textbox")) {
+ node.remove();
+ }
+
+ toolbar.removeAttribute("label");
+
+ if (IsInHTMLSourceMode()) {
+ // we have destroyed the contents of the status bar and are
+ // about to recreate it ; but we don't want to do that in
+ // Source mode
+ return;
+ }
+
+ var tag, button;
+ var bodyElement = GetBodyElement();
+ var isFocusNode = true;
+ var tmp;
+ do {
+ tag = element.nodeName.toLowerCase();
+
+ button = document.createXULElement("toolbarbutton");
+ button.setAttribute("label", "<" + tag + ">");
+ button.setAttribute("value", tag);
+ button.setAttribute("context", "structToolbarContext");
+ button.className = "struct-button";
+
+ toolbar.insertBefore(button, toolbar.firstChild);
+
+ button.addEventListener("command", newCommandListener(element));
+
+ button.addEventListener(
+ "contextmenu",
+ newContextmenuListener(button, element)
+ );
+
+ if (isFocusNode && oneElementSelected) {
+ button.setAttribute("checked", "true");
+ isFocusNode = false;
+ }
+
+ tmp = element;
+ element = element.parentNode;
+ } while (element && tmp != bodyElement);
+}
+
+function SelectFocusNodeAncestor(element) {
+ var editor = GetCurrentEditor();
+ if (editor) {
+ if (element == GetBodyElement()) {
+ editor.selectAll();
+ } else {
+ editor.selectElement(element);
+ }
+ }
+ ResetStructToolbar();
+}
+
+function GetSelectionContainer() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ return null;
+ }
+
+ var selection;
+ try {
+ selection = editor.selection;
+ if (!selection) {
+ return null;
+ }
+ } catch (e) {
+ return null;
+ }
+
+ var result = { oneElementSelected: false };
+
+ if (selection.isCollapsed) {
+ result.node = selection.focusNode;
+ } else {
+ var rangeCount = selection.rangeCount;
+ if (rangeCount == 1) {
+ result.node = editor.getSelectedElement("");
+ var range = selection.getRangeAt(0);
+
+ // check for a weird case : when we select a piece of text inside
+ // a text node and apply an inline style to it, the selection starts
+ // at the end of the text node preceding the style and ends after the
+ // last char of the style. Assume the style element is selected for
+ // user's pleasure
+ if (
+ !result.node &&
+ range.startContainer.nodeType == Node.TEXT_NODE &&
+ range.startOffset == range.startContainer.length &&
+ range.endContainer.nodeType == Node.TEXT_NODE &&
+ range.endOffset == range.endContainer.length &&
+ range.endContainer.nextSibling == null &&
+ range.startContainer.nextSibling == range.endContainer.parentNode
+ ) {
+ result.node = range.endContainer.parentNode;
+ }
+
+ if (!result.node) {
+ // let's rely on the common ancestor of the selection
+ result.node = range.commonAncestorContainer;
+ } else {
+ result.oneElementSelected = true;
+ }
+ } else {
+ // assume table cells !
+ var i,
+ container = null;
+ for (i = 0; i < rangeCount; i++) {
+ range = selection.getRangeAt(i);
+ if (!container) {
+ container = range.startContainer;
+ } else if (container != range.startContainer) {
+ // all table cells don't belong to same row so let's
+ // select the parent of all rows
+ result.node = container.parentNode;
+ break;
+ }
+ result.node = container;
+ }
+ }
+ }
+
+ // make sure we have an element here
+ while (result.node.nodeType != Node.ELEMENT_NODE) {
+ result.node = result.node.parentNode;
+ }
+
+ // and make sure the element is not a special editor node like
+ // the <br> we insert in blank lines
+ // and don't select anonymous content !!! (fix for bug 190279)
+ while (
+ result.node.hasAttribute("_moz_editor_bogus_node") ||
+ editor.isAnonymousElement(result.node)
+ ) {
+ result.node = result.node.parentNode;
+ }
+
+ return result;
+}
+
+function FillInHTMLTooltipEditor(tooltip) {
+ const XLinkNS = "http://www.w3.org/1999/xlink";
+ var tooltipText = null;
+ var node;
+ if (IsInPreviewMode()) {
+ for (node = document.tooltipNode; node; node = node.parentNode) {
+ if (node.nodeType == Node.ELEMENT_NODE) {
+ tooltipText = node.getAttributeNS(XLinkNS, "title");
+ if (tooltipText && /\S/.test(tooltipText)) {
+ tooltip.setAttribute("label", tooltipText);
+ return true;
+ }
+ tooltipText = node.getAttribute("title");
+ if (tooltipText && /\S/.test(tooltipText)) {
+ tooltip.setAttribute("label", tooltipText);
+ return true;
+ }
+ }
+ }
+ } else {
+ for (node = document.tooltipNode; node; node = node.parentNode) {
+ if (
+ ChromeUtils.getClassName(node) === "HTMLImageElement" ||
+ ChromeUtils.getClassName(node) === "HTMLInputElement"
+ ) {
+ tooltipText = node.getAttribute("src");
+ } else if (ChromeUtils.getClassName(node) === "HTMLAnchorElement") {
+ tooltipText = node.getAttribute("href") || node.getAttribute("name");
+ }
+ if (tooltipText) {
+ tooltip.setAttribute("label", tooltipText);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+function UpdateTOC() {
+ window.openDialog(
+ "chrome://editor/content/EdInsertTOC.xhtml",
+ "_blank",
+ "chrome,close,modal,titlebar"
+ );
+ window.content.focus();
+}
+
+function InitTOCMenu() {
+ var elt = GetCurrentEditor().document.getElementById("mozToc");
+ var createMenuitem = document.getElementById("insertTOCMenuitem");
+ var updateMenuitem = document.getElementById("updateTOCMenuitem");
+ var removeMenuitem = document.getElementById("removeTOCMenuitem");
+ if (removeMenuitem && createMenuitem && updateMenuitem) {
+ if (elt) {
+ createMenuitem.setAttribute("disabled", "true");
+ updateMenuitem.removeAttribute("disabled");
+ removeMenuitem.removeAttribute("disabled");
+ } else {
+ createMenuitem.removeAttribute("disabled");
+ removeMenuitem.setAttribute("disabled", "true");
+ updateMenuitem.setAttribute("disabled", "true");
+ }
+ }
+}
+
+function RemoveTOC() {
+ var theDocument = GetCurrentEditor().document;
+ var elt = theDocument.getElementById("mozToc");
+ if (elt) {
+ elt.remove();
+ }
+
+ let anchorNodes = theDocument.querySelectorAll('a[name^="mozTocId"]');
+ for (let node of anchorNodes) {
+ if (node.parentNode) {
+ node.remove();
+ }
+ }
+}
diff --git a/comm/suite/editor/base/content/editor.xhtml b/comm/suite/editor/base/content/editor.xhtml
new file mode 100644
index 0000000000..033185592f
--- /dev/null
+++ b/comm/suite/editor/base/content/editor.xhtml
@@ -0,0 +1,402 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/" type="text/css"?>
+
+<?xml-stylesheet href="chrome://editor/skin/editorPrimaryToolbar.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/editorFormatToolbar.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/editorModeToolbar.css" type="text/css"?>
+<?xul-overlay href="chrome://editor/content/editorOverlay.xhtml"?>
+<?xul-overlay href="chrome://editor/content/editingOverlay.xhtml"?>
+<?xul-overlay href="chrome://editor/content/composerOverlay.xhtml"?>
+<?xul-overlay href="chrome://communicator/content/contentAreaContextOverlay.xhtml"?>
+<?xul-overlay href="chrome://editor/content/EditorContextMenuOverlay.xhtml"?>
+<?xul-overlay href="chrome://communicator/content/charsetOverlay.xhtml"?>
+<?xul-overlay href="chrome://communicator/content/utilityOverlay.xhtml"?>
+<?xul-overlay href="chrome://communicator/content/tasksOverlay.xhtml"?>
+<?xul-overlay href="chrome://communicator/content/sidebar/sidebarOverlay.xhtml"?>
+
+<!DOCTYPE window [
+<!ENTITY % editorDTD SYSTEM "chrome://editor/locale/editor.dtd" >
+%editorDTD;
+<!ENTITY % editorOverlayDTD SYSTEM "chrome://editor/locale/editorOverlay.dtd" >
+%editorOverlayDTD;
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+]>
+
+<window id="editorWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="EditorOnLoad()"
+ onunload="EditorShutdown()"
+ onclose="return Async.promiseSpinningly(EditorCanClose())"
+ onfocus="EditorOnFocus()"
+ title="&editorWindow.titlemodifier;"
+ titlemodifier="&editorWindow.titlemodifier;"
+ titlemenuseparator="&editorWindow.titlemodifiermenuseparator;"
+ toggletoolbar="true"
+ lightweightthemes="true"
+ lightweightthemesfooter="status-bar"
+ windowtype="composer:html"
+ macanimationtype="document"
+ drawtitle="true"
+ width="640" height="480"
+ screenX="10" screenY="10"
+ persist="screenX screenY width height sizemode">
+
+ <script src="chrome://editor/content/editor.js"/>
+ <script src="chrome://editor/content/publishprefs.js"/>
+ <script src="chrome://communicator/content/contentAreaClick.js"/>
+ <script src="chrome://global/content/printUtils.js"/>
+ <script src="chrome://global/content/nsDragAndDrop.js"/>
+
+ <popupset id="contentAreaContextSet"/>
+ <popupset id="editorPopupSet">
+ <menupopup id="structToolbarContext"/>
+ <menupopup id="sidebarPopup"/>
+ </popupset>
+
+ <commandset id="editorCommands">
+ <commandset id="commonEditorMenuItems"/>
+ <commandset id="composerMenuItems"/>
+ <commandset id="composerOnlyMenuItems"
+ commandupdater="true"
+ events="create, mode_switch"
+ oncommandupdate="goUpdateComposerMenuItems(this);">
+ <!-- file menu -->
+ <command id="cmd_exportToText"
+ label="&exportToTextCmd.label;"
+ accesskey="&exportToTextCmd.accesskey;"
+ oncommand="goDoCommand('cmd_exportToText');"/>
+ <command id="cmd_preview"
+ oncommand="goDoCommand('cmd_preview');"/>
+ <command id="cmd_editSendPage"
+ label="&sendPageCmd.label;"
+ accesskey="&sendPageCmd.accesskey;"
+ oncommand="goDoCommand('cmd_editSendPage');"/>
+ <!-- format menu -->
+ <command id="cmd_pageProperties"
+ oncommand="goDoCommand('cmd_pageProperties');"/>
+ <!-- tools menu -->
+ <command id="cmd_validate"
+ oncommand="goDoCommand('cmd_validate');"/>
+ <!-- toolbars -->
+ <command id="cmd_NormalMode"
+ oncommand="goDoCommand('cmd_NormalMode');"/>
+ <command id="cmd_AllTagsMode"
+ oncommand="goDoCommand('cmd_AllTagsMode');"/>
+ <command id="cmd_HTMLSourceMode"
+ oncommand="goDoCommand('cmd_HTMLSourceMode');"/>
+ <command id="cmd_PreviewMode"
+ oncommand="goDoCommand('cmd_PreviewMode');"/>
+ </commandset>
+ <commandset id="composerEditMenuItems"/>
+ <commandset id="composerSaveMenuItems"/>
+ <commandset id="composerStyleMenuItems">
+ <command id="cmd_updateStructToolbar"
+ oncommand="goDoCommand('cmd_updateStructToolbar');"/>
+ </commandset>
+ <commandset id="composerTableMenuItems"/>
+ <commandset id="composerListMenuItems"/>
+ <commandset id="tasksCommands"/>
+ <!-- view menu -->
+ <command id="cmd_viewEditModeToolbar"
+ oncommand="goToggleToolbar('EditModeToolbar','cmd_viewEditModeToolbar');"
+ checked="true"/>
+ </commandset>
+
+ <tooltip id="aHTMLTooltip" onpopupshowing="return FillInHTMLTooltipEditor(this);"/>
+
+ <!-- keys are appended from the overlay -->
+ <keyset id="editorKeys">
+ <keyset id="tasksKeys"/>
+ <key id="showHideSidebar"/>
+ <!-- eat these tab events here to stop focus from moving -->
+ <key keycode="VK_TAB" oncommand="return true;"/>
+ <key keycode="VK_TAB" modifiers="shift" oncommand="return true;"/>
+ <key keycode="VK_TAB" modifiers="control" oncommand="return true;"/>
+ <key keycode="VK_TAB" modifiers="control,shift" oncommand="return true;"/>
+ </keyset>
+
+ <vbox id="titlebar"/>
+
+<toolbox id="EditorToolbox"
+ class="toolbox-top"
+ mode="full"
+ defaultmode="full">
+ <toolbar id="toolbar-menubar"
+ type="menubar"
+ class="chromeclass-menubar"
+ persist="collapsed"
+ grippytooltiptext="&menuBar.tooltip;"
+ customizable="true"
+ defaultset="menubar-items"
+ mode="icons"
+ iconsize="small"
+ defaultmode="icons"
+ defaulticonsize="small"
+ context="toolbar-context-menu">
+ <toolbaritem id="menubar-items"
+ class="menubar-items"
+ align="center">
+ <menubar id="main-menubar">
+ <menu id="menu_File"/>
+ <menu id="menu_Edit"/>
+
+ <menu id="menu_View">
+ <!-- id pulls in "Show Sidebar" item from sidebarOverlay -->
+ <menupopup id="menu_View_Popup">
+ <menu id="menu_Toolbars">
+ <menupopup id="view_toolbars_popup"
+ onpopupshowing="onViewToolbarsPopupShowing(event);"
+ oncommand="onViewToolbarCommand(event);">
+ <menuitem id="viewEditModeToolbar"
+ label="&editmodeToolbarCmd.label;"
+ accesskey="&editmodeToolbarCmd.accesskey;"
+ type="checkbox"
+ command="cmd_viewEditModeToolbar"/>
+ <menuitem id="menu_showTaskbar"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="viewSep1"/>
+ <menuitem id="viewNormalMode"
+ type="radio"
+ group="mode"
+ checked="true"
+ label="&NormalMode.label;"
+ accesskey="&NormalMode.accesskey;"
+ command="cmd_NormalMode"/>
+ <menuitem id="viewAllTagsMode"
+ type="radio"
+ group="mode"
+ label="&AllTagsMode.label;"
+ accesskey="&AllTagsMode.accesskey;"
+ command="cmd_AllTagsMode"/>
+ <menuitem id="viewSourceMode"
+ type="radio"
+ group="mode"
+ label="&HTMLSourceMode.label;"
+ accesskey="&HTMLSourceMode.accesskey;"
+ command="cmd_HTMLSourceMode"/>
+ <menuitem id="viewPreviewMode"
+ type="radio"
+ group="mode"
+ label="&PreviewMode.label;"
+ accesskey="&PreviewMode.accesskey;"
+ command="cmd_PreviewMode"/>
+ <menuseparator id="viewSep2"/>
+ <menu id="charsetMenu"
+ onpopupshowing="EditorUpdateCharsetMenu(event.target);"
+ oncommand="EditorSetCharacterSet(event);"/>
+ </menupopup>
+ </menu>
+
+ <menu id="insertMenu"/>
+
+ <menu id="formatMenu"
+ label="&formatMenu.label;"
+ accesskey="&formatMenu.accesskey;">
+ <menupopup id="formatMenuPopup">
+ <menuitem id="snapToGrid"
+ label="&grid.label;"
+ accesskey="&grid.accesskey;"
+ oncommand="goDoCommand('cmd_grid');"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuseparator/>
+ <menuitem id="objectProperties"/>
+ <menuitem id="colorsAndBackground"/>
+ <!-- Don't use 'observes', must call command correctly -->
+ <menuitem id="pageProperties"
+ label="&pageProperties.label;"
+ accesskey="&pageProperties.accesskey;"
+ oncommand="goDoCommand('cmd_pageProperties');"
+ observes="cmd_renderedHTMLEnabler"/>
+ </menupopup>
+ </menu>
+
+ <menu id="tableMenu"/>
+
+ <!-- tasks menu filled from tasksOverlay -->
+ <menu id="tasksMenu">
+ <menupopup id="taskPopup">
+ <menuitem id="menu_validate"
+ label="&validateCmd.label;"
+ accesskey="&validateCmd.accesskey;"
+ command="cmd_validate"/>
+ <menuseparator id="sep_validate"/>
+ </menupopup>
+ </menu>
+
+ <menu id="windowMenu"/>
+
+ <!-- help menu filled from globalOverlay -->
+ <menu id="menu_Help"/>
+ </menubar>
+ </toolbaritem>
+ </toolbar>
+
+ <!-- toolbar mostly filled out from editorOverlay -->
+ <!-- add class="standard" for dark blue background, icons need rework first -->
+ <toolbar id="EditToolbar"
+ class="chromeclass-toolbar toolbar-primary"
+ persist="collapsed"
+ grippytooltiptext="&compositionToolbar.tooltip;"
+ toolbarname="&compositionToolbarCmd.label;"
+ accesskey="&compositionToolbarCmd.accesskey;"
+ customizable="true"
+ defaultset="newButton,openButton,saveButton,publishButton,previewButton,print-button,separator,linkButton,imageButton,tableButton,spellingButton,spring,throbber-box"
+ context="toolbar-context-menu">
+ <toolbarbutton id="newButton"/>
+ <toolbarbutton id="openButton"/>
+ <toolbarbutton id="saveButton"/>
+ <toolbarbutton id="publishButton"/>
+ <toolbarbutton id="previewButton"
+ class="toolbarbutton-1"
+ label="&previewToolbarCmd.label;"
+ removable="true"
+ command="cmd_preview"
+ tooltiptext="&previewToolbarCmd.tooltip;"/>
+ <toolbarbutton id="cutButton"/>
+ <toolbarbutton id="copyButton"/>
+ <toolbarbutton id="pasteButton"/>
+ <toolbarbutton id="print-button"/>
+ <toolbarbutton id="findButton"/>
+ <toolbarbutton id="linkButton"/>
+ <toolbarbutton id="namedAnchorButton"/>
+ <toolbarbutton id="imageButton"/>
+ <toolbarbutton id="formButton"/>
+ <toolbarbutton id="hlineButton"/>
+ <toolbarbutton id="tableButton"/>
+ <toolbarbutton id="spellingButton"/>
+ <toolbaritem id="throbber-box"/>
+ </toolbar>
+
+ <toolbarset id="customToolbars" context="toolbar-context-menu"/>
+
+ <toolbarpalette id="EditToolbarPalette"/>
+
+ <toolbar id="FormatToolbar"
+ class="chromeclass-toolbar"
+ persist="collapsed"
+ grippytooltiptext="&formatToolbar.tooltip;"
+ toolbarname="&formattingToolbarCmd.label;"
+ accesskey="&formattingToolbarCmd.accesskey;"
+ customizable="true"
+ defaultset="paragraph-select-container,color-buttons-container,HighlightColorButton,separator,DecreaseFontSizeButton,IncreaseFontSizeButton,separator,boldButton,italicButton,underlineButton,separator,ulButton,olButton,outdentButton,indentButton,separator,align-left-button,align-center-button,align-right-button,align-justify-button,absolutePositionButton,decreaseZIndexButton,increaseZIndexButton"
+ mode="icons"
+ iconsize="small"
+ defaultmode="icons"
+ defaulticonsize="small"
+ context="toolbar-context-menu"
+ nowindowdrag="true">
+ <!-- from editorOverlay -->
+ <toolbaritem id="paragraph-select-container"/>
+ <toolbaritem id="color-buttons-container"
+ disableoncustomize="true"/>
+ <toolbarbutton id="HighlightColorButton"/>
+ <!-- Enable if required for SeaMonkey.
+ <toolbarbutton id="AbsoluteFontSizeButton"/>
+ -->
+ <toolbarbutton id="DecreaseFontSizeButton"/>
+ <toolbarbutton id="IncreaseFontSizeButton"/>
+ <toolbarbutton id="boldButton"/>
+ <toolbarbutton id="italicButton"/>
+ <toolbarbutton id="underlineButton"/>
+ <toolbarbutton id="ulButton"/>
+ <toolbarbutton id="olButton"/>
+ <toolbarbutton id="outdentButton"/>
+ <toolbarbutton id="indentButton"/>
+ <toolbarbutton id="align-left-button"/>
+ <toolbarbutton id="align-center-button"/>
+ <toolbarbutton id="align-right-button"/>
+ <toolbarbutton id="align-justify-button"/>
+ <toolbarbutton id="absolutePositionButton"/>
+ <toolbarbutton id="decreaseZIndexButton"/>
+ <toolbarbutton id="increaseZIndexButton"/>
+
+ <!-- TODO: Change to a menulist? -->
+ <!-- menu>
+ <button id="AlignPopupButton"/>
+ <menupopup id="AlignmentPopup"/>
+ </menu -->
+
+
+ <spacer flex="1"/>
+ </toolbar>
+</toolbox>
+
+<!-- sidebar/toolbar/content/status -->
+<hbox id="sidebar-parent" flex="1">
+ <!-- From sidebarOverlay.xhtml -->
+ <vbox id="sidebar-box" class="chromeclass-extrachrome" hidden="true"/>
+ <splitter id="sidebar-splitter" class="chromeclass-extrachrome" hidden="true"/>
+
+ <vbox id="appcontent" flex="1">
+ <deck id="ContentWindowDeck" selectedIndex="0" flex="1">
+ <vbox>
+ <findbar id="FindToolbar" browserid="content-frame"/>
+ <editor editortype="html"
+ type="content"
+ primary="true"
+ id="content-frame"
+ onclick="EditorClick(event);"
+ ondblclick="EditorDblClick(event);"
+ context="contentAreaContextMenu"
+ flex="1"
+ tooltip="aHTMLTooltip"/>
+ </vbox>
+ <vbox>
+ <label id="doctype-text" crop="right"/>
+ <editor type="content"
+ id="content-source"
+ context="contentAreaContextMenu"
+ flex="1"/>
+ </vbox>
+ </deck>
+
+ <!-- Edit Mode toolbar -->
+ <tabbox id="EditModeToolbar"
+ persist="collapsed">
+ <tabs id="EditModeTabs"
+ class="tabs-bottom"
+ flex="1"
+ onselect="this.selectedItem.doCommand();">
+ <tab id="NormalModeButton"
+ class="tab-bottom edit-mode"
+ label="&NormalModeTab.label;"
+ tooltiptext="&NormalMode.tooltip;"
+ command="cmd_NormalMode"/>
+ <tab id="TagModeButton"
+ class="tab-bottom edit-mode"
+ label="&AllTagsModeTab.label;"
+ tooltiptext="&AllTagsMode.tooltip;"
+ command="cmd_AllTagsMode"/>
+ <tab id="SourceModeButton"
+ class="tab-bottom edit-mode"
+ label="&HTMLSourceModeTab.label;"
+ tooltiptext="&HTMLSourceMode.tooltip;"
+ dir="&HTMLSourceModeTab.dir;"
+ command="cmd_HTMLSourceMode"/>
+ <tab id="PreviewModeButton"
+ class="tab-bottom edit-mode"
+ label="&PreviewModeTab.label;"
+ tooltiptext="&PreviewMode.tooltip;"
+ command="cmd_PreviewMode"/>
+ </tabs>
+ </tabbox>
+
+ </vbox> <!-- appcontent -->
+</hbox><!-- sidebar-parent -->
+
+ <!-- Some of this is from globalOverlay.xhtml -->
+ <hbox class="statusbar chromeclass-status" id="status-bar">
+ <statusbarpanel id="component-bar"/>
+ <hbox id="structToolbar" class="statusbarpanel" flex="1" pack="end">
+ <label id="structSpacer" value="" flex="1"/>
+ </hbox>
+ <statusbarpanel id="offline-status" class="statusbarpanel-iconic"/>
+ </hbox>
+</window>
diff --git a/comm/suite/editor/base/content/editorApplicationOverlay.js b/comm/suite/editor/base/content/editorApplicationOverlay.js
new file mode 100644
index 0000000000..17d02a510b
--- /dev/null
+++ b/comm/suite/editor/base/content/editorApplicationOverlay.js
@@ -0,0 +1,161 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Implementations of nsIControllerCommand for composer commands */
+
+function initEditorContextMenuItems(aEvent)
+{
+ var shouldShowEditPage = !gContextMenu.onImage && !gContextMenu.onLink && !gContextMenu.onTextInput && !gContextMenu.inDirList;
+ gContextMenu.showItem( "context-editpage", shouldShowEditPage );
+
+ var shouldShowEditLink = gContextMenu.onSaveableLink;
+ gContextMenu.showItem( "context-editlink", shouldShowEditLink );
+
+ // Hide the applications separator if there's no add-on apps present.
+ gContextMenu.showItem("context-sep-apps", gContextMenu.shouldShowSeparator("context-sep-apps"));
+}
+
+function initEditorContextMenuListener(aEvent)
+{
+ var popup = document.getElementById("contentAreaContextMenu");
+ if (popup)
+ popup.addEventListener("popupshowing", initEditorContextMenuItems);
+}
+
+addEventListener("load", initEditorContextMenuListener, false);
+
+function editDocument(aDocument)
+{
+ if (!aDocument)
+ aDocument = window.content.document;
+
+ editPage(aDocument.URL);
+}
+
+function editPageOrFrame()
+{
+ var focusedWindow = document.commandDispatcher.focusedWindow;
+
+ // if the uri is a specific frame, grab it, else use the frameset uri
+ // and let Composer handle error if necessary
+ editPage(getContentFrameURI(focusedWindow));
+}
+
+function getContentFrameURI(aFocusedWindow)
+{
+ let isContentFrame = aFocusedWindow ?
+ (aFocusedWindow.top == window.content) : false;
+
+ let contentFrame = isContentFrame ?
+ aFocusedWindow : window.content;
+
+ return contentFrame.location.href;
+}
+
+// Any non-editor window wanting to create an editor with a URL
+// should use this instead of "window.openDialog..."
+// We must always find an existing window with requested URL
+function editPage(url, aFileType)
+{
+ // aFileType is optional and needs to default to html.
+ aFileType = aFileType || "html";
+
+ // Always strip off "view-source:" and #anchors
+ url = url.replace(/^view-source:/, "").replace(/#.*/, "");
+
+ // if the current window is a browser window, then extract the current charset menu setting from the current
+ // document and use it to initialize the new composer window...
+
+ var wintype = document.documentElement.getAttribute('windowtype');
+ var charsetArg;
+
+ if (wintype == "navigator:browser" && content.document)
+ charsetArg = "charset=" + content.document.characterSet;
+
+ try {
+ let uri = createURI(url, null, null);
+
+ let enumerator = Services.wm.getEnumerator("composer:" + aFileType);
+ let emptyWindow;
+ while ( enumerator.hasMoreElements() )
+ {
+ var win = enumerator.getNext();
+ if (win && !win.closed && win.IsWebComposer())
+ {
+ if (CheckOpenWindowForURIMatch(uri, win))
+ {
+ // We found an editor with our url
+ win.focus();
+ return;
+ }
+ else if (!emptyWindow && win.PageIsEmptyAndUntouched())
+ {
+ emptyWindow = win;
+ }
+ }
+ }
+
+ if (emptyWindow)
+ {
+ // we have an empty window we can use
+ if (aFileType == "html" && emptyWindow.IsInHTMLSourceMode())
+ emptyWindow.SetEditMode(emptyWindow.PreviousNonSourceDisplayMode);
+ emptyWindow.EditorLoadUrl(url);
+ emptyWindow.focus();
+ emptyWindow.SetSaveAndPublishUI(url);
+ return;
+ }
+
+ // Create new Composer / Text Editor window.
+ if (aFileType == "text" && ("EditorNewPlaintext" in window))
+ EditorNewPlaintext(url, charsetArg);
+ else
+ NewEditorWindow(url, charsetArg);
+
+ } catch(e) {}
+}
+
+function createURI(urlstring)
+{
+ try {
+ return Services.io.newURI(urlstring);
+ } catch (e) {}
+
+ return null;
+}
+
+function CheckOpenWindowForURIMatch(uri, win)
+{
+ try {
+ return createURI(win.content.document.URL).equals(uri);
+ } catch (e) {}
+
+ return false;
+}
+
+function toEditor()
+{
+ if (!CycleWindow("composer:html"))
+ NewEditorWindow();
+}
+
+function NewEditorWindow(aUrl, aCharsetArg)
+{
+ window.openDialog("chrome://editor/content",
+ "_blank",
+ "chrome,all,dialog=no",
+ aUrl || "about:blank",
+ aCharsetArg);
+}
+
+function NewEditorFromTemplate()
+{
+ // XXX not implemented
+}
+
+function NewEditorFromDraft()
+{
+ // XXX not implemented
+}
diff --git a/comm/suite/editor/base/content/editorOverlay.xhtml b/comm/suite/editor/base/content/editorOverlay.xhtml
new file mode 100644
index 0000000000..7a5d3900f0
--- /dev/null
+++ b/comm/suite/editor/base/content/editorOverlay.xhtml
@@ -0,0 +1,1504 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay [
+<!ENTITY % editorOverlayDTD SYSTEM "chrome://editor/locale/editorOverlay.dtd">
+%editorOverlayDTD;
+<!ENTITY % editorSmileyOverlayDTD SYSTEM
+ "chrome://editor/locale/editorSmileyOverlay.dtd">
+%editorSmileyOverlayDTD;
+]>
+
+<overlay id="editorOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+<script src="chrome://editor/content/editorUtilities.js"/>
+<script src="chrome://editor/content/ComposerCommands.js"/>
+
+ <keyset id="editorKeys">
+ <!-- defined in globalOverlay -->
+ <key id="key_newNavigator"/>
+ <key id="key_newPrivateWindow"/>
+ <key id="key_newBlankPage"/>
+ <key id="key_save"
+ key="&saveCmd.key;"
+ command="cmd_save"
+ modifiers="accel"/>
+ <key id="key_print"/>
+ <key id="key_close"/>
+ <key id="key_undo"/>
+ <key id="key_redo"/>
+ <key id="key_cut"/>
+ <key id="key_copy"/>
+ <key id="key_paste"/>
+ <key id="key_delete"/>
+ <key id="key_delete2"/>
+ <key id="key_selectAll"/>
+ <key id="pastequotationkb"
+ key="&pasteAsQuotationCmd.key;"
+ command="cmd_pasteQuote"
+ modifiers="accel, shift"/>
+ <key id="pastenoformattingkb"
+ key="&pasteNoFormatting.key;"
+ command="cmd_pasteNoFormatting"
+ modifiers="accel, shift"/>
+ <key id="key_rewrap"
+ key="&editRewrapCmd.key;"
+ command="cmd_rewrap"
+ modifiers="accel"/>
+ <keyset id="findKeys"/>
+ <key id="key_checkspelling"
+ key="&checkSpellingCmd2.key;"
+ command="cmd_spelling"
+ modifiers="accel,shift"/>
+
+ <key id="boldkb"
+ key="&styleBoldCmd.key;"
+ command="cmd_bold"
+ modifiers="accel"/>
+ <key id="italickb"
+ key="&styleItalicCmd.key;"
+ command="cmd_italic"
+ modifiers="accel"/>
+ <key id="underlinekb"
+ key="&styleUnderlineCmd.key;"
+ command="cmd_underline"
+ modifiers="accel"/>
+ <key id="fixedwidthkb"
+ key="&fontFixedWidth.key;"
+ command="cmd_tt"
+ modifiers="accel"/>
+
+ <key id="increaseindentkb"
+ key="&increaseIndent.key;"
+ command="cmd_indent"
+ modifiers="accel"/>
+ <key id="decreaseindentkb"
+ key="&decreaseIndent.key;"
+ command="cmd_outdent"
+ modifiers="accel"/>
+
+ <key id="removestyleskb"
+ key="&formatRemoveStyles.key;"
+ command="cmd_removeStyles"
+ modifiers="accel, shift"/>
+ <key id="removestyleskb2"
+ key=" "
+ command="cmd_removeStyles"
+ modifiers="accel"/>
+ <key id="removelinkskb"
+ key="&formatRemoveLinks.key;"
+ command="cmd_removeLinks"
+ modifiers="accel, shift"/>
+ <key id="removenamedanchorskb"
+ key="&formatRemoveNamedAnchors2.key;"
+ command="cmd_removeNamedAnchors"
+ modifiers="accel, shift"/>
+ <key id="decreasefontsizekb"
+ key="&decrementFontSize.key;"
+ command="cmd_decreaseFontStep"
+ modifiers="accel"/>
+ <key id="increasefontsizekb"
+ key="&incrementFontSize.key;"
+ command="cmd_increaseFontStep"
+ modifiers="accel"/>
+ <key key="&incrementFontSize.key;"
+ command="cmd_increaseFontStep"
+ modifiers="accel,shift"/>
+ <key key="&incrementFontSize.key2;"
+ command="cmd_increaseFontStep"
+ modifiers="accel"/>
+
+ <key id="insertlinkkb"
+ key="&insertLinkCmd2.key;"
+ command="cmd_link"
+ modifiers="accel"/>
+ </keyset>
+
+ <!-- commands updated when the editor gets created -->
+ <commandset id="commonEditorMenuItems"
+ commandupdater="true"
+ events="create"
+ oncommandupdate="goUpdateComposerMenuItems(this)">
+ <command id="cmd_printSetup" oncommand="goDoCommand('cmd_printSetup');"/>
+ <command id="cmd_printpreview" oncommand="goDoCommand('cmd_printpreview');"/>
+ <command id="cmd_print" oncommand="goDoCommand('cmd_print');"/>
+ <command id="cmd_close" oncommand="goDoCommand('cmd_close');"/>
+ </commandset>
+
+ <commandset id="composerMenuItems"
+ commandupdater="true"
+ events="create, mode_switch"
+ oncommandupdate="goUpdateComposerMenuItems(this)">
+ <!-- format menu -->
+ <command id="cmd_listProperties" oncommand="goDoCommand('cmd_listProperties')"/>
+ <command id="cmd_colorProperties" oncommand="goDoCommand('cmd_colorProperties')"/>
+
+ <command id="cmd_link" oncommand="goDoCommand('cmd_link')"/>
+ <command id="cmd_anchor" oncommand="goDoCommand('cmd_anchor')"/>
+ <command id="cmd_image" oncommand="goDoCommand('cmd_image')"/>
+ <command id="cmd_hline" oncommand="goDoCommand('cmd_hline')"/>
+ <command id="cmd_table" oncommand="goDoCommand('cmd_table')"/>
+ <command id="cmd_objectProperties" oncommand="goDoCommand('cmd_objectProperties')"/>
+ <command id="cmd_insertChars" oncommand="goDoCommand('cmd_insertChars')" label="&insertCharsCmd.label;"/>
+ <command id="cmd_insertHTMLWithDialog" oncommand="goDoCommand('cmd_insertHTMLWithDialog')" label="&insertHTMLCmd.label;"/>
+ <command id="cmd_insertMathWithDialog" oncommand="goDoCommand('cmd_insertMathWithDialog')" label="&insertMathCmd.label;"/>
+ <command id="cmd_insertBreak" oncommand="goDoCommand('cmd_insertBreak')"/>
+ <command id="cmd_insertBreakAll" oncommand="goDoCommand('cmd_insertBreakAll')"/>
+
+ <!-- only used in context popup menu -->
+ <command id="cmd_editLink" oncommand="goDoCommand('cmd_editLink')"/>
+
+ <!-- dummy command used just to disable things in non-HTML modes -->
+ <command id="cmd_renderedHTMLEnabler"/>
+ </commandset>
+
+ <!-- edit menu commands. These get updated by code in globalOverlay.js -->
+ <commandset id="composerEditMenuItems"
+ commandupdater="true"
+ events="create, mode_switch"
+ oncommandupdate="goUpdateComposerMenuItems(this)">
+ <command id="cmd_undo"/>
+ <command id="cmd_redo"/>
+ <command id="cmd_cut"/>
+ <command id="cmd_copy"/>
+ <command id="cmd_paste"/>
+ <command id="cmd_pasteNoFormatting"
+ label="&pasteNoFormatting.label;"
+ accesskey="&pasteNoFormatting.accesskey;"
+ oncommand="goDoCommand('cmd_pasteNoFormatting');"/>
+ <command id="cmd_delete"/>
+ <command id="cmd_selectAll"/>
+ <command id="cmd_preferences" oncommand="goDoCommand('cmd_preferences')"/>
+ <command id="cmd_findReplace" oncommand="goDoCommand('cmd_findReplace')"/>
+ <command id="cmd_find" oncommand="goDoCommand('cmd_find')"/>
+ <command id="cmd_findNext" oncommand="goDoCommand('cmd_findNext');"/>
+ <command id="cmd_findPrev" oncommand="goDoCommand('cmd_findPrev');"/>
+ <command id="cmd_spelling" oncommand="goDoCommand('cmd_spelling')"/>
+ <command id="cmd_pasteQuote"
+ label="&pasteAsQuotationCmd.label;"
+ accesskey="&pasteAsQuotationCmd.accesskey;"
+ oncommand="goDoCommand('cmd_pasteQuote');"/>
+ <command id="cmd_rewrap" oncommand="goDoCommand('cmd_rewrap');"/>
+ </commandset>
+
+ <!-- style related commands that update on creation, and on selection change -->
+ <commandset id="composerStyleMenuItems"
+ commandupdater="true"
+ events="create, style, mode_switch"
+ oncommandupdate="goUpdateComposerMenuItems(this)">
+ <command id="cmd_bold" state="false" oncommand="doStyleUICommand('cmd_bold')"/>
+ <command id="cmd_italic" state="false" oncommand="doStyleUICommand('cmd_italic')"/>
+ <command id="cmd_underline" state="false" oncommand="doStyleUICommand('cmd_underline')"/>
+ <command id="cmd_tt" state="false" oncommand="doStyleUICommand('cmd_tt')"/>
+ <command id="cmd_smiley"/>
+ <command id="cmd_strikethrough" state="false" oncommand="doStyleUICommand('cmd_strikethrough');"/>
+ <command id="cmd_superscript" state="false" oncommand="doStyleUICommand('cmd_superscript');"/>
+ <command id="cmd_subscript" state="false" oncommand="doStyleUICommand('cmd_subscript');"/>
+ <command id="cmd_nobreak" state="false" oncommand="doStyleUICommand('cmd_nobreak');"/>
+ <command id="cmd_em" state="false" oncommand="doStyleUICommand('cmd_em')"/>
+ <command id="cmd_strong" state="false" oncommand="doStyleUICommand('cmd_strong')"/>
+ <command id="cmd_cite" state="false" oncommand="doStyleUICommand('cmd_cite')"/>
+ <command id="cmd_abbr" state="false" oncommand="doStyleUICommand('cmd_abbr')"/>
+ <command id="cmd_acronym" state="false" oncommand="doStyleUICommand('cmd_acronym')"/>
+ <command id="cmd_code" state="false" oncommand="doStyleUICommand('cmd_code')"/>
+ <command id="cmd_samp" state="false" oncommand="doStyleUICommand('cmd_samp')"/>
+ <command id="cmd_var" state="false" oncommand="doStyleUICommand('cmd_var')"/>
+ <command id="cmd_ul" state="false" oncommand="doStyleUICommand('cmd_ul')"/>
+ <command id="cmd_ol" state="false" oncommand="doStyleUICommand('cmd_ol')"/>
+ <command id="cmd_indent" oncommand="goDoCommand('cmd_indent')"/>
+ <command id="cmd_outdent" oncommand="goDoCommand('cmd_outdent')"/>
+
+ <!-- the state attribute gets filled with the paragraph format before the command is executed -->
+ <command id="cmd_paragraphState" state="" oncommand="doStatefulCommand('cmd_paragraphState', event.target.value)"/>
+ <command id="cmd_fontFace" state="" oncommand="doStatefulCommand('cmd_fontFace', event.target.value)"/>
+
+ <!-- No "oncommand", use EditorSelectColor() to bring up color dialog -->
+ <command id="cmd_fontColor" state=""/>
+ <command id="cmd_backgroundColor" state=""/>
+ <command id="cmd_highlight" state="transparent" oncommand="EditorSelectColor('Highlight', event);"/>
+ <command id="cmd_fontSize" oncommand="goDoCommand('cmd_fontSize')"/>
+ <command id="cmd_align" state=""/>
+ <command id="cmd_absPos" state="" oncommand="goDoCommand('cmd_absPos')"/>
+ <command id="cmd_increaseZIndex" state="" oncommand="goDoCommand('cmd_increaseZIndex')"/>
+ <command id="cmd_decreaseZIndex" state="" oncommand="goDoCommand('cmd_decreaseZIndex')"/>
+ <command id="cmd_advancedProperties" oncommand="goDoCommand('cmd_advancedProperties')"/>
+ <command id="cmd_increaseFontStep" oncommand="goDoCommand('cmd_increaseFontStep')"/>
+ <command id="cmd_decreaseFontStep" oncommand="goDoCommand('cmd_decreaseFontStep')"/>
+ <command id="cmd_removeStyles" oncommand="goDoCommand('cmd_removeStyles')"/>
+ <command id="cmd_removeLinks" oncommand="goDoCommand('cmd_removeLinks')"/>
+ <command id="cmd_removeNamedAnchors" oncommand="goDoCommand('cmd_removeNamedAnchors')"/>
+ </commandset>
+
+ <!-- commands updated only when the menu gets created -->
+ <commandset id="composerListMenuItems"
+ commandupdater="true"
+ events="create, mode_switch"
+ oncommandupdate="goUpdateComposerMenuItems(this)">
+ <!-- List menu -->
+ <command id="cmd_dt" oncommand="doStyleUICommand('cmd_dt')"/>
+ <command id="cmd_dd" oncommand="doStyleUICommand('cmd_dd')"/>
+ <command id="cmd_removeList" oncommand="goDoCommand('cmd_removeList')"/>
+ <!-- cmd_ul and cmd_ol are shared with toolbar and are in composerStyleMenuItems commandset -->
+ </commandset>
+
+ <commandset id="composerTableMenuItems"
+ commandupdater="true"
+ events="create, mode_switch"
+ oncommandupdate="goUpdateTableMenuItems(this)">
+ <!-- Table menu -->
+ <command id="cmd_SelectTable" oncommand="goDoCommand('cmd_SelectTable')"/>
+ <command id="cmd_SelectRow" oncommand="goDoCommand('cmd_SelectRow')"/>
+ <command id="cmd_SelectColumn" oncommand="goDoCommand('cmd_SelectColumn')"/>
+ <command id="cmd_SelectCell" oncommand="goDoCommand('cmd_SelectCell')"/>
+ <command id="cmd_SelectAllCells" oncommand="goDoCommand('cmd_SelectAllCells')"/>
+ <command id="cmd_InsertTable" oncommand="goDoCommand('cmd_InsertTable')"/>
+ <command id="cmd_InsertRowAbove" oncommand="goDoCommand('cmd_InsertRowAbove')"/>
+ <command id="cmd_InsertRowBelow" oncommand="goDoCommand('cmd_InsertRowBelow')"/>
+ <command id="cmd_InsertColumnBefore" oncommand="goDoCommand('cmd_InsertColumnBefore')"/>
+ <command id="cmd_InsertColumnAfter" oncommand="goDoCommand('cmd_InsertColumnAfter')"/>
+ <command id="cmd_InsertCellBefore" oncommand="goDoCommand('cmd_InsertCellBefore')"/>
+ <command id="cmd_InsertCellAfter" oncommand="goDoCommand('cmd_InsertCellAfter')"/>
+ <command id="cmd_DeleteTable" oncommand="goDoCommand('cmd_DeleteTable')"/>
+ <command id="cmd_DeleteRow" oncommand="goDoCommand('cmd_DeleteRow')"/>
+ <command id="cmd_DeleteColumn" oncommand="goDoCommand('cmd_DeleteColumn')"/>
+ <command id="cmd_DeleteCell" oncommand="goDoCommand('cmd_DeleteCell')"/>
+ <command id="cmd_DeleteCellContents" oncommand="goDoCommand('cmd_DeleteCellContents')"/>
+ <command id="cmd_NormalizeTable" oncommand="goDoCommand('cmd_NormalizeTable')"/>
+ <command id="cmd_JoinTableCells" oncommand="goDoCommand('cmd_JoinTableCells')"/>
+ <command id="cmd_SplitTableCell" oncommand="goDoCommand('cmd_SplitTableCell')"/>
+ <command id="cmd_ConvertToTable" oncommand="goDoCommand('cmd_ConvertToTable')"/>
+ <command id="cmd_TableOrCellColor" oncommand="goDoCommand('cmd_TableOrCellColor')"/>
+ <command id="cmd_editTable" oncommand="goDoCommand('cmd_editTable')"/>
+ </commandset>
+
+ <commandset id="editorCommands">
+ <commandset id="globalEditMenuItems"/>
+ <commandset id="selectEditMenuItems"/>
+ <commandset id="undoEditMenuItems"/>
+ <commandset id="clipboardEditMenuItems"/>
+ <command id="toggleSidebar"/>
+ <!-- file menu -->
+ <command id="cmd_newNavigator"/>
+ <command id="cmd_newPrivateWindow"/>
+ <command id="cmd_newEditor"/>
+ <command id="cmd_newEditorTemplate"/>
+ <command id="cmd_newEditorDraft"/>
+ </commandset>
+
+ <popupset id="editorPopupSet">
+ <menupopup id="popupNotificationMenu"/>
+ <menupopup id="toolbar-context-menu"/>
+ <panel id="customizeToolbarSheetPopup"/>
+ </popupset>
+
+ <menu id="menu_Edit">
+ <menupopup id="menu_EditPopup">
+ <!-- from utilityOverlay.xhtml -->
+ <menuitem id="menu_undo"/>
+ <menuitem id="menu_redo"/>
+ <menuseparator id="sep_cut"/>
+ <menuitem id="menu_cut"/>
+ <menuitem id="menu_copy"/>
+ <menuitem id="menu_paste"/>
+ <menuitem id="menu_pasteNoFormatting"
+ key="pastenoformattingkb"
+ command="cmd_pasteNoFormatting"/>
+ <menuitem id="menu_pasteQuote"
+ key="pastequotationkb"
+ command="cmd_pasteQuote"/>
+ <menuitem id="menu_rewrap"
+ label="&editRewrapCmd.label;"
+ accesskey="&editRewrapCmd.accesskey;"
+ key="key_rewrap"
+ command="cmd_rewrap"/>
+ <menuitem id="menu_delete"/>
+ <menuseparator id="sep_selectAll"/>
+ <menuitem id="menu_selectAll"/>
+ <menuseparator id="sep_find"/>
+ <menuitem id="menu_find"
+ label="&findBarCmd.label;"/>
+ <menuitem id="menu_findReplace"
+ label="&findReplaceCmd.label;"/>
+ <menuitem id="menu_findNext"/>
+ <menuitem id="menu_findPrev"/>
+ <menuseparator id="sep_checkspelling"/>
+ <menuitem id="menu_checkspelling"
+ label="&checkSpellingCmd2.label;"
+ accesskey="&checkSpellingCmd2.accesskey;"
+ key="key_checkspelling"
+ command="cmd_spelling"/>
+ <menuitem id="menu_inlineSpellCheck"
+ type="checkbox"
+ label="&enableInlineSpellChecker.label;"
+ accesskey="&enableInlineSpellChecker.accesskey;"/>
+ <menuseparator id="sep_preferences"/>
+ <menuitem id="menu_preferences"
+ command="cmd_preferences"/>
+ </menupopup>
+ </menu>
+
+ <!-- Insert menu -->
+ <menu id="insertMenu"
+ label="&insertMenu.label;"
+ accesskey="&insertMenu.accesskey;">
+ <menupopup id="insertMenuPopup">
+ <menuitem id="insertImage"
+ label="&insertImageCmd.label;"
+ accesskey="&insertImageCmd.accesskey;"
+ command="cmd_image"/>
+ <menuitem id="insertTable"
+ label="&insertTableCmd.label;"
+ accesskey="&insertTableCmd.accesskey;"
+ command="cmd_InsertTable"/>
+ <menuitem id="insertLink"
+ label="&insertLinkCmd2.label;"
+ accesskey="&insertLinkCmd2.accesskey;"
+ command="cmd_link"
+ key="insertlinkkb"/>
+ <menuitem id="insertAnchor"
+ label="&insertAnchorCmd.label;"
+ accesskey="&insertAnchorCmd.accesskey;"
+ command="cmd_anchor"/>
+ <menuitem id="insertHline"
+ label="&insertHLineCmd.label;"
+ accesskey="&insertHLineCmd.accesskey;"
+ command="cmd_hline"/>
+ <menuitem id="insertHTMLSource"
+ accesskey="&insertHTMLCmd.accesskey;"
+ command="cmd_insertHTMLWithDialog"/>
+ <menuitem id="insertMath"
+ accesskey="&insertMathCmd.accesskey;"
+ command="cmd_insertMathWithDialog"/>
+ <menuitem id="insertChars"
+ accesskey="&insertCharsCmd.accesskey;"
+ command="cmd_insertChars"/>
+ <menu id="insertTOC" label="&tocMenu.label;" accesskey="&tocMenu.accesskey;">
+ <menupopup id="insertTOCPopup" onpopupshowing="InitTOCMenu()">
+ <menuitem id="insertTOCMenuitem"
+ label="&insertTOC.label;"
+ accesskey="&insertTOC.accesskey;"
+ oncommand="UpdateTOC()"/>
+ <menuitem id="updateTOCMenuitem"
+ label="&updateTOC.label;"
+ accesskey="&updateTOC.accesskey;"
+ oncommand="UpdateTOC()"/>
+ <menuitem id="removeTOCMenuitem"
+ label="&removeTOC.label;"
+ accesskey="&removeTOC.accesskey;"
+ oncommand="RemoveTOC()"/>
+ </menupopup>
+ </menu>
+ <menu id="insertSmiley"
+ label="&insertSmiley.label;"
+ accesskey="&insertSmiley.accesskey;">
+ <menupopup id="smilyMenuPopup">
+ <menuitem class="smiley insert-smile menuitem-iconic"
+ label="&smiley1Cmd.label;"
+ accesskey="&smiley1Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-)');"/>
+ <menuitem class="smiley insert-frown menuitem-iconic"
+ label="&smiley2Cmd.label;"
+ accesskey="&smiley2Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-(');"/>
+ <menuitem class="smiley insert-wink menuitem-iconic"
+ label="&smiley3Cmd.label;"
+ accesskey="&smiley3Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ';-)');"/>
+ <menuitem class="smiley insert-tongue menuitem-iconic"
+ label="&smiley4Cmd.label;"
+ accesskey="&smiley4Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-P');"/>
+ <menuitem class="smiley insert-laughing menuitem-iconic"
+ label="&smiley5Cmd.label;"
+ accesskey="&smiley5Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-D');"/>
+ <menuitem class="smiley insert-embarrassed menuitem-iconic"
+ label="&smiley6Cmd.label;"
+ accesskey="&smiley6Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-[');"/>
+ <menuitem class="smiley insert-undecided menuitem-iconic"
+ label="&smiley7Cmd.label;"
+ accesskey="&smiley7Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-\\');"/>
+ <menuitem class="smiley insert-surprise menuitem-iconic"
+ label="&smiley8Cmd.label;"
+ accesskey="&smiley8Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', '=-O');"/>
+ <menuitem class="smiley insert-kiss menuitem-iconic"
+ label="&smiley9Cmd.label;"
+ accesskey="&smiley9Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-*');"/>
+ <menuitem class="smiley insert-yell menuitem-iconic"
+ label="&smiley10Cmd.label;"
+ accesskey="&smiley10Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', '>:o');"/>
+ <menuitem class="smiley insert-cool menuitem-iconic"
+ label="&smiley11Cmd.label;"
+ accesskey="&smiley11Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', '8-)');"/>
+ <menuitem class="smiley insert-money menuitem-iconic"
+ label="&smiley12Cmd.label;"
+ accesskey="&smiley12Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-$');"/>
+ <menuitem class="smiley insert-foot menuitem-iconic"
+ label="&smiley13Cmd.label;"
+ accesskey="&smiley13Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-!');"/>
+ <menuitem class="smiley insert-innocent menuitem-iconic"
+ label="&smiley14Cmd.label;"
+ accesskey="&smiley14Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', 'O:-)');"/>
+ <menuitem class="smiley insert-cry menuitem-iconic"
+ label="&smiley15Cmd.label;"
+ accesskey="&smiley15Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':\'(');"/>
+ <menuitem class="smiley insert-sealed menuitem-iconic"
+ label="&smiley16Cmd.label;"
+ accesskey="&smiley16Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-X');"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="insertMenuSeparator"/>
+ <menuitem id="insertBreakAll"
+ accesskey="&insertBreakAllCmd.accesskey;"
+ command="cmd_insertBreakAll"
+ label="&insertBreakAllCmd.label;"/>
+ </menupopup>
+ </menu>
+
+ <!-- Format Menu -->
+ <menupopup id="formatMenuPopup" onpopupshowing="EditorInitFormatMenu()">
+ <!-- Font face submenu -->
+ <menu id="fontFaceMenu"
+ label="&fontfaceMenu.label;"
+ accesskey="&fontfaceMenu.accesskey;"
+ position="1">
+ <menupopup id="fontFaceMenuPopup"
+ oncommand="if (event.target.localName == 'menuitem')
+ doStatefulCommand('cmd_fontFace', event.target.getAttribute('value'));"
+ onpopupshowing="initFontFaceMenu(this);">
+ <menuitem id="menu_fontFaceVarWidth"
+ label="&fontVarWidth.label;"
+ accesskey="&fontVarWidth.accesskey;"
+ value=""
+ type="radio"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_fontFaceFixedWidth"
+ label="&fontFixedWidth.label;"
+ accesskey="&fontFixedWidth.accesskey;"
+ value="tt"
+ type="radio"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuseparator id="fontFaceMenuAfterGenericFontsSeparator"/>
+ <menuitem id="menu_fontFaceHelvetica"
+ label="&fontHelvetica.label;"
+ accesskey="&fontHelvetica.accesskey;"
+ value="Helvetica, Arial, sans-serif"
+ value_parsed="helvetica,arial,sans-serif"
+ type="radio"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_fontFaceTimes"
+ label="&fontTimes.label;"
+ accesskey="&fontTimes.accesskey;"
+ value="Times New Roman, Times, serif"
+ value_parsed="times new roman,times,serif"
+ type="radio"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_fontFaceCourier"
+ label="&fontCourier.label;"
+ accesskey="&fontCourier.accesskey;"
+ value="Courier New, Courier, monospace"
+ value_parsed="courier new,courier,monospace"
+ type="radio"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuseparator id="fontFaceMenuAfterDefaultFontsSeparator"
+ class="fontFaceMenuAfterDefaultFonts"/>
+ <menuseparator id="fontFaceMenuAfterUsedFontsSeparator"
+ class="fontFaceMenuAfterUsedFonts"
+ collapsed="true"/>
+ <!-- Local font face items added here by initLocalFontFaceMenu() -->
+ </menupopup>
+ </menu>
+
+ <!-- Font size submenu -->
+ <menu id="fontSizeMenu" label="&fontSizeMenu.label;"
+ accesskey="&fontSizeMenu.accesskey;"
+ position="2">
+ <menupopup id="fontSizeMenuPopup" onpopupshowing="initFontSizeMenu(this, true)">
+ <menuitem id="menu_decreaseFontSize"
+ label="&decreaseFontSize.label;"
+ accesskey="&decreaseFontSize.accesskey;"
+ command="cmd_decreaseFontStep"
+ type="radio" name="1" autocheck="false"
+ key="decreasefontsizekb"/>
+ <menuitem id="menu_increaseFontSize"
+ label="&increaseFontSize.label;"
+ accesskey="&increaseFontSize.accesskey;"
+ command="cmd_increaseFontStep"
+ type="radio" name="1" autocheck="false"
+ key="increasefontsizekb"/>
+ <menuseparator id="fontSizeMenuAfterIncreaseFontSizeSeparator"/>
+ <menuitem id="menu_x-small"
+ label="&size-tinyCmd.label;"
+ accesskey="&size-tinyCmd.accesskey;"
+ oncommand="EditorSetFontSize('x-small')"
+ type="radio" name="1"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_size-small"
+ label="&size-smallCmd.label;"
+ accesskey="&size-smallCmd.accesskey;"
+ oncommand="EditorSetFontSize('small')"
+ type="radio" name="1"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_size-medium"
+ label="&size-mediumCmd.label;"
+ accesskey="&size-mediumCmd.accesskey;"
+ oncommand="EditorSetFontSize('medium')"
+ type="radio" name="1"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_size-large"
+ label="&size-largeCmd.label;"
+ accesskey="&size-largeCmd.accesskey;"
+ oncommand="EditorSetFontSize('large')"
+ type="radio" name="1"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_size-x-large"
+ label="&size-extraLargeCmd.label;"
+ accesskey="&size-extraLargeCmd.accesskey;"
+ oncommand="EditorSetFontSize('x-large')"
+ type="radio" name="1"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_size-xx-large"
+ label="&size-hugeCmd.label;"
+ accesskey="&size-hugeCmd.accesskey;"
+ oncommand="EditorSetFontSize('xx-large')"
+ type="radio" name="1"
+ observes="cmd_renderedHTMLEnabler"/>
+ <!-- Enable if required for SeaMonkey.
+ <menuitem id="fontSizeMenu_smallBigInfo"
+ type="checkbox" name="2" disabled="true" hidden="true"/>
+ -->
+ </menupopup>
+ </menu>
+
+ <!-- Font style submenu -->
+ <menu id="fontStyleMenu" label="&fontStyleMenu.label;"
+ accesskey="&fontStyleMenu.accesskey;"
+ position="3">
+ <menupopup id="fontStyleMenuPopup" onpopupshowing="initFontStyleMenu(this)">
+ <menuitem id="menu_styleBold"
+ label="&styleBoldCmd.label;"
+ accesskey="&styleBoldCmd.accesskey;"
+ observes="cmd_bold"
+ type="checkbox"
+ key="boldkb"/>
+ <menuitem id="menu_styleItalic"
+ label="&styleItalicCmd.label;"
+ accesskey="&styleItalicCmd.accesskey;"
+ observes="cmd_italic"
+ type="checkbox"
+ key="italickb"/>
+ <menuitem id="menu_styleUnderline"
+ label="&styleUnderlineCmd.label;"
+ accesskey="&styleUnderlineCmd.accesskey;"
+ observes="cmd_underline"
+ type="checkbox"
+ key="underlinekb"/>
+ <menuitem id="menu_styleStrikeThru"
+ label="&styleStrikeThruCmd.label;"
+ accesskey="&styleStrikeThruCmd.accesskey;"
+ observes="cmd_strikethrough"
+ type="checkbox"/>
+ <menuitem id="menu_styleSuperscript"
+ label="&styleSuperscriptCmd.label;"
+ accesskey="&styleSuperscriptCmd.accesskey;"
+ observes="cmd_superscript"
+ type="checkbox"/>
+ <menuitem id="menu_styleSubscript"
+ label="&styleSubscriptCmd.label;"
+ accesskey="&styleSubscriptCmd.accesskey;"
+ observes="cmd_subscript"
+ type="checkbox"/>
+ <menuitem id="menu_fontFixedWidth"
+ label="&fontFixedWidth.label;"
+ accesskey="&fontFixedWidth.accesskey;"
+ observes="cmd_tt"
+ type="checkbox"
+ key="fixedwidthkb"/>
+ <menuitem id="menu_styleNonbreaking"
+ label="&styleNonbreakingCmd.label;"
+ accesskey="&styleNonbreakingCmd.accesskey;"
+ observes="cmd_nobreak"
+ type="checkbox"/>
+ <menuseparator id="fontStyleMenuAfterNonbreakingSeparator"/>
+ <menuitem id="menu_styleEm"
+ label="&styleEm.label;"
+ accesskey="&styleEm.accesskey;"
+ observes="cmd_em"
+ type="checkbox"/>
+ <menuitem id="menu_styleStrong"
+ label="&styleStrong.label;"
+ accesskey="&styleStrong.accesskey;"
+ observes="cmd_strong"
+ type="checkbox"/>
+ <menuitem id="menu_styleCite"
+ label="&styleCite.label;"
+ accesskey="&styleCite.accesskey;"
+ observes="cmd_cite"
+ type="checkbox"/>
+ <menuitem id="menu_styleAbbr"
+ label="&styleAbbr.label;"
+ accesskey="&styleAbbr.accesskey;"
+ observes="cmd_abbr"
+ type="checkbox"/>
+ <menuitem id="menu_styleAcronym"
+ label="&styleAcronym.label;"
+ accesskey="&styleAcronym.accesskey;"
+ observes="cmd_acronym"
+ type="checkbox"/>
+ <menuitem id="menu_styleCode"
+ label="&styleCode.label;"
+ accesskey="&styleCode.accesskey;"
+ observes="cmd_code"
+ type="checkbox"/>
+ <menuitem id="menu_styleSamp"
+ label="&styleSamp.label;"
+ accesskey="&styleSamp.accesskey;"
+ observes="cmd_samp"
+ type="checkbox"/>
+ <menuitem id="menu_styleVar"
+ label="&styleVar.label;"
+ accesskey="&styleVar.accesskey;"
+ observes="cmd_var"
+ type="checkbox"/>
+ </menupopup>
+ </menu>
+
+ <!-- Note: "cmd_fontColor" only monitors color state, it doesn't execute the command
+ (We should use "cmd_fontColorState" and "cmd_backgroundColorState" ?) -->
+ <menuitem id="fontColor"
+ label="&formatFontColor.label;"
+ accesskey="&formatFontColor.accesskey;"
+ observes="cmd_fontColor"
+ oncommand="EditorSelectColor('Text', null);"
+ position="4"/>
+ <menuseparator id="removeSep" position="5"/>
+
+ <!-- label and accesskey set at runtime from strings -->
+ <menuitem id="removeStylesMenuitem"
+ key="removestyleskb"
+ observes="cmd_removeStyles"
+ position="6"/>
+ <menuitem id="removeLinksMenuitem"
+ key="removelinkskb"
+ observes="cmd_removeLinks"
+ position="7"/>
+ <menuitem id="removeNamedAnchorsMenuitem"
+ label="&formatRemoveNamedAnchors.label;"
+ key="removenamedanchorskb"
+ accesskey="&formatRemoveNamedAnchors.accesskey;"
+ observes="cmd_removeNamedAnchors"
+ position="8"/>
+ <menuseparator id="tabSep" position="9"/>
+
+ <!-- Note: the 'Init' menu methods for Paragraph, List, and Align
+ assume that the id = 'menu_'+tagName (the 'value' label),
+ except for the first ('none') item
+ -->
+ <!-- Paragraph Style submenu -->
+ <menu id="paragraphMenu" label="&paragraphMenu.label;"
+ accesskey="&paragraphMenu.accesskey;"
+ position="10" onpopupshowing="InitParagraphMenu()">
+ <menupopup id="paragraphMenuPopup"
+ oncommand="doStatefulCommand('cmd_paragraphState', event.target.getAttribute('value'))">
+ <menuitem id="menu_bodyText"
+ type="radio"
+ name="1"
+ label="&bodyTextCmd.label;"
+ accesskey="&bodyTextCmd.accesskey;"
+ value=""
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_p"
+ type="radio"
+ name="1"
+ label="&paragraphParagraphCmd.label;"
+ accesskey="&paragraphParagraphCmd.accesskey;"
+ value="p"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_h1"
+ type="radio"
+ name="1"
+ label="&heading1Cmd.label;"
+ accesskey="&heading1Cmd.accesskey;"
+ value="h1"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_h2"
+ type="radio"
+ name="1"
+ label="&heading2Cmd.label;"
+ accesskey="&heading2Cmd.accesskey;"
+ value="h2"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_h3"
+ type="radio"
+ name="1" label="&heading3Cmd.label;"
+ accesskey="&heading3Cmd.accesskey;"
+ value="h3"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_h4"
+ type="radio"
+ name="1" label="&heading4Cmd.label;"
+ accesskey="&heading4Cmd.accesskey;"
+ value="h4"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_h5"
+ type="radio"
+ name="1" label="&heading5Cmd.label;"
+ accesskey="&heading5Cmd.accesskey;"
+ value="h5"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_h6"
+ type="radio"
+ name="1"
+ label="&heading6Cmd.label;"
+ accesskey="&heading6Cmd.accesskey;"
+ value="h6"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_address"
+ type="radio"
+ name="1"
+ label="&paragraphAddressCmd.label;"
+ accesskey="&paragraphAddressCmd.accesskey;"
+ value="address"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_pre"
+ type="radio"
+ name="1"
+ label="&paragraphPreformatCmd.label;"
+ accesskey="&paragraphPreformatCmd.accesskey;"
+ value="pre"
+ observes="cmd_renderedHTMLEnabler"/>
+ </menupopup>
+ </menu>
+
+ <!-- List Style submenu -->
+ <menu id="listMenu" label="&formatlistMenu.label;"
+ accesskey="&formatlistMenu.accesskey;"
+ position="11" onpopupshowing="InitListMenu()">
+ <menupopup>
+ <menuitem id="menu_noList"
+ type="radio"
+ name="1"
+ label="&noneCmd.label;"
+ accesskey="&noneCmd.accesskey;"
+ command="cmd_removeList"/>
+ <menuitem id="menu_ul"
+ type="radio"
+ name="1"
+ label="&listBulletCmd.label;"
+ accesskey="&listBulletCmd.accesskey;"
+ command="cmd_ul"/>
+ <menuitem id="menu_ol"
+ type="radio"
+ name="1"
+ label="&listNumberedCmd.label;"
+ accesskey="&listNumberedCmd.accesskey;"
+ command="cmd_ol"/>
+ <menuitem id="menu_dt"
+ type="radio"
+ name="1"
+ label="&listTermCmd.label;"
+ accesskey="&listTermCmd.accesskey;"
+ command="cmd_dt"/>
+ <menuitem id="menu_dd"
+ type="radio"
+ name="1"
+ label="&listDefinitionCmd.label;"
+ accesskey="&listDefinitionCmd.accesskey;"
+ command="cmd_dd"/>
+ <menuseparator/>
+ <menuitem id="listProps"
+ label="&listPropsCmd.label;"
+ accesskey="&listPropsCmd.accesskey;"
+ command="cmd_listProperties"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="identingSep" position="12"/>
+
+ <menuitem id="increaseIndent"
+ label="&increaseIndent.label;"
+ accesskey="&increaseIndent.accesskey;"
+ key="increaseindentkb"
+ command="cmd_indent"
+ position="13"/>
+ <menuitem id="decreaseIndent"
+ label="&decreaseIndent.label;"
+ accesskey="&decreaseIndent.accesskey;"
+ key="decreaseindentkb"
+ command="cmd_outdent"
+ position="14"/>
+
+ <menu id="alignMenu" label="&alignMenu.label;" accesskey="&alignMenu.accesskey;"
+ onpopupshowing="InitAlignMenu()"
+ position="15">
+ <!-- Align submenu -->
+ <menupopup id="alignMenuPopup"
+ oncommand="doStatefulCommand('cmd_align', event.target.getAttribute('value'))">
+ <menuitem id="menu_left"
+ label="&alignLeft.label;"
+ accesskey="&alignLeft.accesskey;"
+ type="radio"
+ name="1"
+ value="left"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_center"
+ label="&alignCenter.label;"
+ accesskey="&alignCenter.accesskey;"
+ type="radio"
+ name="1"
+ value="center"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_right"
+ label="&alignRight.label;"
+ accesskey="&alignRight.accesskey;"
+ type="radio"
+ name="1"
+ value="right"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_justify"
+ label="&alignJustify.label;"
+ accesskey="&alignJustify.accesskey;"
+ type="radio"
+ name="1"
+ value="justify"
+ observes="cmd_renderedHTMLEnabler"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="tableSep" position="16"/>
+ <!-- Merge Table Menu and separator in Messenger Composer here -->
+ <!-- Merge property items here -->
+ </menupopup>
+
+ <!-- Next 2 are items to append at the bottom of the formatMenuPopup -->
+ <!-- label and accesskey filled in during menu creation -->
+ <menuitem id="objectProperties" command="cmd_objectProperties"/>
+ <!-- Don't use 'observes', must call command correctly -->
+ <menuitem id="colorsAndBackground"
+ label="&colorsAndBackground.label;"
+ accesskey="&colorsAndBackground.accesskey;"
+ oncommand="goDoCommand('cmd_colorProperties')"
+ observes="cmd_renderedHTMLEnabler"/>
+
+ <menu id="tableMenu" label="&tableMenu.label;" accesskey="&tableMenu.accesskey;">
+ <menupopup id="tableMenuPopup" onpopupshowing="EditorInitTableMenu()">
+ <!-- From EditorCommandOverlay.xhtml (shared with context popup) -->
+ <menu id="tableInsertMenu" label="&tableInsertMenu.label;" accesskey="&tableInsertMenu.accesskey;">
+ <menupopup id="tableInsertMenuPopup">
+ <menuitem id="menu_insertTable"
+ label="&insertTableCmd.label;"
+ accesskey="&insertTableCmd.accesskey;"
+ command="cmd_InsertTable"/>
+ <menuseparator id="tableMenuAfterInsertTableSeparator"/>
+ <menuitem id="menu_tableRowAbove"
+ label="&tableRowAbove.label;"
+ accesskey="&tableRowAbove.accesskey;"
+ command="cmd_InsertRowAbove"/>
+ <menuitem id="menu_tableRowBelow"
+ label="&tableRowBelow.label;"
+ accesskey="&tableRowBelow.accesskey;"
+ command="cmd_InsertRowBelow"/>
+ <menuseparator id="tableMenuAfterTableRowSeparator"/>
+ <menuitem id="menu_tableColumnBefore"
+ label="&tableColumnBefore.label;"
+ accesskey="&tableColumnBefore.accesskey;"
+ command="cmd_InsertColumnBefore"/>
+ <menuitem id="menu_tableColumnAfter"
+ label="&tableColumnAfter.label;"
+ accesskey="&tableColumnAfter.accesskey;"
+ command="cmd_InsertColumnAfter"/>
+ <menuseparator id="tableMenuAfterInsertColumnSeparator"/>
+ <menuitem id="menu_tableCellBefore"
+ label="&tableCellBefore.label;"
+ accesskey="&tableCellBefore.accesskey;"
+ command="cmd_InsertCellBefore"/>
+ <menuitem id="menu_tableCellAfter"
+ label="&tableCellAfter.label;"
+ accesskey="&tableCellAfter.accesskey;"
+ command="cmd_InsertCellAfter"/>
+ </menupopup>
+ </menu>
+ <menu id="tableSelectMenu" label="&tableSelectMenu.label;" accesskey="&tableSelectMenu.accesskey;">
+ <menupopup id="tableSelectPopup">
+ <menuitem id="menu_SelectTable"
+ label="&tableTable.label;"
+ accesskey="&tableTable.accesskey;"
+ command="cmd_SelectTable"/>
+ <menuitem id="menu_SelectRow"
+ label="&tableRow.label;"
+ accesskey="&tableRow.accesskey;"
+ command="cmd_SelectRow"/>
+ <menuitem id="menu_SelectColumn"
+ label="&tableColumn.label;"
+ accesskey="&tableColumn.accesskey;"
+ command="cmd_SelectColumn"/>
+ <menuitem id="menu_SelectCell"
+ label="&tableCell.label;"
+ accesskey="&tableCell.accesskey;"
+ command="cmd_SelectCell"/>
+ <menuitem id="menu_SelectAllCells"
+ label="&tableAllCells.label;"
+ accesskey="&tableAllCells.accesskey;"
+ command="cmd_SelectAllCells"/>
+ </menupopup>
+ </menu>
+ <menu id="tableDeleteMenu" label="&tableDeleteMenu.label;" accesskey="&tableDeleteMenu.accesskey;">
+ <menupopup id="tableDeletePopup">
+ <menuitem id="menu_DeleteTable"
+ label="&tableTable.label;"
+ accesskey="&tableTable.accesskey;"
+ command="cmd_DeleteTable"/>
+ <menuitem id="menu_DeleteRow"
+ label="&tableRows.label;"
+ accesskey="&tableRow.accesskey;"
+ command="cmd_DeleteRow"/>
+ <menuitem id="menu_DeleteColumn"
+ label="&tableColumns.label;"
+ accesskey="&tableColumn.accesskey;"
+ command="cmd_DeleteColumn"/>
+ <menuitem id="menu_DeleteCell"
+ label="&tableCells.label;"
+ accesskey="&tableCell.accesskey;"
+ command="cmd_DeleteCell"/>
+ <menuitem id="menu_DeleteCellContents"
+ label="&tableCellContents.label;"
+ accesskey="&tableCellContents.accesskey;"
+ command="cmd_DeleteCellContents"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="tableDeleteMenuAfterDeleteCellSeparator"/>
+ <!-- menu label is set in InitTableMenu -->
+ <menuitem id="menu_JoinTableCells"
+ label="&tableJoinCells.label;"
+ accesskey="&tableJoinCells.accesskey;"
+ command="cmd_JoinTableCells"/>
+ <menuitem id="menu_SlitTableCell"
+ label="&tableSplitCell.label;"
+ accesskey="&tableSplitCell.accesskey;"
+ command="cmd_SplitTableCell"/>
+ <menuitem id="menu_ConvertToTable"
+ label="&convertToTable.label;"
+ accesskey="&convertToTable.accesskey;"
+ command="cmd_ConvertToTable"/>
+ <menuseparator id="tableDeleteMenuAfterConvertToTableSeparator"/>
+ <menuitem id="menu_TableOrCellColor"
+ label="&tableOrCellColor.label;"
+ accesskey="&tableOrCellColor.accesskey;"
+ command="cmd_TableOrCellColor"/>
+ <menuitem id="menu_tableProperties"
+ label="&tableProperties.label;"
+ accesskey="&tableProperties.accesskey;"
+ command="cmd_editTable"/>
+ </menupopup>
+ </menu>
+
+ <toolbarbutton id="AlignPopupButton"
+ class="formatting-button"
+ label="&AlignPopupButton.label;"
+ removable="true"
+ tooltiptext="&AlignPopupButton.tooltip;"
+ type="menu"
+ observes="cmd_align">
+ <menupopup id="AlignPopup">
+ <menuitem id="AlignLeftItem" class="menuitem-iconic" label="&alignLeft.label;"
+ oncommand="doStatefulCommand('cmd_align', 'left')"
+ tooltiptext="&alignLeftButton.tooltip;" />
+ <menuitem id="AlignCenterItem" class="menuitem-iconic" label="&alignCenter.label;"
+ oncommand="doStatefulCommand('cmd_align', 'center')"
+ tooltiptext="&alignCenterButton.tooltip;" />
+ <menuitem id="AlignRightItem" class="menuitem-iconic" label="&alignRight.label;"
+ oncommand="doStatefulCommand('cmd_align', 'right')"
+ tooltiptext="&alignRightButton.tooltip;" />
+ <menuitem id="AlignJustifyItem" class="menuitem-iconic" label="&alignJustify.label;"
+ oncommand="doStatefulCommand('cmd_align', 'justify')"
+ tooltiptext="&alignJustifyButton.tooltip;"/>
+ </menupopup>
+ </toolbarbutton>
+
+ <toolbarbutton id="InsertPopupButton"
+ class="formatting-button"
+ label="&InsertPopupButton.label;"
+ removable="true"
+ tooltiptext="&InsertPopupButton.tooltip;"
+ type="menu"
+ observes="cmd_renderedHTMLEnabler">
+ <menupopup id="InsertPopup">
+ <menuitem id="InsertLinkItem"
+ class="menuitem-iconic"
+ command="cmd_link"
+ label="&linkToolbarCmd.label;"
+ tooltiptext="&linkToolbarCmd.tooltip;"/>
+ <menuitem id="InsertAnchorItem"
+ class="menuitem-iconic"
+ command="cmd_anchor"
+ label="&anchorToolbarCmd.label;"
+ tooltiptext="&anchorToolbarCmd.tooltip;"/>
+ <menuitem id="InsertImageItem"
+ class="menuitem-iconic"
+ command="cmd_image"
+ label="&imageToolbarCmd.label;"
+ tooltiptext="&imageToolbarCmd.tooltip;"/>
+ <menuitem id="InsertHRuleItem"
+ class="menuitem-iconic"
+ command="cmd_hline"
+ label="&hruleToolbarCmd.label;"
+ tooltiptext="&hruleToolbarCmd.tooltip;"/>
+ <menuitem id="InsertTableItem"
+ class="menuitem-iconic"
+ command="cmd_table"
+ label="&tableToolbarCmd.label;"
+ tooltiptext="&tableToolbarCmd.tooltip;"/>
+ </menupopup>
+ </toolbarbutton>
+
+ <!-- Editor toolbar items -->
+ <!-- note that we override the submenu item label "Blank Window" with "New" used for the menu -->
+ <toolbarbutton id="cutButton" class="toolbarbutton-1"
+ removable="true"
+ command="cmd_cut"
+ tooltiptext="&cutToolbarCmd.tooltip;"/>
+ <toolbarbutton id="copyButton" class="toolbarbutton-1"
+ removable="true"
+ command="cmd_copy"
+ tooltiptext="&copyToolbarCmd.tooltip;"/>
+ <toolbarbutton id="pasteButton" class="toolbarbutton-1"
+ removable="true"
+ command="cmd_paste"
+ tooltiptext="&pasteToolbarCmd.tooltip;"/>
+
+ <toolbarbutton id="findButton"
+ class="toolbarbutton-1"
+ label="&findToolbarCmd.label;"
+ removable="true"
+ command="cmd_find"
+ tooltiptext="&findToolbarCmd.tooltip;"/>
+ <toolbarbutton id="spellingButton"
+ class="toolbarbutton-1"
+ label="&spellToolbarCmd.label;"
+ removable="true"
+ command="cmd_spelling"
+ tooltiptext="&spellToolbarCmd.tooltip;"/>
+ <toolbarbutton id="imageButton"
+ class="toolbarbutton-1"
+ removable="true"
+ label="&imageToolbarCmd.label;"
+ command="cmd_image"
+ tooltiptext="&imageToolbarCmd.tooltip;"/>
+ <toolbarbutton id="hlineButton"
+ class="toolbarbutton-1"
+ removable="true"
+ label="&hruleToolbarCmd.label;"
+ command="cmd_hline"
+ tooltiptext="&hruleToolbarCmd.tooltip;"/>
+ <toolbarbutton id="tableButton"
+ class="toolbarbutton-1"
+ removable="true"
+ label="&tableToolbarCmd.label;"
+ command="cmd_table"
+ tooltiptext="&tableToolbarCmd.tooltip;"/>
+ <toolbarbutton id="linkButton"
+ class="toolbarbutton-1"
+ removable="true"
+ label="&linkToolbarCmd.label;"
+ command="cmd_link"
+ tooltiptext="&linkToolbarCmd.tooltip;"/>
+ <toolbarbutton id="namedAnchorButton"
+ class="toolbarbutton-1"
+ removable="true"
+ label="&anchorToolbarCmd.label;"
+ command="cmd_anchor"
+ tooltiptext="&anchorToolbarCmd.tooltip;"/>
+
+ <!-- Formatting toolbar items. "value" are HTML tagnames, don't translate -->
+<toolbaritem id="paragraph-select-container"
+ class="formatting-button"
+ title="&ParagraphSelect.title;"
+ align="center"
+ removable="true"
+ tooltiptext="&ParagraphSelect.tooltip;"
+ observes="cmd_renderedHTMLEnabler">
+ <menulist id="ParagraphSelect"
+ class="toolbar-focustarget"
+ crop="right">
+ <observes element="paragraph-select-container" attribute="disabled"/>
+ <observes element="cmd_paragraphState" attribute="state" onbroadcast="onParagraphFormatChange(this.parentNode, 'cmd_paragraphState')"/>
+ <menupopup id="ParagraphPopup" oncommand="doStatefulCommand('cmd_paragraphState', event.target.value)">
+ <menuitem id="toolbarmenu_bodyText" label="&bodyTextCmd.label;" value=""/>
+ <menuitem id="toolbarmenu_paragraph" label="&paragraphParagraphCmd.label;" value="p"/>
+ <menuitem id="toolbarmenu_h1" label="&heading1Cmd.label;" value="h1"/>
+ <menuitem id="toolbarmenu_h2" label="&heading2Cmd.label;" value="h2"/>
+ <menuitem id="toolbarmenu_h3" label="&heading3Cmd.label;" value="h3"/>
+ <menuitem id="toolbarmenu_h4" label="&heading4Cmd.label;" value="h4"/>
+ <menuitem id="toolbarmenu_h5" label="&heading5Cmd.label;" value="h5"/>
+ <menuitem id="toolbarmenu_h6" label="&heading6Cmd.label;" value="h6"/>
+ <menuitem id="toolbarmenu_address" label="&paragraphAddressCmd.label;" value="address"/>
+ <menuitem id="toolbarmenu_pre" label="&paragraphPreformatCmd.label;" value="pre"/>
+ </menupopup>
+ </menulist>
+</toolbaritem>
+
+ <!-- "value" are HTML tagnames, don't translate -->
+<toolbaritem id="font-face-select-container"
+ class="formatting-button"
+ title="&FontFaceSelect.title;"
+ align="center"
+ removable="true"
+ tooltiptext="&FontFaceSelect.tooltip;"
+ observes="cmd_renderedHTMLEnabler">
+ <menulist id="FontFaceSelect"
+ class="toolbar-focustarget"
+ crop="center"
+ sizetopopup="pref">
+ <observes element="font-face-select-container" attribute="disabled"/>
+ <observes element="cmd_fontFace" attribute="state" onbroadcast="onFontFaceChange(this.parentNode, 'cmd_fontFace')"/>
+ <menupopup id="FontFacePopup" oncommand="doStatefulCommand('cmd_fontFace', event.target.value)">
+ <menuitem id="toolbarmenu_fontVarWidth" label="&fontVarWidth.label;" value=""/>
+ <menuitem id="toolbarmenu_fontFixedWidth" label="&fontFixedWidth.label;" value="tt"/>
+ <menuseparator id="toolbarmenuAfterGenericFontsSeparator"/>
+ <menuitem id="toolbarmenu_fontHelvetica" label="&fontHelvetica.label;"
+ value="Helvetica, Arial, sans-serif"
+ value_parsed="helvetica,arial,sans-serif"/>
+ <menuitem id="toolbarmenu_fontTimes" label="&fontTimes.label;"
+ value="Times New Roman, Times, serif"
+ value_parsed="times new roman,times,serif"/>
+ <menuitem id="toolbarmenu_fontCourier" label="&fontCourier.label;"
+ value="Courier New, Courier, monospace"
+ value_parsed="courier new,courier,monospace"/>
+ <menuseparator id="toolbarmenuAfterDefaultFontsSeparator"
+ class="fontFaceMenuAfterDefaultFonts"/>
+ <menuseparator id="toolbarmenuAfterUsedFontsSeparator"
+ class="fontFaceMenuAfterUsedFonts"
+ hidden="true"/>
+ <!-- Local font face items added here by initLocalFontFaceMenu() -->
+ </menupopup>
+ </menulist>
+</toolbaritem>
+
+<toolbaritem id="font-size-select-container"
+ class="formatting-button"
+ title="&FontSizeSelect.title;"
+ align="center"
+ removable="true"
+ tooltiptext="&FontSizeSelect.tooltip;">
+ <menulist id="FontSizeSelect"
+ class="toolbar-focustarget"
+ crop="right"
+ oncommand="EditorSelectFontSize();">
+ <observes element="font-size-select-container" attribute="disabled"/>
+ <observes element="cmd_fontSize" attribute="state" onbroadcast="onFontSizeChange(this.parentNode, 'cmd_fontSize')"/>
+ <menupopup id="FontSizePopup">
+ <menuitem id="toobarmenu_fontSize_x-small" label="&size-tinyCmd.label;"/>
+ <menuitem id="toobarmenu_fontSize_small" label="&size-smallCmd.label;"/>
+ <menuitem id="toobarmenu_fontSize_medium" label="&size-mediumCmd.label;"/>
+ <menuitem id="toobarmenu_fontSize_large" label="&size-largeCmd.label;"/>
+ <menuitem id="toobarmenu_fontSize_x-large" label="&size-extraLargeCmd.label;"/>
+ <menuitem id="toobarmenu_fontSize_xx-large" label="&size-hugeCmd.label;"/>
+ </menupopup>
+ </menulist>
+</toolbaritem>
+
+<toolbaritem id="color-buttons-container"
+ align="center"
+ title="&colorButtons.title;"
+ removable="true"
+ class="formatting-button">
+ <stack id="ColorButtons" align="center">
+ <observes element="cmd_fontColor" attribute="state" onbroadcast="onFontColorChange()"/>
+ <observes element="cmd_backgroundColor" attribute="state" onbroadcast="onBackgroundColorChange()"/>
+ <box class="color-button" id="BackgroundColorButton"
+ onclick="EditorSelectColor('', event);"
+ tooltiptext="&BackgroundColorButton.tooltip;"/>
+ <box class="color-button" id="TextColorButton"
+ onclick="EditorSelectColor('Text', event);"
+ tooltiptext="&TextColorButton.tooltip;"/>
+ </stack>
+</toolbaritem>
+ <toolbarbutton id="HighlightColorButton"
+ class="formatting-button"
+ label="&HighlightColorButton.label;"
+ removable="true"
+ tooltiptext="&HighlightColorButton.tooltip;"
+ command="cmd_highlight">
+ <observes element="cmd_highlight" attribute="state" onbroadcast="onHighlightColorChange()"/>
+ <observes element="cmd_highlight" attribute="collapsed"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="AbsoluteFontSizeButton"
+ class="formatting-button"
+ label="&absoluteFontSize.label;"
+ removable="true"
+ tooltiptext="&absoluteFontSizeToolbarCmd.tooltip;"
+ type="menu"
+ observes="cmd_renderedHTMLEnabler">
+ <menupopup id="AbsoluteFontSizeButtonPopup"
+ onpopupshowing="initFontSizeMenu(this, false);">
+ <menuitem id="toobarmenu_fontSize_x-small"
+ label="&size-tinyCmd.label;"
+ type="radio" name="1"
+ oncommand="EditorSetFontSize('x-small')"/>
+ <menuitem id="toobarmenu_fontSize_small"
+ label="&size-smallCmd.label;"
+ type="radio" name="1"
+ oncommand="EditorSetFontSize('small')"/>
+ <menuitem id="toobarmenu_fontSize_medium"
+ label="&size-mediumCmd.label;"
+ type="radio" name="1"
+ oncommand="EditorSetFontSize('medium')"/>
+ <menuitem id="toobarmenu_fontSize_large"
+ label="&size-largeCmd.label;"
+ type="radio" name="1"
+ oncommand="EditorSetFontSize('large')"/>
+ <menuitem id="toobarmenu_fontSize_x-large"
+ label="&size-extraLargeCmd.label;"
+ type="radio" name="1"
+ oncommand="EditorSetFontSize('x-large')"/>
+ <menuitem id="toobarmenu_fontSize_xx-large"
+ label="&size-hugeCmd.label;"
+ type="radio" name="1"
+ oncommand="EditorSetFontSize('xx-large')"/>
+ <!-- Enable if required for SeaMonkey.
+ <menuitem id="toobarmenu_fontSize_smallBigInfo"
+ type="checkbox" name="2" disabled="true" hidden="true"/>
+ -->
+ </menupopup>
+ </toolbarbutton>
+ <toolbarbutton id="DecreaseFontSizeButton"
+ class="formatting-button"
+ label="&smaller.label;"
+ removable="true"
+ tooltiptext="&decreaseFontSizeToolbarCmd.tooltip;"
+ command="cmd_decreaseFontStep"/>
+ <toolbarbutton id="IncreaseFontSizeButton"
+ class="formatting-button"
+ label="&larger.label;"
+ removable="true"
+ tooltiptext="&increaseFontSizeToolbarCmd.tooltip;"
+ command="cmd_increaseFontStep"/>
+ <toolbarbutton id="boldButton"
+ class="formatting-button"
+ label="&bold.label;"
+ removable="true"
+ tooltiptext="&boldToolbarCmd.tooltip;"
+ type="checkbox"
+ autoCheck="false"
+ command="cmd_bold">
+ <observes element="cmd_bold" type="checkbox" attribute="state" onbroadcast="onButtonUpdate(this.parentNode, 'cmd_bold')"/>
+ </toolbarbutton>
+ <toolbarbutton id="italicButton"
+ class="formatting-button"
+ label="&italic.label;"
+ removable="true"
+ tooltiptext="&italicToolbarCmd.tooltip;"
+ type="checkbox"
+ autoCheck="false"
+ command="cmd_italic">
+ <observes element="cmd_italic" attribute="state" onbroadcast="onButtonUpdate(this.parentNode, 'cmd_italic')"/>
+ </toolbarbutton>
+ <toolbarbutton id="underlineButton"
+ class="formatting-button"
+ label="&underline.label;"
+ removable="true"
+ tooltiptext="&underlineToolbarCmd.tooltip;"
+ type="checkbox"
+ autoCheck="false"
+ command="cmd_underline">
+ <observes element="cmd_underline" attribute="state" onbroadcast="onButtonUpdate(this.parentNode, 'cmd_underline')"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="ulButton"
+ class="formatting-button"
+ label="&bullets.label;"
+ removable="true"
+ tooltiptext="&bulletListToolbarCmd.tooltip;"
+ type="radio"
+ group="lists"
+ autoCheck="false"
+ command="cmd_ul">
+ <observes element="cmd_ul" attribute="state" onbroadcast="onButtonUpdate(this.parentNode, 'cmd_ul')"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="olButton"
+ class="formatting-button"
+ label="&numbers.label;"
+ removable="true"
+ tooltiptext="&numberListToolbarCmd.tooltip;"
+ type="radio"
+ group="lists"
+ autoCheck="false"
+ command="cmd_ol">
+ <observes element="cmd_ol" attribute="state" onbroadcast="onButtonUpdate(this.parentNode, 'cmd_ol')"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="outdentButton"
+ class="formatting-button"
+ label="&outdent.label;"
+ removable="true"
+ tooltiptext="&outdentToolbarCmd.tooltip;"
+ command="cmd_outdent"/>
+ <toolbarbutton id="indentButton"
+ class="formatting-button"
+ label="&indent.label;"
+ removable="true"
+ tooltiptext="&indentToolbarCmd.tooltip;"
+ command="cmd_indent"/>
+
+ <!-- alignment buttons -->
+ <toolbarbutton id="align-left-button"
+ class="formatting-button"
+ label="&alignLeftButton.label;"
+ removable="true"
+ tooltiptext="&alignLeftButton.tooltip;"
+ type="radio"
+ group="align"
+ autoCheck="false"
+ oncommand="doStatefulCommand('cmd_align', 'left')">
+ <observes element="cmd_align" attribute="state"
+ onbroadcast="onStateButtonUpdate(this.parentNode, 'cmd_align', 'left')" />
+ </toolbarbutton>
+ <toolbarbutton id="align-center-button"
+ class="formatting-button"
+ label="&alignCenterButton.label;"
+ removable="true"
+ tooltiptext="&alignCenterButton.tooltip;"
+ type="radio"
+ group="align"
+ autoCheck="false"
+ oncommand="doStatefulCommand('cmd_align', 'center')">
+ <observes element="cmd_align" attribute="state"
+ onbroadcast="onStateButtonUpdate(this.parentNode, 'cmd_align', 'center')"/>
+ </toolbarbutton>
+ <toolbarbutton id="align-right-button"
+ class="formatting-button"
+ label="&alignRightButton.label;"
+ removable="true"
+ tooltiptext="&alignRightButton.tooltip;"
+ type="radio"
+ group="align"
+ autoCheck="false"
+ oncommand="doStatefulCommand('cmd_align', 'right')">
+ <observes element="cmd_align" attribute="state"
+ onbroadcast="onStateButtonUpdate(this.parentNode, 'cmd_align', 'right')"/>
+ </toolbarbutton>
+ <toolbarbutton id="align-justify-button"
+ class="formatting-button"
+ label="&alignJustifyButton.label;"
+ removable="true"
+ tooltiptext="&alignJustifyButton.tooltip;"
+ type="radio"
+ group="align"
+ autoCheck="false"
+ oncommand="doStatefulCommand('cmd_align', 'justify')">
+ <observes element="cmd_align" attribute="state"
+ onbroadcast="onStateButtonUpdate(this.parentNode, 'cmd_align', 'justify')"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="absolutePositionButton"
+ class="formatting-button"
+ label="&absolutePosition.label;"
+ removable="true"
+ tooltiptext="&layer.tooltip;"
+ type="checkbox"
+ command="cmd_absPos">
+ <observes element="cmd_absPos" attribute="state" onbroadcast="onStateButtonUpdate(this.parentNode, 'cmd_absPos', 'absolute')"/>
+ </toolbarbutton>
+ <toolbarbutton id="decreaseZIndexButton"
+ class="formatting-button"
+ label="&decreaseZIndex.label;"
+ removable="true"
+ tooltiptext="&layerSendToBack.tooltip;"
+ command="cmd_decreaseZIndex"/>
+ <toolbarbutton id="increaseZIndexButton"
+ class="formatting-button"
+ label="&increaseZIndex.label;"
+ removable="true"
+ tooltiptext="&layerBringToFront.tooltip;"
+ command="cmd_increaseZIndex"/>
+
+ <!-- smiley menu -->
+ <toolbarbutton id="smileButtonMenu"
+ class="formatting-button"
+ label="&SmileButton.label;"
+ removable="true"
+ tooltiptext="&SmileButton.tooltip;"
+ type="menu"
+ observes="cmd_smiley">
+ <menupopup id="smilyPopup">
+ <menuitem class="smiley insert-smile menuitem-iconic"
+ label="&smiley1Cmd.label;"
+ tooltiptext="&smiley1Cmd.tooltip;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-)');"/>
+ <menuitem class="smiley insert-frown menuitem-iconic"
+ label="&smiley2Cmd.label;"
+ tooltiptext="&smiley2Cmd.tooltip;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-(');"/>
+ <menuitem class="smiley insert-wink menuitem-iconic"
+ label="&smiley3Cmd.label;"
+ tooltiptext="&smiley3Cmd.tooltip;"
+ oncommand="doStatefulCommand('cmd_smiley', ';-)');"/>
+ <menuitem class="smiley insert-tongue menuitem-iconic"
+ label="&smiley4Cmd.label;"
+ tooltiptext="&smiley4Cmd.tooltip;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-P');"/>
+ <menuitem class="smiley insert-laughing menuitem-iconic"
+ label="&smiley5Cmd.label;"
+ tooltiptext="&smiley5Cmd.tooltip;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-D');"/>
+ <menuitem class="smiley insert-embarrassed menuitem-iconic"
+ label="&smiley6Cmd.label;"
+ tooltiptext="&smiley6Cmd.tooltip;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-[');"/>
+ <menuitem class="smiley insert-undecided menuitem-iconic"
+ label="&smiley7Cmd.label;"
+ tooltiptext="&smiley7Cmd.tooltip;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-\\');"/>
+ <menuitem class="smiley insert-surprise menuitem-iconic"
+ label="&smiley8Cmd.label;"
+ tooltiptext="&smiley8Cmd.tooltip;"
+ oncommand="doStatefulCommand('cmd_smiley', '=-O');"/>
+ <menuitem class="smiley insert-kiss menuitem-iconic"
+ label="&smiley9Cmd.label;"
+ tooltiptext="&smiley9Cmd.tooltip;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-*');"/>
+ <menuitem class="smiley insert-yell menuitem-iconic"
+ label="&smiley10Cmd.label;"
+ tooltiptext="&smiley10Cmd.tooltip;"
+ oncommand="doStatefulCommand('cmd_smiley', '>:o');"/>
+ <menuitem class="smiley insert-cool menuitem-iconic"
+ label="&smiley11Cmd.label;"
+ tooltiptext="&smiley11Cmd.tooltip;"
+ oncommand="doStatefulCommand('cmd_smiley', '8-)');"/>
+ <menuitem class="smiley insert-money menuitem-iconic"
+ label="&smiley12Cmd.label;"
+ tooltiptext="&smiley12Cmd.tooltip;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-$');"/>
+ <menuitem class="smiley insert-foot menuitem-iconic"
+ label="&smiley13Cmd.label;"
+ tooltiptext="&smiley13Cmd.tooltip;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-!');"/>
+ <menuitem class="smiley insert-innocent menuitem-iconic"
+ label="&smiley14Cmd.label;"
+ tooltiptext="&smiley14Cmd.tooltip;"
+ oncommand="doStatefulCommand('cmd_smiley', 'O:-)');"/>
+ <menuitem class="smiley insert-cry menuitem-iconic"
+ label="&smiley15Cmd.label;"
+ tooltiptext="&smiley15Cmd.tooltip;"
+ oncommand="doStatefulCommand('cmd_smiley', ':\'(');"/>
+ <menuitem class="smiley insert-sealed menuitem-iconic"
+ label="&smiley16Cmd.label;"
+ tooltiptext="&smiley16Cmd.tooltip;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-X');"/>
+ </menupopup>
+ </toolbarbutton>
+</overlay>
diff --git a/comm/suite/editor/base/content/editorTasksOverlay.xhtml b/comm/suite/editor/base/content/editorTasksOverlay.xhtml
new file mode 100644
index 0000000000..d3fcb2d4b2
--- /dev/null
+++ b/comm/suite/editor/base/content/editorTasksOverlay.xhtml
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay SYSTEM "chrome://communicator/locale/tasksOverlay.dtd">
+
+<overlay id="editorTasksOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <script src="chrome://editor/content/editorApplicationOverlay.js"/>
+
+ <keyset id="tasksKeys">
+ <key id="key_editor" key="&editorCmd.commandkey;" command="Tasks:Editor" modifiers="accel"/>
+ </keyset>
+
+ <commandset id="tasksCommands">
+ <command id="Tasks:Editor" oncommand="toEditor();"/>
+ </commandset>
+
+ <hbox id="component-bar" class="statusbarpanel">
+ <toolbarbutton class="taskbutton" id="mini-comp" command="Tasks:Editor"
+ tooltiptext="&taskComposer.tooltip;" insertafter="mini-nav"/>
+ </hbox>
+
+ <menupopup id="windowPopup">
+ <menuitem label="&editorCmd.label;" accesskey="&editorCmd.accesskey;" key="key_editor" command="Tasks:Editor" id="tasksMenuEditor" insertafter="IMMenuItem,tasksMenuNavigator" class="menuitem-iconic icon-composer16 menu-iconic"/>
+ </menupopup>
+
+</overlay>
+
diff --git a/comm/suite/editor/base/content/editorUtilities.js b/comm/suite/editor/base/content/editorUtilities.js
new file mode 100644
index 0000000000..ea06446e45
--- /dev/null
+++ b/comm/suite/editor/base/content/editorUtilities.js
@@ -0,0 +1,1014 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from editor.js */
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+// Each editor window must include this file
+// Variables shared by all dialogs:
+
+// Object to attach commonly-used widgets (all dialogs should use this)
+var gDialog = {};
+
+var kOutputEncodeBasicEntities =
+ Ci.nsIDocumentEncoder.OutputEncodeBasicEntities;
+var kOutputEncodeHTMLEntities = Ci.nsIDocumentEncoder.OutputEncodeHTMLEntities;
+var kOutputEncodeLatin1Entities =
+ Ci.nsIDocumentEncoder.OutputEncodeLatin1Entities;
+var kOutputEncodeW3CEntities = Ci.nsIDocumentEncoder.OutputEncodeW3CEntities;
+var kOutputFormatted = Ci.nsIDocumentEncoder.OutputFormatted;
+var kOutputLFLineBreak = Ci.nsIDocumentEncoder.OutputLFLineBreak;
+var kOutputSelectionOnly = Ci.nsIDocumentEncoder.OutputSelectionOnly;
+var kOutputWrap = Ci.nsIDocumentEncoder.OutputWrap;
+
+var gStringBundle;
+var gFilePickerDirectory;
+
+/** *********** Message dialogs ***************/
+
+// Optional: Caller may supply text to substitute for "Ok" and/or "Cancel"
+function ConfirmWithTitle(title, message, okButtonText, cancelButtonText) {
+ let okFlag = okButtonText
+ ? Services.prompt.BUTTON_TITLE_IS_STRING
+ : Services.prompt.BUTTON_TITLE_OK;
+ let cancelFlag = cancelButtonText
+ ? Services.prompt.BUTTON_TITLE_IS_STRING
+ : Services.prompt.BUTTON_TITLE_CANCEL;
+
+ return (
+ Services.prompt.confirmEx(
+ window,
+ title,
+ message,
+ okFlag * Services.prompt.BUTTON_POS_0 +
+ cancelFlag * Services.prompt.BUTTON_POS_1,
+ okButtonText,
+ cancelButtonText,
+ null,
+ null,
+ { value: 0 }
+ ) == 0
+ );
+}
+
+/** *********** String Utilities ***************/
+
+function GetString(name) {
+ if (!gStringBundle) {
+ try {
+ gStringBundle = Services.strings.createBundle(
+ "chrome://editor/locale/editor.properties"
+ );
+ } catch (ex) {}
+ }
+ if (gStringBundle) {
+ try {
+ return gStringBundle.GetStringFromName(name);
+ } catch (e) {}
+ }
+ return null;
+}
+
+function GetFormattedString(aName, aVal) {
+ if (!gStringBundle) {
+ try {
+ gStringBundle = Services.strings.createBundle(
+ "chrome://editor/locale/editor.properties"
+ );
+ } catch (ex) {}
+ }
+ if (gStringBundle) {
+ try {
+ return gStringBundle.formatStringFromName(aName, [aVal]);
+ } catch (e) {}
+ }
+ return null;
+}
+
+function TrimStringLeft(string) {
+ if (!string) {
+ return "";
+ }
+ return string.trimLeft();
+}
+
+function TrimStringRight(string) {
+ if (!string) {
+ return "";
+ }
+ return string.trimRight();
+}
+
+// Remove whitespace from both ends of a string
+function TrimString(string) {
+ if (!string) {
+ return "";
+ }
+ return string.trim();
+}
+
+function TruncateStringAtWordEnd(string, maxLength, addEllipses) {
+ // Return empty if string is null, undefined, or the empty string
+ if (!string) {
+ return "";
+ }
+
+ // We assume they probably don't want whitespace at the beginning
+ string = string.trimLeft();
+ if (string.length <= maxLength) {
+ return string;
+ }
+
+ // We need to truncate the string to maxLength or fewer chars
+ if (addEllipses) {
+ maxLength -= 3;
+ }
+ string = string.replace(RegExp("(.{0," + maxLength + "})\\s.*"), "$1");
+
+ if (string.length > maxLength) {
+ string = string.slice(0, maxLength);
+ }
+
+ if (addEllipses) {
+ string += "...";
+ }
+ return string;
+}
+
+// Replace all whitespace characters with supplied character
+// E.g.: Use charReplace = " ", to "unwrap" the string by removing line-end chars
+// Use charReplace = "_" when you don't want spaces (like in a URL)
+function ReplaceWhitespace(string, charReplace) {
+ return string.trim().replace(/\s+/g, charReplace);
+}
+
+// Replace whitespace with "_" and allow only HTML CDATA
+// characters: "a"-"z","A"-"Z","0"-"9", "_", ":", "-", ".",
+// and characters above ASCII 127
+function ConvertToCDATAString(string) {
+ return string
+ .replace(/\s+/g, "_")
+ .replace(/[^a-zA-Z0-9_\.\-\:\u0080-\uFFFF]+/g, "");
+}
+
+function GetSelectionAsText() {
+ try {
+ return GetCurrentEditor().outputToString(
+ "text/plain",
+ kOutputSelectionOnly
+ );
+ } catch (e) {}
+
+ return "";
+}
+
+/** *********** Get Current Editor and associated interfaces or info ***************/
+const nsIHTMLEditor = Ci.nsIHTMLEditor;
+const nsITableEditor = Ci.nsITableEditor;
+const nsIEditorStyleSheets = Ci.nsIEditorStyleSheets;
+const nsIEditingSession = Ci.nsIEditingSession;
+
+function GetCurrentEditor() {
+ // Get the active editor from the <editor> tag
+ // XXX This will probably change if we support > 1 editor in main Composer window
+ // (e.g. a plaintext editor for HTMLSource)
+
+ // For dialogs: Search up parent chain to find top window with editor
+ var editor;
+ try {
+ var editorElement = GetCurrentEditorElement();
+ editor = editorElement.getEditor(editorElement.contentWindow);
+
+ // Do QIs now so editor users won't have to figure out which interface to use
+ // Using "instanceof" does the QI for us.
+ editor instanceof Ci.nsIHTMLEditor;
+ } catch (e) {
+ dump(e) + "\n";
+ }
+
+ return editor;
+}
+
+function GetCurrentTableEditor() {
+ var editor = GetCurrentEditor();
+ return editor && editor instanceof nsITableEditor ? editor : null;
+}
+
+function GetCurrentEditorElement() {
+ var tmpWindow = window;
+
+ do {
+ // Get the <editor> element(s)
+ let editorItem = tmpWindow.document.querySelector("editor");
+
+ // This will change if we support > 1 editor element
+ if (editorItem) {
+ return editorItem;
+ }
+
+ tmpWindow = tmpWindow.opener;
+ } while (tmpWindow);
+
+ return null;
+}
+
+function GetCurrentCommandManager() {
+ try {
+ return GetCurrentEditorElement().commandManager;
+ } catch (e) {
+ dump(e) + "\n";
+ }
+
+ return null;
+}
+
+function GetCurrentEditorType() {
+ try {
+ return GetCurrentEditorElement().editortype;
+ } catch (e) {
+ dump(e) + "\n";
+ }
+
+ return "";
+}
+
+function IsHTMLEditor() {
+ // We don't have an editorElement, just return false
+ if (!GetCurrentEditorElement()) {
+ return false;
+ }
+
+ var editortype = GetCurrentEditorType();
+ switch (editortype) {
+ case "html":
+ case "htmlmail":
+ return true;
+
+ case "text":
+ case "textmail":
+ return false;
+
+ default:
+ dump("INVALID EDITOR TYPE: " + editortype + "\n");
+ break;
+ }
+ return false;
+}
+
+function PageIsEmptyAndUntouched() {
+ return IsDocumentEmpty() && !IsDocumentModified() && !IsHTMLSourceChanged();
+}
+
+function IsInHTMLSourceMode() {
+ return gEditorDisplayMode == kDisplayModeSource;
+}
+
+function IsInPreviewMode() {
+ return gEditorDisplayMode == kDisplayModePreview;
+}
+
+// are we editing HTML (i.e. neither in HTML source mode, nor editing a text file)
+function IsEditingRenderedHTML() {
+ return IsHTMLEditor() && !IsInHTMLSourceMode();
+}
+
+function IsWebComposer() {
+ return document.documentElement.id == "editorWindow";
+}
+
+function IsDocumentEditable() {
+ try {
+ return GetCurrentEditor().isDocumentEditable;
+ } catch (e) {}
+ return false;
+}
+
+function IsDocumentEmpty() {
+ try {
+ return GetCurrentEditor().documentIsEmpty;
+ } catch (e) {}
+ return false;
+}
+
+function IsDocumentModified() {
+ try {
+ return GetCurrentEditor().documentModified;
+ } catch (e) {}
+ return false;
+}
+
+function IsHTMLSourceChanged() {
+ // gSourceTextEditor will not be defined if we're just a text editor.
+ return gSourceTextEditor ? gSourceTextEditor.documentModified : false;
+}
+
+function newCommandParams() {
+ try {
+ return Cu.createCommandParams();
+ } catch (e) {
+ dump("error thrown in newCommandParams: " + e + "\n");
+ }
+ return null;
+}
+
+/** *********** General editing command utilities ***************/
+
+function GetDocumentTitle() {
+ try {
+ return GetCurrentEditorElement().contentDocument.title;
+ } catch (e) {}
+
+ return "";
+}
+
+function SetDocumentTitle(title) {
+ try {
+ GetCurrentEditorElement().contentDocument.title = title;
+
+ // Update window title (doesn't work if called from a dialog)
+ if ("UpdateWindowTitle" in window) {
+ window.UpdateWindowTitle();
+ }
+ } catch (e) {}
+}
+
+function EditorGetTextProperty(
+ property,
+ attribute,
+ value,
+ firstHas,
+ anyHas,
+ allHas
+) {
+ try {
+ return GetCurrentEditor().getInlinePropertyWithAttrValue(
+ property,
+ attribute,
+ value,
+ firstHas,
+ anyHas,
+ allHas
+ );
+ } catch (e) {}
+}
+
+function EditorSetTextProperty(property, attribute, value) {
+ try {
+ GetCurrentEditor().setInlineProperty(property, attribute, value);
+ if ("gContentWindow" in window) {
+ window.gContentWindow.focus();
+ }
+ } catch (e) {}
+}
+
+function EditorRemoveTextProperty(property, attribute) {
+ try {
+ GetCurrentEditor().removeInlineProperty(property, attribute);
+ if ("gContentWindow" in window) {
+ window.gContentWindow.focus();
+ }
+ } catch (e) {}
+}
+
+/** *********** Element enbabling/disabling ***************/
+
+// this function takes an elementID and a flag
+// if the element can be found by ID, then it is either enabled (by removing "disabled" attr)
+// or disabled (setAttribute) as specified in the "doEnable" parameter
+function SetElementEnabledById(elementID, doEnable) {
+ SetElementEnabled(document.getElementById(elementID), doEnable);
+}
+
+function SetElementEnabled(element, doEnable) {
+ if (element) {
+ if (doEnable) {
+ element.removeAttribute("disabled");
+ } else {
+ element.setAttribute("disabled", "true");
+ }
+ } else {
+ dump("Element not found in SetElementEnabled\n");
+ }
+}
+
+/** *********** Services / Prefs ***************/
+
+function GetFileProtocolHandler() {
+ let handler = Services.io.getProtocolHandler("file");
+ return handler.QueryInterface(Ci.nsIFileProtocolHandler);
+}
+
+function SetStringPref(aPrefName, aPrefValue) {
+ try {
+ Services.prefs.setStringPref(aPrefName, aPrefValue);
+ } catch (e) {}
+}
+
+// Set initial directory for a filepicker from URLs saved in prefs
+function SetFilePickerDirectory(filePicker, fileType) {
+ if (filePicker) {
+ try {
+ // Save current directory so we can reset it in SaveFilePickerDirectory
+ gFilePickerDirectory = filePicker.displayDirectory;
+
+ let location = Services.prefs.getComplexValue(
+ "editor.lastFileLocation." + fileType,
+ Ci.nsIFile
+ );
+ if (location) {
+ filePicker.displayDirectory = location;
+ }
+ } catch (e) {}
+ }
+}
+
+// Save the directory of the selected file to prefs
+function SaveFilePickerDirectory(filePicker, fileType) {
+ if (filePicker && filePicker.file) {
+ try {
+ var fileDir;
+ if (filePicker.file.parent) {
+ fileDir = filePicker.file.parent.QueryInterface(Ci.nsIFile);
+ }
+
+ Services.prefs.setComplexValue(
+ "editor.lastFileLocation." + fileType,
+ Ci.nsIFile,
+ fileDir
+ );
+
+ Services.prefs.savePrefFile(null);
+ } catch (e) {}
+ }
+
+ // Restore the directory used before SetFilePickerDirectory was called;
+ // This reduces interference with Browser and other module directory defaults
+ if (gFilePickerDirectory) {
+ filePicker.displayDirectory = gFilePickerDirectory;
+ }
+
+ gFilePickerDirectory = null;
+}
+
+function GetDefaultBrowserColors() {
+ var colors = {
+ TextColor: 0,
+ BackgroundColor: 0,
+ LinkColor: 0,
+ ActiveLinkColor: 0,
+ VisitedLinkColor: 0,
+ };
+ var useSysColors = Services.prefs.getBoolPref(
+ "browser.display.use_system_colors",
+ false
+ );
+
+ if (!useSysColors) {
+ colors.TextColor = Services.prefs.getCharPref(
+ "browser.display.foreground_color",
+ 0
+ );
+ colors.BackgroundColor = Services.prefs.getCharPref(
+ "browser.display.background_color",
+ 0
+ );
+ }
+ // Use OS colors for text and background if explicitly asked or pref is not set
+ if (!colors.TextColor) {
+ colors.TextColor = "windowtext";
+ }
+
+ if (!colors.BackgroundColor) {
+ colors.BackgroundColor = "window";
+ }
+
+ colors.LinkColor = Services.prefs.getCharPref("browser.anchor_color");
+ colors.ActiveLinkColor = Services.prefs.getCharPref("browser.active_color");
+ colors.VisitedLinkColor = Services.prefs.getCharPref("browser.visited_color");
+
+ return colors;
+}
+
+/** *********** URL handling ***************/
+
+function TextIsURI(selectedText) {
+ return (
+ selectedText &&
+ /^http:\/\/|^https:\/\/|^file:\/\/|^ftp:\/\/|^about:|^mailto:|^news:|^snews:|^telnet:|^ldap:|^ldaps:|^gopher:|^finger:|^javascript:/i.test(
+ selectedText
+ )
+ );
+}
+
+function IsUrlAboutBlank(urlString) {
+ return urlString == "about:blank";
+}
+
+function MakeRelativeUrl(url) {
+ let inputUrl = url.trim();
+ if (!inputUrl) {
+ return inputUrl;
+ }
+
+ // Get the filespec relative to current document's location
+ // NOTE: Can't do this if file isn't saved yet!
+ var docUrl = GetDocumentBaseUrl();
+ var docScheme = GetScheme(docUrl);
+
+ // Can't relativize if no doc scheme (page hasn't been saved)
+ if (!docScheme) {
+ return inputUrl;
+ }
+
+ var urlScheme = GetScheme(inputUrl);
+
+ // Do nothing if not the same scheme or url is already relativized
+ if (docScheme != urlScheme) {
+ return inputUrl;
+ }
+
+ // Host must be the same
+ var docHost = GetHost(docUrl);
+ var urlHost = GetHost(inputUrl);
+ if (docHost != urlHost) {
+ return inputUrl;
+ }
+
+ // Get just the file path part of the urls
+ // XXX Should we use GetCurrentEditor().documentCharacterSet for 2nd param ?
+ let docPath = Services.io.newURI(
+ docUrl,
+ GetCurrentEditor().documentCharacterSet
+ ).pathQueryRef;
+ let urlPath = Services.io.newURI(
+ inputUrl,
+ GetCurrentEditor().documentCharacterSet
+ ).pathQueryRef;
+
+ // We only return "urlPath", so we can convert the entire docPath for
+ // case-insensitive comparisons.
+ var doCaseInsensitive = docScheme == "file" && AppConstants.platform == "win";
+ if (doCaseInsensitive) {
+ docPath = docPath.toLowerCase();
+ }
+
+ // Get document filename before we start chopping up the docPath
+ var docFilename = GetFilename(docPath);
+
+ // Both url and doc paths now begin with "/"
+ // Look for shared dirs starting after that
+ urlPath = urlPath.slice(1);
+ docPath = docPath.slice(1);
+
+ var firstDirTest = true;
+ var nextDocSlash = 0;
+ var done = false;
+
+ // Remove all matching subdirs common to both doc and input urls
+ do {
+ nextDocSlash = docPath.indexOf("/");
+ var nextUrlSlash = urlPath.indexOf("/");
+
+ if (nextUrlSlash == -1) {
+ // We're done matching and all dirs in url
+ // what's left is the filename
+ done = true;
+
+ // Remove filename for named anchors in the same file
+ if (nextDocSlash == -1 && docFilename) {
+ var anchorIndex = urlPath.indexOf("#");
+ if (anchorIndex > 0) {
+ var urlFilename = doCaseInsensitive ? urlPath.toLowerCase() : urlPath;
+
+ if (urlFilename.startsWith(docFilename)) {
+ urlPath = urlPath.slice(anchorIndex);
+ }
+ }
+ }
+ } else if (nextDocSlash >= 0) {
+ // Test for matching subdir
+ var docDir = docPath.slice(0, nextDocSlash);
+ var urlDir = urlPath.slice(0, nextUrlSlash);
+ if (doCaseInsensitive) {
+ urlDir = urlDir.toLowerCase();
+ }
+
+ if (urlDir == docDir) {
+ // Remove matching dir+"/" from each path
+ // and continue to next dir.
+ docPath = docPath.slice(nextDocSlash + 1);
+ urlPath = urlPath.slice(nextUrlSlash + 1);
+ } else {
+ // No match, we're done.
+ done = true;
+
+ // Be sure we are on the same local drive or volume
+ // (the first "dir" in the path) because we can't
+ // relativize to different drives/volumes.
+ // UNIX doesn't have volumes, so we must not do this else
+ // the first directory will be misinterpreted as a volume name.
+ if (
+ firstDirTest &&
+ docScheme == "file" &&
+ AppConstants.platform != "unix"
+ ) {
+ return inputUrl;
+ }
+ }
+ } else {
+ // No more doc dirs left, we're done
+ done = true;
+ }
+
+ firstDirTest = false;
+ } while (!done);
+
+ // Add "../" for each dir left in docPath
+ while (nextDocSlash > 0) {
+ urlPath = "../" + urlPath;
+ nextDocSlash = docPath.indexOf("/", nextDocSlash + 1);
+ }
+ return urlPath;
+}
+
+function MakeAbsoluteUrl(url) {
+ let resultUrl = TrimString(url);
+ if (!resultUrl) {
+ return resultUrl;
+ }
+
+ // Check if URL is already absolute, i.e., it has a scheme
+ let urlScheme = GetScheme(resultUrl);
+
+ if (urlScheme) {
+ return resultUrl;
+ }
+
+ let docUrl = GetDocumentBaseUrl();
+ let docScheme = GetScheme(docUrl);
+
+ // Can't relativize if no doc scheme (page hasn't been saved)
+ if (!docScheme) {
+ return resultUrl;
+ }
+
+ // Make a URI object to use its "resolve" method
+ let absoluteUrl = resultUrl;
+ let docUri = Services.io.newURI(
+ docUrl,
+ GetCurrentEditor().documentCharacterSet
+ );
+
+ try {
+ absoluteUrl = docUri.resolve(resultUrl);
+ // This is deprecated and buggy!
+ // If used, we must make it a path for the parent directory (remove filename)
+ // absoluteUrl = IOService.resolveRelativePath(resultUrl, docUrl);
+ } catch (e) {}
+
+ return absoluteUrl;
+}
+
+// Get the HREF of the page's <base> tag or the document location
+// returns empty string if no base href and document hasn't been saved yet
+function GetDocumentBaseUrl() {
+ try {
+ var docUrl;
+
+ // if document supplies a <base> tag, use that URL instead
+ let base = GetCurrentEditor().document.querySelector("base");
+ if (base) {
+ docUrl = base.getAttribute("href");
+ }
+ if (!docUrl) {
+ docUrl = GetDocumentUrl();
+ }
+
+ if (!IsUrlAboutBlank(docUrl)) {
+ return docUrl;
+ }
+ } catch (e) {}
+ return "";
+}
+
+function GetDocumentUrl() {
+ try {
+ return GetCurrentEditor().document.URL;
+ } catch (e) {}
+ return "";
+}
+
+// Extract the scheme (e.g., 'file', 'http') from a URL string
+function GetScheme(urlspec) {
+ var resultUrl = TrimString(urlspec);
+ // Unsaved document URL has no acceptable scheme yet
+ if (!resultUrl || IsUrlAboutBlank(resultUrl)) {
+ return "";
+ }
+
+ var scheme = "";
+ try {
+ // This fails if there's no scheme
+ scheme = Services.io.extractScheme(resultUrl);
+ } catch (e) {}
+
+ return scheme ? scheme.toLowerCase() : "";
+}
+
+function GetHost(urlspec) {
+ if (!urlspec) {
+ return "";
+ }
+
+ var host = "";
+ try {
+ host = Services.io.newURI(urlspec).host;
+ } catch (e) {}
+
+ return host;
+}
+
+function GetUsername(urlspec) {
+ if (!urlspec) {
+ return "";
+ }
+
+ var username = "";
+ try {
+ username = Services.io.newURI(urlspec).username;
+ } catch (e) {}
+
+ return username;
+}
+
+function GetFilename(urlspec) {
+ if (!urlspec || IsUrlAboutBlank(urlspec)) {
+ return "";
+ }
+
+ var filename;
+
+ try {
+ let uri = Services.io.newURI(urlspec);
+ if (uri) {
+ let url = uri.QueryInterface(Ci.nsIURL);
+ if (url) {
+ filename = url.fileName;
+ }
+ }
+ } catch (e) {}
+
+ return filename ? filename : "";
+}
+
+// Return the url without username and password
+// Optional output objects return extracted username and password strings
+// This uses just string routines via nsIIOServices
+function StripUsernamePassword(urlspec, usernameObj, passwordObj) {
+ urlspec = TrimString(urlspec);
+ if (!urlspec || IsUrlAboutBlank(urlspec)) {
+ return urlspec;
+ }
+
+ if (usernameObj) {
+ usernameObj.value = "";
+ }
+ if (passwordObj) {
+ passwordObj.value = "";
+ }
+
+ // "@" must exist else we will never detect username or password
+ var atIndex = urlspec.indexOf("@");
+ if (atIndex > 0) {
+ try {
+ let uri = Services.io.newURI(urlspec);
+ let username = uri.username;
+ let password = uri.password;
+
+ if (usernameObj && username) {
+ usernameObj.value = username;
+ }
+ if (passwordObj && password) {
+ passwordObj.value = password;
+ }
+ if (username) {
+ let usernameStart = urlspec.indexOf(username);
+ if (usernameStart != -1) {
+ return urlspec.slice(0, usernameStart) + urlspec.slice(atIndex + 1);
+ }
+ }
+ } catch (e) {}
+ }
+ return urlspec;
+}
+
+function StripPassword(urlspec, passwordObj) {
+ urlspec = TrimString(urlspec);
+ if (!urlspec || IsUrlAboutBlank(urlspec)) {
+ return urlspec;
+ }
+
+ if (passwordObj) {
+ passwordObj.value = "";
+ }
+
+ // "@" must exist else we will never detect password
+ var atIndex = urlspec.indexOf("@");
+ if (atIndex > 0) {
+ try {
+ let password = Services.io.newURI(urlspec).password;
+
+ if (passwordObj && password) {
+ passwordObj.value = password;
+ }
+ if (password) {
+ // Find last ":" before "@"
+ let colon = urlspec.lastIndexOf(":", atIndex);
+ if (colon != -1) {
+ // Include the "@"
+ return urlspec.slice(0, colon) + urlspec.slice(atIndex);
+ }
+ }
+ } catch (e) {}
+ }
+ return urlspec;
+}
+
+// Version to use when you have an nsIURI object
+function StripUsernamePasswordFromURI(uri) {
+ var urlspec = "";
+ if (uri) {
+ try {
+ urlspec = uri.spec;
+ var userPass = uri.userPass;
+ if (userPass) {
+ let start = urlspec.indexOf(userPass);
+ urlspec =
+ urlspec.slice(0, start) + urlspec.slice(start + userPass.length + 1);
+ }
+ } catch (e) {}
+ }
+ return urlspec;
+}
+
+function InsertUsernameIntoUrl(urlspec, username) {
+ if (!urlspec || !username) {
+ return urlspec;
+ }
+
+ try {
+ let URI = Services.io.newURI(
+ urlspec,
+ GetCurrentEditor().documentCharacterSet
+ );
+ URI.username = username;
+ return URI.spec;
+ } catch (e) {}
+
+ return urlspec;
+}
+
+function ConvertRGBColorIntoHEXColor(color) {
+ if (/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.test(color)) {
+ var r = Number(RegExp.$1).toString(16);
+ if (r.length == 1) {
+ r = "0" + r;
+ }
+ var g = Number(RegExp.$2).toString(16);
+ if (g.length == 1) {
+ g = "0" + g;
+ }
+ var b = Number(RegExp.$3).toString(16);
+ if (b.length == 1) {
+ b = "0" + b;
+ }
+ return "#" + r + g + b;
+ }
+
+ return color;
+}
+
+/** *********** CSS ***************/
+
+function GetHTMLOrCSSStyleValue(element, attrName, cssPropertyName) {
+ var value;
+ if (Services.prefs.getBoolPref("editor.use_css") && IsHTMLEditor()) {
+ value = element.style.getPropertyValue(cssPropertyName);
+ }
+
+ if (!value) {
+ value = element.getAttribute(attrName);
+ }
+
+ if (!value) {
+ return "";
+ }
+
+ return value;
+}
+
+/** *********** Miscellaneous ***************/
+// Clone simple JS objects
+function Clone(obj) {
+ var clone = {};
+ for (var i in obj) {
+ if (typeof obj[i] == "object") {
+ clone[i] = Clone(obj[i]);
+ } else {
+ clone[i] = obj[i];
+ }
+ }
+ return clone;
+}
+
+/**
+ * Utility functions to handle shortended data: URLs in EdColorProps.js and EdImageOverlay.js.
+ */
+
+/**
+ * Is the passed in image URI a shortened data URI?
+ * @return {bool}
+ */
+function isImageDataShortened(aImageData) {
+ return /^data:/i.test(aImageData) && aImageData.includes("…");
+}
+
+/**
+ * Event handler for Copy or Cut
+ * @param aEvent the event
+ */
+function onCopyOrCutShortened(aEvent) {
+ // Put the original data URI onto the clipboard in case the value
+ // is a shortened data URI.
+ let field = aEvent.target;
+ let startPos = field.selectionStart;
+ if (startPos == undefined) {
+ return;
+ }
+ let endPos = field.selectionEnd;
+ let selection = field.value.substring(startPos, endPos).trim();
+
+ // Test that a) the user selected the whole value,
+ // b) the value is a data URI,
+ // c) it contains the ellipsis we added. Otherwise it could be
+ // a new value that the user pasted in.
+ if (selection == field.value.trim() && isImageDataShortened(selection)) {
+ aEvent.clipboardData.setData("text/plain", field.fullDataURI);
+ if (aEvent.type == "cut") {
+ // We have to cut the selection manually. Since we tested that
+ // everything was selected, we can just reset the field.
+ field.value = "";
+ }
+ aEvent.preventDefault();
+ }
+}
+
+/**
+ * Set up element showing an image URI with a shortened version.
+ * and add event handler for Copy or Cut.
+ *
+ * @param aImageData the data: URL of the image to be shortened.
+ * Note: Original stored in 'aDialogField.fullDataURI'.
+ * @param aDialogField The field of the dialog to contain the data.
+ * @return {bool} URL was shortened?
+ */
+function shortenImageData(aImageData, aDialogField) {
+ let shortened = false;
+ aDialogField.value = aImageData.replace(/^(data:.+;base64,)(.*)/i, function(
+ match,
+ nonDataPart,
+ dataPart
+ ) {
+ if (dataPart.length <= 35) {
+ return match;
+ }
+
+ shortened = true;
+ aDialogField.addEventListener("copy", onCopyOrCutShortened);
+ aDialogField.addEventListener("cut", onCopyOrCutShortened);
+ aDialogField.fullDataURI = aImageData;
+ aDialogField.removeAttribute("tooltiptext");
+ aDialogField.setAttribute("tooltip", "shortenedDataURI");
+ return (
+ nonDataPart +
+ dataPart.substring(0, 5) +
+ "…" +
+ dataPart.substring(dataPart.length - 30)
+ );
+ });
+ return shortened;
+}
+
+/**
+ * Return full data URIs for a shortened element.
+ *
+ * @param aDialogField The field of the dialog containing the data.
+ */
+function restoredImageData(aDialogField) {
+ return aDialogField.fullDataURI;
+}
diff --git a/comm/suite/editor/base/content/images/bringtofront-disabled.png b/comm/suite/editor/base/content/images/bringtofront-disabled.png
new file mode 100644
index 0000000000..ee8bfb0185
--- /dev/null
+++ b/comm/suite/editor/base/content/images/bringtofront-disabled.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/bringtofront.png b/comm/suite/editor/base/content/images/bringtofront.png
new file mode 100644
index 0000000000..ab22be7e66
--- /dev/null
+++ b/comm/suite/editor/base/content/images/bringtofront.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/sendtoback-disabled.png b/comm/suite/editor/base/content/images/sendtoback-disabled.png
new file mode 100644
index 0000000000..fe1e0502b2
--- /dev/null
+++ b/comm/suite/editor/base/content/images/sendtoback-disabled.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/sendtoback.png b/comm/suite/editor/base/content/images/sendtoback.png
new file mode 100644
index 0000000000..5aa02b7f0b
--- /dev/null
+++ b/comm/suite/editor/base/content/images/sendtoback.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-a.png b/comm/suite/editor/base/content/images/tag-a.png
new file mode 100644
index 0000000000..e66eb1db47
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-a.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-abr.png b/comm/suite/editor/base/content/images/tag-abr.png
new file mode 100644
index 0000000000..a04af50b35
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-abr.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-acr.png b/comm/suite/editor/base/content/images/tag-acr.png
new file mode 100644
index 0000000000..75cc3a5a9a
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-acr.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-adr.png b/comm/suite/editor/base/content/images/tag-adr.png
new file mode 100644
index 0000000000..63b95a3e02
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-adr.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-anchor.png b/comm/suite/editor/base/content/images/tag-anchor.png
new file mode 100644
index 0000000000..5b116c668c
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-anchor.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-app.png b/comm/suite/editor/base/content/images/tag-app.png
new file mode 100644
index 0000000000..ad0c0cac30
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-app.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-ara.png b/comm/suite/editor/base/content/images/tag-ara.png
new file mode 100644
index 0000000000..6c8354fa45
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-ara.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-b.png b/comm/suite/editor/base/content/images/tag-b.png
new file mode 100644
index 0000000000..0a40231180
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-b.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-bas.png b/comm/suite/editor/base/content/images/tag-bas.png
new file mode 100644
index 0000000000..d86b376ed1
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-bas.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-bdo.png b/comm/suite/editor/base/content/images/tag-bdo.png
new file mode 100644
index 0000000000..13a0db68cd
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-bdo.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-big.png b/comm/suite/editor/base/content/images/tag-big.png
new file mode 100644
index 0000000000..1bf075320c
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-big.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-blq.png b/comm/suite/editor/base/content/images/tag-blq.png
new file mode 100644
index 0000000000..7faa4c2846
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-blq.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-body.png b/comm/suite/editor/base/content/images/tag-body.png
new file mode 100644
index 0000000000..df47443823
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-body.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-br.png b/comm/suite/editor/base/content/images/tag-br.png
new file mode 100644
index 0000000000..8e93c47db3
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-br.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-bsf.png b/comm/suite/editor/base/content/images/tag-bsf.png
new file mode 100644
index 0000000000..8b2b078619
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-bsf.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-btn.png b/comm/suite/editor/base/content/images/tag-btn.png
new file mode 100644
index 0000000000..2996ff9a74
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-btn.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-cit.png b/comm/suite/editor/base/content/images/tag-cit.png
new file mode 100644
index 0000000000..37624fe222
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-cit.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-clg.png b/comm/suite/editor/base/content/images/tag-clg.png
new file mode 100644
index 0000000000..1c912ef1be
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-clg.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-cod.png b/comm/suite/editor/base/content/images/tag-cod.png
new file mode 100644
index 0000000000..5b7831f386
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-cod.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-col.png b/comm/suite/editor/base/content/images/tag-col.png
new file mode 100644
index 0000000000..834b57bb7b
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-col.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-cpt.png b/comm/suite/editor/base/content/images/tag-cpt.png
new file mode 100644
index 0000000000..4bcba8bf33
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-cpt.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-ctr.png b/comm/suite/editor/base/content/images/tag-ctr.png
new file mode 100644
index 0000000000..3e6aee0663
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-ctr.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-dd.png b/comm/suite/editor/base/content/images/tag-dd.png
new file mode 100644
index 0000000000..0b192b50ac
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-dd.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-del.png b/comm/suite/editor/base/content/images/tag-del.png
new file mode 100644
index 0000000000..0dd897c7be
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-del.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-dfn.png b/comm/suite/editor/base/content/images/tag-dfn.png
new file mode 100644
index 0000000000..ea820aeecc
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-dfn.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-dir.png b/comm/suite/editor/base/content/images/tag-dir.png
new file mode 100644
index 0000000000..3f20e2dd70
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-dir.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-div.png b/comm/suite/editor/base/content/images/tag-div.png
new file mode 100644
index 0000000000..8478e20f03
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-div.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-dl.png b/comm/suite/editor/base/content/images/tag-dl.png
new file mode 100644
index 0000000000..576b6f3968
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-dl.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-dt.png b/comm/suite/editor/base/content/images/tag-dt.png
new file mode 100644
index 0000000000..4c9121ebd5
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-dt.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-em.png b/comm/suite/editor/base/content/images/tag-em.png
new file mode 100644
index 0000000000..1a5f24551e
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-em.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-fld.png b/comm/suite/editor/base/content/images/tag-fld.png
new file mode 100644
index 0000000000..c299e5cad1
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-fld.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-fnt.png b/comm/suite/editor/base/content/images/tag-fnt.png
new file mode 100644
index 0000000000..eb8dba7c9e
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-fnt.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-for.png b/comm/suite/editor/base/content/images/tag-for.png
new file mode 100644
index 0000000000..bb38c428d0
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-for.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-frm.png b/comm/suite/editor/base/content/images/tag-frm.png
new file mode 100644
index 0000000000..5bd4689246
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-frm.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-fst.png b/comm/suite/editor/base/content/images/tag-fst.png
new file mode 100644
index 0000000000..269d5505f4
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-fst.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-h1.png b/comm/suite/editor/base/content/images/tag-h1.png
new file mode 100644
index 0000000000..2edff90dfe
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-h1.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-h2.png b/comm/suite/editor/base/content/images/tag-h2.png
new file mode 100644
index 0000000000..a55fb07cd4
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-h2.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-h3.png b/comm/suite/editor/base/content/images/tag-h3.png
new file mode 100644
index 0000000000..c8aa875994
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-h3.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-h4.png b/comm/suite/editor/base/content/images/tag-h4.png
new file mode 100644
index 0000000000..dd73041ff3
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-h4.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-h5.png b/comm/suite/editor/base/content/images/tag-h5.png
new file mode 100644
index 0000000000..1f3e94d5e3
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-h5.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-h6.png b/comm/suite/editor/base/content/images/tag-h6.png
new file mode 100644
index 0000000000..c2153ea2cc
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-h6.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-hed.png b/comm/suite/editor/base/content/images/tag-hed.png
new file mode 100644
index 0000000000..c1b87f447c
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-hed.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-hr.png b/comm/suite/editor/base/content/images/tag-hr.png
new file mode 100644
index 0000000000..b9d6a35a58
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-hr.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-html.png b/comm/suite/editor/base/content/images/tag-html.png
new file mode 100644
index 0000000000..0d1c9b361c
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-html.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-i.png b/comm/suite/editor/base/content/images/tag-i.png
new file mode 100644
index 0000000000..e75db74169
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-i.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-ifr.png b/comm/suite/editor/base/content/images/tag-ifr.png
new file mode 100644
index 0000000000..f212680ea4
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-ifr.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-img.png b/comm/suite/editor/base/content/images/tag-img.png
new file mode 100644
index 0000000000..f0b458e356
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-img.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-inp.png b/comm/suite/editor/base/content/images/tag-inp.png
new file mode 100644
index 0000000000..d9e81ea407
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-inp.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-ins.png b/comm/suite/editor/base/content/images/tag-ins.png
new file mode 100644
index 0000000000..a477f94b88
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-ins.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-isx.png b/comm/suite/editor/base/content/images/tag-isx.png
new file mode 100644
index 0000000000..4f53e9bf1d
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-isx.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-kbd.png b/comm/suite/editor/base/content/images/tag-kbd.png
new file mode 100644
index 0000000000..4945dfbd74
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-kbd.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-lbl.png b/comm/suite/editor/base/content/images/tag-lbl.png
new file mode 100644
index 0000000000..b1533723f1
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-lbl.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-lgn.png b/comm/suite/editor/base/content/images/tag-lgn.png
new file mode 100644
index 0000000000..c9d3149a9f
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-lgn.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-li.png b/comm/suite/editor/base/content/images/tag-li.png
new file mode 100644
index 0000000000..1d63b29e7c
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-li.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-lnk.png b/comm/suite/editor/base/content/images/tag-lnk.png
new file mode 100644
index 0000000000..58194ca38f
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-lnk.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-lst.png b/comm/suite/editor/base/content/images/tag-lst.png
new file mode 100644
index 0000000000..f79929c047
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-lst.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-map.png b/comm/suite/editor/base/content/images/tag-map.png
new file mode 100644
index 0000000000..9fc0dfe028
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-map.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-men.png b/comm/suite/editor/base/content/images/tag-men.png
new file mode 100644
index 0000000000..ccde7feec1
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-men.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-met.png b/comm/suite/editor/base/content/images/tag-met.png
new file mode 100644
index 0000000000..b6d86a7946
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-met.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-nbr.png b/comm/suite/editor/base/content/images/tag-nbr.png
new file mode 100644
index 0000000000..80ee8fd90c
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-nbr.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-nfr.png b/comm/suite/editor/base/content/images/tag-nfr.png
new file mode 100644
index 0000000000..885c530bf8
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-nfr.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-nsc.png b/comm/suite/editor/base/content/images/tag-nsc.png
new file mode 100644
index 0000000000..fdcde6f81e
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-nsc.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-obj.png b/comm/suite/editor/base/content/images/tag-obj.png
new file mode 100644
index 0000000000..05f80f0c87
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-obj.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-ol.png b/comm/suite/editor/base/content/images/tag-ol.png
new file mode 100644
index 0000000000..22456f8d2d
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-ol.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-opg.png b/comm/suite/editor/base/content/images/tag-opg.png
new file mode 100644
index 0000000000..9bcb0948fa
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-opg.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-opt.png b/comm/suite/editor/base/content/images/tag-opt.png
new file mode 100644
index 0000000000..46ec67560e
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-opt.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-p.png b/comm/suite/editor/base/content/images/tag-p.png
new file mode 100644
index 0000000000..0f49a89eec
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-p.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-pln.png b/comm/suite/editor/base/content/images/tag-pln.png
new file mode 100644
index 0000000000..e6d49b442c
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-pln.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-pre.png b/comm/suite/editor/base/content/images/tag-pre.png
new file mode 100644
index 0000000000..84423c484e
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-pre.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-prm.png b/comm/suite/editor/base/content/images/tag-prm.png
new file mode 100644
index 0000000000..e65d57ec1e
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-prm.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-q.png b/comm/suite/editor/base/content/images/tag-q.png
new file mode 100644
index 0000000000..a34e65d542
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-q.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-s.png b/comm/suite/editor/base/content/images/tag-s.png
new file mode 100644
index 0000000000..37564252ee
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-s.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-scr.png b/comm/suite/editor/base/content/images/tag-scr.png
new file mode 100644
index 0000000000..c8df1cefe1
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-scr.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-slc.png b/comm/suite/editor/base/content/images/tag-slc.png
new file mode 100644
index 0000000000..837b0eab89
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-slc.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-sml.png b/comm/suite/editor/base/content/images/tag-sml.png
new file mode 100644
index 0000000000..4df1639861
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-sml.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-smp.png b/comm/suite/editor/base/content/images/tag-smp.png
new file mode 100644
index 0000000000..e95e85d75f
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-smp.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-spn.png b/comm/suite/editor/base/content/images/tag-spn.png
new file mode 100644
index 0000000000..d1066e5248
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-spn.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-stk.png b/comm/suite/editor/base/content/images/tag-stk.png
new file mode 100644
index 0000000000..5700f9ed6e
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-stk.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-stl.png b/comm/suite/editor/base/content/images/tag-stl.png
new file mode 100644
index 0000000000..22fead4662
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-stl.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-stn.png b/comm/suite/editor/base/content/images/tag-stn.png
new file mode 100644
index 0000000000..9155590bfd
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-stn.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-sub.png b/comm/suite/editor/base/content/images/tag-sub.png
new file mode 100644
index 0000000000..f1ea4abbab
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-sub.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-sup.png b/comm/suite/editor/base/content/images/tag-sup.png
new file mode 100644
index 0000000000..a814d9f815
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-sup.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-tbd.png b/comm/suite/editor/base/content/images/tag-tbd.png
new file mode 100644
index 0000000000..e46c1931b3
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-tbd.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-tbl.png b/comm/suite/editor/base/content/images/tag-tbl.png
new file mode 100644
index 0000000000..cb553528f0
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-tbl.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-td.png b/comm/suite/editor/base/content/images/tag-td.png
new file mode 100644
index 0000000000..beebc393a4
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-td.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-tft.png b/comm/suite/editor/base/content/images/tag-tft.png
new file mode 100644
index 0000000000..cb0db0fe21
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-tft.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-th.png b/comm/suite/editor/base/content/images/tag-th.png
new file mode 100644
index 0000000000..dac140f41e
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-th.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-thd.png b/comm/suite/editor/base/content/images/tag-thd.png
new file mode 100644
index 0000000000..7b7325c2af
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-thd.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-tr.png b/comm/suite/editor/base/content/images/tag-tr.png
new file mode 100644
index 0000000000..5ab2fc0e85
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-tr.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-tt.png b/comm/suite/editor/base/content/images/tag-tt.png
new file mode 100644
index 0000000000..61108f6366
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-tt.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-ttl.png b/comm/suite/editor/base/content/images/tag-ttl.png
new file mode 100644
index 0000000000..2cbdbe3943
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-ttl.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-txt.png b/comm/suite/editor/base/content/images/tag-txt.png
new file mode 100644
index 0000000000..2ec48df034
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-txt.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-u.png b/comm/suite/editor/base/content/images/tag-u.png
new file mode 100644
index 0000000000..c435789a59
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-u.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-ul.png b/comm/suite/editor/base/content/images/tag-ul.png
new file mode 100644
index 0000000000..5bdee5d496
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-ul.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-userdefined.png b/comm/suite/editor/base/content/images/tag-userdefined.png
new file mode 100644
index 0000000000..1b36f9f259
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-userdefined.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-var.png b/comm/suite/editor/base/content/images/tag-var.png
new file mode 100644
index 0000000000..aa8200597b
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-var.png
Binary files differ
diff --git a/comm/suite/editor/base/content/images/tag-xmp.png b/comm/suite/editor/base/content/images/tag-xmp.png
new file mode 100644
index 0000000000..3c66fa0d9e
--- /dev/null
+++ b/comm/suite/editor/base/content/images/tag-xmp.png
Binary files differ
diff --git a/comm/suite/editor/base/content/publishprefs.js b/comm/suite/editor/base/content/publishprefs.js
new file mode 100644
index 0000000000..4de3b4f282
--- /dev/null
+++ b/comm/suite/editor/base/content/publishprefs.js
@@ -0,0 +1,867 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/****************** Get publishing data methods *******************/
+
+// Build an array of all publish site data obtained from prefs
+function GetPublishSiteData()
+{
+ var publishBranch = GetPublishPrefsBranch();
+ if (!publishBranch)
+ return null;
+
+ // Array of site names - sorted, but don't put default name first
+ var siteNameList = GetSiteNameList(true, false);
+ if (!siteNameList)
+ return null;
+
+ // Array of all site data
+ var siteArray = [];
+
+ // We rewrite siteName prefs to eliminate names if data is bad
+ // and to be sure order is the same as sorted name list
+ try {
+ publishBranch.deleteBranch("site_name.");
+ } catch (e) {}
+
+ // Get publish data using siteName as the key
+ var index = 0;
+ for (var i = 0; i < siteNameList.length; i++)
+ {
+ // Associated data uses site name as key
+ var publishData = GetPublishData_internal(publishBranch, siteNameList[i]);
+ if (publishData)
+ {
+ siteArray[index] = publishData;
+ SetPublishStringPref(publishBranch, "site_name."+index, siteNameList[i]);
+ index++;
+ }
+ else
+ {
+ try {
+ // Remove bad site prefs now
+ publishBranch.deleteBranch("site_data." + siteNameList[i] + ".");
+ } catch (e) {}
+ }
+ }
+
+ SavePrefFile();
+
+ if (index == 0) // No Valid pref records found!
+ return null;
+
+
+ return siteArray;
+}
+
+function GetDefaultPublishSiteName()
+{
+ var publishBranch = GetPublishPrefsBranch();
+ var name = "";
+ if (publishBranch)
+ name = GetPublishStringPref(publishBranch, "default_site");
+
+ return name;
+}
+
+// Return object with all info needed to publish
+// from database of sites previously published to.
+function CreatePublishDataFromUrl(docUrl)
+{
+ if (!docUrl || IsUrlAboutBlank(docUrl) || GetScheme(docUrl) == "file")
+ return null;
+
+ var pubSiteData = GetPublishSiteData();
+ if (pubSiteData)
+ {
+ var dirObj = {};
+ var index = FindSiteIndexAndDocDir(pubSiteData, docUrl, dirObj);
+ var publishData;
+ if (index != -1)
+ {
+ publishData = pubSiteData[index];
+ publishData.docDir = FormatDirForPublishing(dirObj.value)
+
+ //XXX Problem: OtherDir: How do we decide when to use the dir in
+ // publishSiteData (default DocDir) or docDir from current filepath?
+ publishData.otherDir = FormatDirForPublishing(pubSiteData[index].otherDir);
+
+ publishData.filename = GetFilename(docUrl);
+ publishData.notInSiteData = false;
+ return publishData;
+ }
+ }
+
+ // Document wasn't found in publish site database
+ // Create data just from URL
+
+ // Extract username and password from docUrl
+ var userObj = {};
+ var passObj = {};
+ var pubUrl = StripUsernamePassword(docUrl, userObj, passObj);
+
+ // Strip off filename
+ var lastSlash = pubUrl.lastIndexOf("\/");
+ //XXX Look for "?", "=", and "&" ?
+ pubUrl = pubUrl.slice(0, lastSlash+1);
+
+ var siteName = CreateSiteNameFromUrl(pubUrl, pubSiteData);
+
+ publishData = {
+ siteName : siteName,
+ previousSiteName : siteName,
+ filename : GetFilename(docUrl),
+ username : userObj.value,
+ password : passObj.value,
+ savePassword : false,
+ publishUrl : pubUrl,
+ browseUrl : pubUrl,
+ docDir : "",
+ otherDir : "",
+ publishOtherFiles : true,
+ dirList : [""],
+ saveDirs : false,
+ notInSiteData : true
+ }
+
+ return publishData;
+}
+
+function CreateSiteNameFromUrl(url, publishSiteData)
+{
+ var host = GetHost(url);
+ var schemePostfix = " (" + GetScheme(url) + ")";
+ var siteName = host + schemePostfix;
+
+ if (publishSiteData)
+ {
+ // Look for duplicates. Append "-1" etc until unique name found
+ var i = 1;
+ var exists = false;
+ do {
+ exists = PublishSiteNameExists(siteName, publishSiteData, -1)
+ if (exists)
+ siteName = host + "-" + i + schemePostfix;
+ i++;
+ }
+ while (exists);
+ }
+ return siteName;
+}
+
+// Similar to above, but in param is a site profile name
+// Note that this is more efficient than getting from a URL,
+// since we don't have to get all the sitedata but can key off of sitename.
+// Caller must supply the current docUrl or just a filename
+// If doc URL is supplied, we find the publish subdirectory if publishUrl is part of docUrl
+function GetPublishDataFromSiteName(siteName, docUrlOrFilename)
+{
+ var publishBranch = GetPublishPrefsBranch();
+ if (!publishBranch)
+ return null;
+
+ var siteNameList = GetSiteNameList(false, false);
+ if (!siteNameList)
+ return null;
+ for (var i = 0; i < siteNameList.length; i++)
+ {
+ if (siteNameList[i] == siteName)
+ {
+ var publishData = GetPublishData_internal(publishBranch, siteName);
+ if (GetScheme(docUrlOrFilename))
+ FillInMatchingPublishData(publishData, docUrlOrFilename);
+ else
+ publishData.filename = docUrlOrFilename;
+
+ return publishData;
+ }
+ }
+ return null;
+}
+
+function GetDefaultPublishData()
+{
+ var publishBranch = GetPublishPrefsBranch();
+ if (!publishBranch)
+ return null;
+
+ var siteName = GetPublishStringPref(publishBranch, "default_site");
+ if (!siteName)
+ return null;
+
+ return GetPublishData_internal(publishBranch, siteName);
+}
+
+function GetPublishData_internal(publishBranch, siteName)
+{
+ if (!publishBranch || !siteName)
+ return null;
+
+ var prefPrefix = "site_data." + siteName + ".";
+
+ // We must have a publish url, else we ignore this site
+ // (siteData and siteNames for sites with incomplete data
+ // will get deleted by SavePublishSiteDataToPrefs)
+ var publishUrl = GetPublishStringPref(publishBranch, prefPrefix+"url");
+ if (!publishUrl)
+ return null;
+
+ var savePassword = false;
+ var publishOtherFiles = true;
+ try {
+ savePassword = publishBranch.getBoolPref(prefPrefix+"save_password");
+ publishOtherFiles = publishBranch.getBoolPref(prefPrefix+"publish_other_files");
+ } catch (e) {}
+
+ var publishData = {
+ siteName : siteName,
+ previousSiteName : siteName,
+ filename : "",
+ username : GetPublishStringPref(publishBranch, prefPrefix+"username"),
+ savePassword : savePassword,
+ publishUrl : publishUrl,
+ browseUrl : GetPublishStringPref(publishBranch, prefPrefix+"browse_url"),
+ docDir : FormatDirForPublishing(GetPublishStringPref(publishBranch, prefPrefix+"doc_dir")),
+ otherDir : FormatDirForPublishing(GetPublishStringPref(publishBranch, prefPrefix+"other_dir")),
+ publishOtherFiles : publishOtherFiles,
+ saveDirs : false
+ }
+
+ // Get password from PasswordManager
+ publishData.password = GetSavedPassword(publishData);
+
+ // If password was found, user must have checked "Save password"
+ // checkbox in prompt outside of publishing, so override the pref we stored
+ if (publishData.password)
+ {
+ if (!savePassword)
+ {
+ try {
+ publishPrefsBranch.setBoolPref(prefPrefix+"save_password", true);
+ } catch (e) {}
+ }
+ publishData.savePassword = true;
+ }
+
+ // Build history list of directories
+ // Always supply the root dir
+ publishData.dirList = [""];
+
+ // Get the rest from prefs
+ var dirPrefs;
+ try {
+ dirPrefs = publishBranch.getChildList(prefPrefix+"dir.");
+ } catch (e) {}
+
+ if (dirPrefs && dirPrefs.length > 0)
+ {
+ if (dirPrefs.length > 1)
+ dirPrefs.sort();
+
+ for (var j = 0; j < dirPrefs.length; j++)
+ {
+ var dirName = GetPublishStringPref(publishBranch, dirPrefs[j]);
+ if (dirName)
+ publishData.dirList[j+1] = dirName;
+ }
+ }
+
+ return publishData;
+}
+
+/****************** Save publishing data methods *********************/
+
+// Save the siteArray containing all current publish site data
+function SavePublishSiteDataToPrefs(siteArray, defaultName)
+{
+ var publishBranch = GetPublishPrefsBranch();
+ if (!publishBranch)
+ return false;
+
+ try {
+ if (siteArray)
+ {
+ var defaultFound = false;
+
+ // Clear existing names and data -- rebuild all site prefs
+ publishBranch.deleteBranch("site_name.");
+ publishBranch.deleteBranch("site_data.");
+
+ for (var i = 0; i < siteArray.length; i++)
+ {
+ SavePublishData_Internal(publishBranch, siteArray[i], i);
+ if (!defaultFound)
+ defaultFound = defaultName == siteArray[i].siteName;
+ }
+ // Assure that we have a default name
+ if (siteArray.length && !defaultFound)
+ defaultName = siteArray[0].siteName;
+ }
+
+ // Save default site name
+ SetPublishStringPref(publishBranch, "default_site", defaultName);
+
+ // Force saving to file so next page edited finds these values
+ SavePrefFile();
+ }
+ catch (ex) { return false; }
+
+ return true;
+}
+
+// Update prefs if publish site already exists
+// or add prefs for a new site
+function SavePublishDataToPrefs(publishData)
+{
+ if (!publishData || !publishData.publishUrl)
+ return false;
+
+ var publishBranch = GetPublishPrefsBranch();
+ if (!publishBranch)
+ return false;
+
+ // Create name from URL if no site name is provided
+ if (!publishData.siteName)
+ publishData.siteName = CreateSiteNameFromUrl(publishData.publishUrl, publishData);
+
+ var siteNamePrefs;
+ try {
+ siteNamePrefs = publishBranch.getChildList("site_name.");
+ } catch (e) {}
+
+ if (!siteNamePrefs || siteNamePrefs.length == 0)
+ {
+ // We currently have no site prefs, so create them
+ var siteData = [publishData];
+ return SavePublishSiteDataToPrefs(siteData, publishData.siteName);
+ }
+
+ // Use "previous" name if available in case it was changed
+ var previousSiteName = ("previousSiteName" in publishData && publishData.previousSiteName) ?
+ publishData.previousSiteName : publishData.siteName;
+
+ // Find site number of existing site or fall through at next available one
+ // (Number is arbitrary; needed to construct unique "site_name.x" pref string)
+ for (var i = 0; i < siteNamePrefs.length; i++)
+ {
+ var siteName = GetPublishStringPref(publishBranch, "site_name."+i);
+
+ if (siteName == previousSiteName)
+ {
+ // Delete prefs for an existing site
+ try {
+ publishBranch.deleteBranch("site_data." + siteName + ".");
+ } catch (e) {}
+ break;
+ }
+ }
+
+ // We've taken care of finding old duplicate, so be sure 'previous name' is current
+ publishData.previousSiteName = publishData.siteName;
+
+ var ret = SavePublishData_Internal(publishBranch, publishData, i);
+ if (ret)
+ {
+ // Check if siteName was the default and we need to update that
+ var defaultSiteName = GetPublishStringPref(publishBranch, "default_site");
+ if (previousSiteName == defaultSiteName
+ && publishData.siteName != defaultSiteName)
+ SetPublishStringPref(publishBranch, "default_site", publishData.siteName);
+
+ SavePrefFile();
+
+ // Clear signal to save these data
+ if ("notInSiteData" in publishData && publishData.notInSiteData)
+ publishData.notInSiteData = false;
+ }
+ return ret;
+}
+
+// Save data at a particular site number
+function SavePublishData_Internal(publishPrefsBranch, publishData, siteIndex)
+{
+ if (!publishPrefsBranch || !publishData)
+ return false;
+
+ SetPublishStringPref(publishPrefsBranch, "site_name."+siteIndex, publishData.siteName);
+
+ FixupUsernamePasswordInPublishData(publishData);
+
+ var prefPrefix = "site_data." + publishData.siteName + "."
+
+ SetPublishStringPref(publishPrefsBranch, prefPrefix+"url", publishData.publishUrl);
+ SetPublishStringPref(publishPrefsBranch, prefPrefix+"browse_url", publishData.browseUrl);
+ SetPublishStringPref(publishPrefsBranch, prefPrefix+"username", publishData.username);
+
+ try {
+ publishPrefsBranch.setBoolPref(prefPrefix+"save_password", publishData.savePassword);
+ publishPrefsBranch.setBoolPref(prefPrefix+"publish_other_files", publishData.publishOtherFiles);
+ } catch (e) {}
+
+ // Save password using PasswordManager
+ // (If publishData.savePassword = false, this clears existing password)
+ SavePassword(publishData);
+
+ SetPublishStringPref(publishPrefsBranch, prefPrefix+"doc_dir",
+ FormatDirForPublishing(publishData.docDir));
+
+ if (publishData.publishOtherFiles && publishData.otherDir)
+ SetPublishStringPref(publishPrefsBranch, prefPrefix+"other_dir",
+ FormatDirForPublishing(publishData.otherDir));
+
+ if ("saveDirs" in publishData && publishData.saveDirs)
+ {
+ if (publishData.docDir)
+ AppendNewDirToList(publishData, publishData.docDir);
+
+ if (publishData.publishOtherFiles && publishData.otherDir
+ && publishData.otherDir != publishData.docDir)
+ AppendNewDirToList(publishData, publishData.otherDir);
+ }
+
+ // Save array of subdirectories with site
+ if (publishData.dirList.length)
+ {
+ publishData.dirList.sort();
+ var dirIndex = 0;
+ for (var j = 0; j < publishData.dirList.length; j++)
+ {
+ var dir = publishData.dirList[j];
+
+ // Don't store the root dir
+ if (dir && dir != "/")
+ {
+ SetPublishStringPref(publishPrefsBranch, prefPrefix + "dir." + dirIndex, dir);
+ dirIndex++;
+ }
+ }
+ }
+
+ return true;
+}
+
+function AppendNewDirToList(publishData, newDir)
+{
+ newDir = FormatDirForPublishing(newDir);
+ if (!publishData || !newDir)
+ return;
+
+ if (!publishData.dirList)
+ {
+ publishData.dirList = [newDir];
+ return;
+ }
+
+ // Check if already in the list
+ for (var i = 0; i < publishData.dirList.length; i++)
+ {
+ // Don't add if already in the list
+ if (newDir == publishData.dirList[i])
+ return;
+ }
+ // Add to end of list
+ publishData.dirList[publishData.dirList.length] = newDir;
+}
+
+function RemovePublishSubdirectoryFromPrefs(publishData, removeDir)
+{
+ removeDir = FormatDirForPublishing(removeDir);
+ if (!publishData || !publishData.siteName || !removeDir)
+ return false;
+
+ var publishBranch = GetPublishPrefsBranch();
+ if (!publishBranch)
+ return false;
+
+ var prefPrefix = "site_data." + publishData.siteName + ".";
+
+ // Remove dir from the default dir prefs
+ if (publishData.docDir == removeDir)
+ {
+ publishData.docDir = "";
+ SetPublishStringPref(publishBranch, prefPrefix+"doc_dir", "");
+ }
+
+ if (publishData.otherDir == removeDir)
+ {
+ publishData.otherDir = "";
+ SetPublishStringPref(publishBranch, prefPrefix+"other_dir", "");
+ }
+
+ prefPrefix += "dir.";
+
+ // Delete entire subdir list
+ try {
+ publishBranch.deleteBranch(prefPrefix);
+ } catch (e) {}
+
+ // Rebuild prefs, skipping over site to remove
+ if (publishData.dirList.length)
+ {
+ var dirIndex = 0;
+ var docDirInList = false;
+ var otherDirInList = false;
+ for (var i = 0; i < publishData.dirList.length; i++)
+ {
+ var dir = publishData.dirList[i];
+ if (dir == removeDir)
+ {
+ // Remove item from the dirList array
+ publishData.dirList.splice(i, 1);
+ --i;
+ }
+ else if (dir && dir != "/") // skip empty or root dir
+ {
+ // Save to prefs
+ SetPublishStringPref(publishBranch, prefPrefix + dirIndex, dir);
+ dirIndex++;
+ }
+ }
+ }
+ SavePrefFile();
+ return true;
+}
+
+function SetDefaultSiteName(name)
+{
+ if (name)
+ {
+ var publishBranch = GetPublishPrefsBranch();
+ if (publishBranch)
+ SetPublishStringPref(publishBranch, "default_site", name);
+
+ SavePrefFile();
+ }
+}
+
+function SavePrefFile()
+{
+ try {
+ Services.prefs.savePrefFile(null);
+ }
+ catch (e) {}
+}
+
+/***************** Helper / utility methods ********************/
+
+function GetPublishPrefsBranch()
+{
+ return Services.prefs.getBranch("editor.publish.");
+}
+
+function GetSiteNameList(doSort, defaultFirst)
+{
+ var publishBranch = GetPublishPrefsBranch();
+ if (!publishBranch)
+ return null;
+
+ var siteNamePrefs;
+ try {
+ siteNamePrefs = publishBranch.getChildList("site_name.");
+ } catch (e) {}
+
+ if (!siteNamePrefs || siteNamePrefs.length == 0)
+ return null;
+
+ // Array of site names
+ var siteNameList = [];
+ var index = 0;
+ var defaultName = "";
+ if (defaultFirst)
+ {
+ defaultName = GetPublishStringPref(publishBranch, "default_site");
+ // This always sorts to top -- replace with real string below
+ siteNameList[0] = "";
+ index++;
+ }
+
+ for (var i = 0; i < siteNamePrefs.length; i++)
+ {
+ var siteName = GetPublishStringPref(publishBranch, siteNamePrefs[i]);
+ // Skip if siteName pref is empty or is default name
+ if (siteName && siteName != defaultName)
+ {
+ siteNameList[index] = siteName;
+ index++;
+ }
+ }
+
+ if (siteNameList.length && doSort)
+ siteNameList.sort();
+
+ if (defaultName)
+ {
+ siteNameList[0] = defaultName;
+ index++;
+ }
+
+ return siteNameList.length? siteNameList : null;
+}
+
+function PublishSiteNameExists(name, publishSiteData, skipSiteIndex)
+{
+ if (!name)
+ return false;
+
+ if (!publishSiteData)
+ {
+ publishSiteData = GetPublishSiteData();
+ skipSiteIndex = -1;
+ }
+
+ if (!publishSiteData)
+ return false;
+
+ // Array of site names - sorted, but don't put default name first
+ for (var i = 0; i < publishSiteData.length; i++)
+ {
+ if (i != skipSiteIndex && name == publishSiteData[i].siteName)
+ return true;
+ }
+ return false;
+}
+
+// Find index of a site record in supplied publish site database
+// docUrl: Document URL with or without filename
+// (Must end in "/" if no filename)
+// dirObj.value = the directory of the document URL
+// relative to the base publishing URL, using "" if none
+//
+// XXX: Currently finds the site with the longest-matching url;
+// should we look for the shortest instead? Or match just the host portion?
+function FindSiteIndexAndDocDir(publishSiteData, docUrl, dirObj)
+{
+ if (dirObj)
+ dirObj.value = "";
+
+ if (!publishSiteData || !docUrl || GetScheme(docUrl) == "file")
+ return -1;
+
+ var siteIndex = -1;
+ var siteUrlLen = 0;
+
+ for (var i = 0; i < publishSiteData.length; i++)
+ {
+ // Site publish or browse url needs to be contained in document URL,
+ // but that may also have a directory after the site base URL
+ // So we must examine all records to find the site URL that best
+ // matches the document URL: the longest-matching substring (XXX is this right?)
+ var lenObj = {value:0};
+ var tempData = Clone(publishSiteData[i]);
+
+ // Check if this site matches docUrl (returns length of match if found)
+ var len = FillInMatchingPublishData(tempData, docUrl);
+
+ if (len > siteUrlLen)
+ {
+ siteIndex = i;
+ siteUrlLen = len;
+ if (dirObj)
+ dirObj.value = tempData.docDir;
+
+ // Continue to find the site with longest-matching publishUrl
+ }
+ }
+ return siteIndex;
+}
+
+// Look for a matching publish url within the document url
+// (We need to look at both "publishUrl" and "browseUrl" in case we are editing
+// an http: document but using ftp: to publish.)
+// If match is found:
+// Fill in the filename and subdirectory based on the docUrl and
+// return the length of the docUrl with username+password stripped out
+function FillInMatchingPublishData(publishData, docUrl)
+{
+ if (!publishData || !docUrl)
+ return 0;
+
+ // Separate docUrl into the base url and filename
+ var lastSlash = docUrl.lastIndexOf("\/");
+ var baseUrl = docUrl.slice(0, lastSlash+1);
+ var filename = docUrl.slice(lastSlash+1);
+
+ // Strip username+password from docUrl because these
+ // are stored separately in publishData, never embedded in the publishUrl
+ // If both docUrl and publishData contain usernames,
+ // we must match that as well as the url
+ var username = {value:""};
+ baseUrl = StripUsernamePassword(baseUrl, username);
+ username = username.value;
+
+ var matchedLength = 0;
+ let pubUrlFound = publishData.publishUrl && baseUrl.startsWith(publishData.publishUrl);
+ let browseUrlFound = publishData.browseUrl && baseUrl.startsWith(publishData.browseUrl);
+
+ if ((pubUrlFound || browseUrlFound)
+ && (!username || !publishData.username || username == publishData.username))
+ {
+ // We found a match
+ matchedLength = pubUrlFound ? publishData.publishUrl.length
+ : publishData.browseUrl.length;
+
+ if (matchedLength > 0)
+ {
+ publishData.filename = filename;
+
+ // Subdirectory within the site is what's left in baseUrl after the matched portion
+ publishData.docDir = FormatDirForPublishing(baseUrl.slice(matchedLength));
+ }
+ }
+ return matchedLength;
+}
+
+// Prefs that don't exist will through an exception,
+// so just return an empty string
+function GetPublishStringPref(prefBranch, name)
+{
+ if (prefBranch && name)
+ {
+ try {
+ return prefBranch.getStringPref(name);
+ } catch (e) {}
+ }
+ return "";
+}
+
+function SetPublishStringPref(prefBranch, name, value)
+{
+ if (prefBranch && name)
+ {
+ try {
+ prefBranch.setStringPref(name, value);
+ } catch (e) {}
+ }
+}
+
+// Assure that a publishing URL ends in "/", "=", "&" or "?"
+// Username and password should always be extracted as separate fields
+// and are not allowed to remain embedded in publishing URL
+function FormatUrlForPublishing(url)
+{
+ url = TrimString(StripUsernamePassword(url));
+ if (url)
+ {
+ var lastChar = url.charAt(url.length-1);
+ if (lastChar != "/" && lastChar != "=" && lastChar != "&" && lastChar != "?")
+ return (url + "/");
+ }
+ return url;
+}
+
+// Username and password present in publish url are
+// extracted into the separate "username" and "password" fields
+// of the publishData object
+// Returns true if we did change the publishData
+function FixupUsernamePasswordInPublishData(publishData)
+{
+ var ret = false;
+ if (publishData && publishData.publishUrl)
+ {
+ var userObj = {value:""};
+ var passObj = {value:""};
+ publishData.publishUrl = FormatUrlForPublishing(StripUsernamePassword(publishData.publishUrl, userObj, passObj));
+ if (userObj.value)
+ {
+ publishData.username = userObj.value;
+ ret = true;
+ }
+ if (passObj.value)
+ {
+ publishData.password = passObj.value;
+ ret = true;
+ }
+ // While we're at it, be sure browse URL is proper format
+ publishData.browseUrl = FormatUrlForPublishing(publishData.browseUrl);
+ }
+ return ret;
+}
+
+// Assure that a publishing directory ends with "/" and does not begin with "/"
+// Input dir is assumed to be a subdirectory string, not a full URL or pathname
+function FormatDirForPublishing(dir)
+{
+ dir = TrimString(dir);
+
+ // The "//" case is an expected "typo" filter
+ // that simplifies code below!
+ if (!dir || dir == "/" || dir == "//")
+ return "";
+
+ // Remove leading "/"
+ if (dir.startsWith("/"))
+ dir = dir.slice(1);
+
+ // Append "/" at the end if necessary
+ var dirLen = dir.length;
+ var lastChar = dir.charAt(dirLen-1);
+ if (dirLen > 1 && ["/", "=", "&", "?"].indexOf(lastChar) == -1)
+ dir += "/";
+
+ return dir;
+}
+
+function GetSavedPassword(publishData)
+{
+ if (!publishData || !publishData.publishUrl)
+ return "";
+
+ let url = GetUrlForPasswordManager(publishData);
+ let logins = Services.logins.findLogins(url, null, url);
+
+ for (let i = 0; i < logins.length; i++) {
+ if (logins[i].username == publishData.username)
+ return logins[i].password;
+ }
+
+ return "";
+}
+
+function SavePassword(publishData)
+{
+ if (!publishData || !publishData.publishUrl || !publishData.username)
+ return false;
+
+ let url = GetUrlForPasswordManager(publishData);
+
+ // Remove existing entry by finding all logins that match.
+ let logins = Services.logins.findLogins(url, null, url);
+
+ for (let i = 0; i < logins.length; i++) {
+ if (logins[i].username == publishData.username) {
+ Services.logins.removeLogin(logins[i]);
+ break;
+ }
+ }
+
+ // If SavePassword is true, add new password.
+ if (publishData.savePassword)
+ {
+ let authInfo = Cc["@mozilla.org/login-manager/loginInfo;1"]
+ .createInstance(Ci.nsILoginInfo);
+ authInfo.init(url, null, url, publishData.username, publishData.password,
+ "", "");
+ Services.logins.addLogin(authInfo);
+ }
+
+ return true;
+}
+
+function GetUrlForPasswordManager(publishData)
+{
+ if (!publishData || !publishData.publishUrl)
+ return false;
+
+ let url = Services.io.newURI(publishData.publishUrl);
+
+ if (url.scheme == "ftp" && publishData.username)
+ // Include username in the URL so we can handle multiple users per server
+ // in the password manager
+ url = url.scheme + "://" + publishData.username + "@" + url.hostPort;
+ else
+ url = url.scheme + "://" + url.hostPort;
+
+ return url;
+}
diff --git a/comm/suite/editor/base/jar.mn b/comm/suite/editor/base/jar.mn
new file mode 100644
index 0000000000..35ed2406b2
--- /dev/null
+++ b/comm/suite/editor/base/jar.mn
@@ -0,0 +1,124 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+comm.jar:
+% content editor %content/editor/
+% overlay chrome://communicator/content/tasksOverlay.xhtml chrome://editor/content/editorTasksOverlay.xhtml
+ content/editor/ComposerCommands.js (content/ComposerCommands.js)
+ content/editor/composerOverlay.xhtml (content/composerOverlay.xhtml)
+ content/editor/editingOverlay.js (content/editingOverlay.js)
+ content/editor/editingOverlay.xhtml (content/editingOverlay.xhtml)
+ content/editor/editor.js (content/editor.js)
+ content/editor/editor.xhtml (content/editor.xhtml)
+ content/editor/EditorAllTags.css (content/EditorAllTags.css)
+ content/editor/editorApplicationOverlay.js (content/editorApplicationOverlay.js)
+ content/editor/EditorContent.css (content/EditorContent.css)
+ content/editor/EditorContextMenu.js (content/EditorContextMenu.js)
+ content/editor/EditorContextMenuOverlay.xhtml (content/EditorContextMenuOverlay.xhtml)
+ content/editor/editorOverlay.xhtml (content/editorOverlay.xhtml)
+ content/editor/editorTasksOverlay.xhtml (content/editorTasksOverlay.xhtml)
+ content/editor/editorUtilities.js (content/editorUtilities.js)
+ content/editor/publishprefs.js (content/publishprefs.js)
+ content/editor/StructBarContextMenu.js (content/StructBarContextMenu.js)
+ content/editor/images/bringtofront.png (content/images/bringtofront.png)
+ content/editor/images/bringtofront-disabled.png (content/images/bringtofront-disabled.png)
+ content/editor/images/sendtoback.png (content/images/sendtoback.png)
+ content/editor/images/sendtoback-disabled.png (content/images/sendtoback-disabled.png)
+ content/editor/images/tag-a.png (content/images/tag-a.png)
+ content/editor/images/tag-abr.png (content/images/tag-abr.png)
+ content/editor/images/tag-acr.png (content/images/tag-acr.png)
+ content/editor/images/tag-adr.png (content/images/tag-adr.png)
+ content/editor/images/tag-anchor.png (content/images/tag-anchor.png)
+ content/editor/images/tag-app.png (content/images/tag-app.png)
+ content/editor/images/tag-ara.png (content/images/tag-ara.png)
+ content/editor/images/tag-b.png (content/images/tag-b.png)
+ content/editor/images/tag-bas.png (content/images/tag-bas.png)
+ content/editor/images/tag-bdo.png (content/images/tag-bdo.png)
+ content/editor/images/tag-big.png (content/images/tag-big.png)
+ content/editor/images/tag-blq.png (content/images/tag-blq.png)
+ content/editor/images/tag-body.png (content/images/tag-body.png)
+ content/editor/images/tag-br.png (content/images/tag-br.png)
+ content/editor/images/tag-bsf.png (content/images/tag-bsf.png)
+ content/editor/images/tag-btn.png (content/images/tag-btn.png)
+ content/editor/images/tag-cit.png (content/images/tag-cit.png)
+ content/editor/images/tag-clg.png (content/images/tag-clg.png)
+ content/editor/images/tag-cod.png (content/images/tag-cod.png)
+ content/editor/images/tag-col.png (content/images/tag-col.png)
+ content/editor/images/tag-cpt.png (content/images/tag-cpt.png)
+ content/editor/images/tag-ctr.png (content/images/tag-ctr.png)
+ content/editor/images/tag-dd.png (content/images/tag-dd.png)
+ content/editor/images/tag-del.png (content/images/tag-del.png)
+ content/editor/images/tag-dfn.png (content/images/tag-dfn.png)
+ content/editor/images/tag-dir.png (content/images/tag-dir.png)
+ content/editor/images/tag-div.png (content/images/tag-div.png)
+ content/editor/images/tag-dl.png (content/images/tag-dl.png)
+ content/editor/images/tag-dt.png (content/images/tag-dt.png)
+ content/editor/images/tag-em.png (content/images/tag-em.png)
+ content/editor/images/tag-fld.png (content/images/tag-fld.png)
+ content/editor/images/tag-fnt.png (content/images/tag-fnt.png)
+ content/editor/images/tag-for.png (content/images/tag-for.png)
+ content/editor/images/tag-frm.png (content/images/tag-frm.png)
+ content/editor/images/tag-fst.png (content/images/tag-fst.png)
+ content/editor/images/tag-h1.png (content/images/tag-h1.png)
+ content/editor/images/tag-h2.png (content/images/tag-h2.png)
+ content/editor/images/tag-h3.png (content/images/tag-h3.png)
+ content/editor/images/tag-h4.png (content/images/tag-h4.png)
+ content/editor/images/tag-h5.png (content/images/tag-h5.png)
+ content/editor/images/tag-h6.png (content/images/tag-h6.png)
+ content/editor/images/tag-hed.png (content/images/tag-hed.png)
+ content/editor/images/tag-hr.png (content/images/tag-hr.png)
+ content/editor/images/tag-html.png (content/images/tag-html.png)
+ content/editor/images/tag-i.png (content/images/tag-i.png)
+ content/editor/images/tag-ifr.png (content/images/tag-ifr.png)
+ content/editor/images/tag-img.png (content/images/tag-img.png)
+ content/editor/images/tag-inp.png (content/images/tag-inp.png)
+ content/editor/images/tag-ins.png (content/images/tag-ins.png)
+ content/editor/images/tag-isx.png (content/images/tag-isx.png)
+ content/editor/images/tag-kbd.png (content/images/tag-kbd.png)
+ content/editor/images/tag-lbl.png (content/images/tag-lbl.png)
+ content/editor/images/tag-lgn.png (content/images/tag-lgn.png)
+ content/editor/images/tag-li.png (content/images/tag-li.png)
+ content/editor/images/tag-lnk.png (content/images/tag-lnk.png)
+ content/editor/images/tag-lst.png (content/images/tag-lst.png)
+ content/editor/images/tag-map.png (content/images/tag-map.png)
+ content/editor/images/tag-men.png (content/images/tag-men.png)
+ content/editor/images/tag-met.png (content/images/tag-met.png)
+ content/editor/images/tag-nbr.png (content/images/tag-nbr.png)
+ content/editor/images/tag-nfr.png (content/images/tag-nfr.png)
+ content/editor/images/tag-nsc.png (content/images/tag-nsc.png)
+ content/editor/images/tag-obj.png (content/images/tag-obj.png)
+ content/editor/images/tag-ol.png (content/images/tag-ol.png)
+ content/editor/images/tag-opg.png (content/images/tag-opg.png)
+ content/editor/images/tag-opt.png (content/images/tag-opt.png)
+ content/editor/images/tag-p.png (content/images/tag-p.png)
+ content/editor/images/tag-pln.png (content/images/tag-pln.png)
+ content/editor/images/tag-pre.png (content/images/tag-pre.png)
+ content/editor/images/tag-prm.png (content/images/tag-prm.png)
+ content/editor/images/tag-q.png (content/images/tag-q.png)
+ content/editor/images/tag-s.png (content/images/tag-s.png)
+ content/editor/images/tag-scr.png (content/images/tag-scr.png)
+ content/editor/images/tag-slc.png (content/images/tag-slc.png)
+ content/editor/images/tag-sml.png (content/images/tag-sml.png)
+ content/editor/images/tag-smp.png (content/images/tag-smp.png)
+ content/editor/images/tag-spn.png (content/images/tag-spn.png)
+ content/editor/images/tag-stk.png (content/images/tag-stk.png)
+ content/editor/images/tag-stl.png (content/images/tag-stl.png)
+ content/editor/images/tag-stn.png (content/images/tag-stn.png)
+ content/editor/images/tag-sub.png (content/images/tag-sub.png)
+ content/editor/images/tag-sup.png (content/images/tag-sup.png)
+ content/editor/images/tag-tbd.png (content/images/tag-tbd.png)
+ content/editor/images/tag-tbl.png (content/images/tag-tbl.png)
+ content/editor/images/tag-td.png (content/images/tag-td.png)
+ content/editor/images/tag-tft.png (content/images/tag-tft.png)
+ content/editor/images/tag-th.png (content/images/tag-th.png)
+ content/editor/images/tag-thd.png (content/images/tag-thd.png)
+ content/editor/images/tag-tr.png (content/images/tag-tr.png)
+ content/editor/images/tag-tt.png (content/images/tag-tt.png)
+ content/editor/images/tag-ttl.png (content/images/tag-ttl.png)
+ content/editor/images/tag-txt.png (content/images/tag-txt.png)
+ content/editor/images/tag-u.png (content/images/tag-u.png)
+ content/editor/images/tag-ul.png (content/images/tag-ul.png)
+ content/editor/images/tag-userdefined.png (content/images/tag-userdefined.png)
+ content/editor/images/tag-var.png (content/images/tag-var.png)
+ content/editor/images/tag-xmp.png (content/images/tag-xmp.png)
diff --git a/comm/suite/editor/base/moz.build b/comm/suite/editor/base/moz.build
new file mode 100644
index 0000000000..de5cd1bf81
--- /dev/null
+++ b/comm/suite/editor/base/moz.build
@@ -0,0 +1,6 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ["jar.mn"]