summaryrefslogtreecommitdiffstats
path: root/comm/suite/editor
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
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')
-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
-rw-r--r--comm/suite/editor/components/dialogs/content/EdAEAttributes.js973
-rw-r--r--comm/suite/editor/components/dialogs/content/EdAECSSAttributes.js146
-rw-r--r--comm/suite/editor/components/dialogs/content/EdAEHTMLAttributes.js367
-rw-r--r--comm/suite/editor/components/dialogs/content/EdAEJSEAttributes.js200
-rw-r--r--comm/suite/editor/components/dialogs/content/EdAdvancedEdit.js342
-rw-r--r--comm/suite/editor/components/dialogs/content/EdAdvancedEdit.xhtml182
-rw-r--r--comm/suite/editor/components/dialogs/content/EdButtonProps.js146
-rw-r--r--comm/suite/editor/components/dialogs/content/EdButtonProps.xhtml92
-rw-r--r--comm/suite/editor/components/dialogs/content/EdColorPicker.js297
-rw-r--r--comm/suite/editor/components/dialogs/content/EdColorPicker.xhtml56
-rw-r--r--comm/suite/editor/components/dialogs/content/EdColorProps.js476
-rw-r--r--comm/suite/editor/components/dialogs/content/EdColorProps.xhtml134
-rw-r--r--comm/suite/editor/components/dialogs/content/EdConvertToTable.js326
-rw-r--r--comm/suite/editor/components/dialogs/content/EdConvertToTable.xhtml43
-rw-r--r--comm/suite/editor/components/dialogs/content/EdDialogCommon.js1038
-rw-r--r--comm/suite/editor/components/dialogs/content/EdDialogTemplate.js45
-rw-r--r--comm/suite/editor/components/dialogs/content/EdDialogTemplate.xhtml23
-rw-r--r--comm/suite/editor/components/dialogs/content/EdDictionary.js164
-rw-r--r--comm/suite/editor/components/dialogs/content/EdDictionary.xhtml59
-rw-r--r--comm/suite/editor/components/dialogs/content/EdFieldSetProps.js196
-rw-r--r--comm/suite/editor/components/dialogs/content/EdFieldSetProps.xhtml67
-rw-r--r--comm/suite/editor/components/dialogs/content/EdFormProps.js136
-rw-r--r--comm/suite/editor/components/dialogs/content/EdFormProps.xhtml98
-rw-r--r--comm/suite/editor/components/dialogs/content/EdHLineProps.js227
-rw-r--r--comm/suite/editor/components/dialogs/content/EdHLineProps.xhtml80
-rw-r--r--comm/suite/editor/components/dialogs/content/EdImageDialog.js661
-rwxr-xr-xcomm/suite/editor/components/dialogs/content/EdImageLinkLoader.js145
-rw-r--r--comm/suite/editor/components/dialogs/content/EdImageProps.js293
-rw-r--r--comm/suite/editor/components/dialogs/content/EdImageProps.xhtml116
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInputImage.js189
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInputImage.xhtml104
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInputProps.js345
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInputProps.xhtml135
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsSrc.js160
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsSrc.xhtml42
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsertChars.js409
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsertChars.xhtml55
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsertMath.js330
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsertMath.xhtml60
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsertTOC.js378
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsertTOC.xhtml225
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsertTable.js254
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsertTable.xhtml82
-rw-r--r--comm/suite/editor/components/dialogs/content/EdLabelProps.js118
-rw-r--r--comm/suite/editor/components/dialogs/content/EdLabelProps.xhtml66
-rw-r--r--comm/suite/editor/components/dialogs/content/EdLinkProps.js331
-rw-r--r--comm/suite/editor/components/dialogs/content/EdLinkProps.xhtml79
-rw-r--r--comm/suite/editor/components/dialogs/content/EdListProps.js455
-rw-r--r--comm/suite/editor/components/dialogs/content/EdListProps.xhtml73
-rw-r--r--comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.js159
-rw-r--r--comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.xhtml43
-rw-r--r--comm/suite/editor/components/dialogs/content/EdPageProps.js159
-rw-r--r--comm/suite/editor/components/dialogs/content/EdPageProps.xhtml50
-rw-r--r--comm/suite/editor/components/dialogs/content/EdReplace.js382
-rw-r--r--comm/suite/editor/components/dialogs/content/EdReplace.xhtml65
-rw-r--r--comm/suite/editor/components/dialogs/content/EdSelectProps.js770
-rw-r--r--comm/suite/editor/components/dialogs/content/EdSelectProps.xhtml143
-rw-r--r--comm/suite/editor/components/dialogs/content/EdSnapToGrid.js62
-rw-r--r--comm/suite/editor/components/dialogs/content/EdSnapToGrid.xhtml47
-rw-r--r--comm/suite/editor/components/dialogs/content/EdSpellCheck.js495
-rw-r--r--comm/suite/editor/components/dialogs/content/EdSpellCheck.xhtml113
-rw-r--r--comm/suite/editor/components/dialogs/content/EdTableProps.js1439
-rw-r--r--comm/suite/editor/components/dialogs/content/EdTableProps.xhtml287
-rw-r--r--comm/suite/editor/components/dialogs/content/EdTextAreaProps.js171
-rw-r--r--comm/suite/editor/components/dialogs/content/EdTextAreaProps.xhtml115
-rw-r--r--comm/suite/editor/components/dialogs/content/EditConflict.js42
-rw-r--r--comm/suite/editor/components/dialogs/content/EditConflict.xhtml40
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorPublish.js558
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorPublish.xhtml132
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorPublishOverlay.xhtml66
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorPublishProgress.js391
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorPublishProgress.xhtml66
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorPublishSettings.js343
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorPublishSettings.xhtml50
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.js155
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.xhtml46
-rw-r--r--comm/suite/editor/components/dialogs/content/edImage.inc.xhtml248
-rw-r--r--comm/suite/editor/components/dialogs/jar.mn82
-rw-r--r--comm/suite/editor/components/dialogs/moz.build6
-rw-r--r--comm/suite/editor/components/moz.build10
-rw-r--r--comm/suite/editor/components/prefs/content/editorPrefsOverlay.xhtml50
-rw-r--r--comm/suite/editor/components/prefs/content/pref-composer.xhtml84
-rw-r--r--comm/suite/editor/components/prefs/content/pref-editing.js187
-rw-r--r--comm/suite/editor/components/prefs/content/pref-editing.xhtml181
-rw-r--r--comm/suite/editor/components/prefs/jar.mn11
-rw-r--r--comm/suite/editor/components/prefs/moz.build6
-rw-r--r--comm/suite/editor/components/texzilla/content/TeXZilla.js339
-rw-r--r--comm/suite/editor/components/texzilla/jar.mn6
-rw-r--r--comm/suite/editor/components/texzilla/moz.build6
-rw-r--r--comm/suite/editor/modules/editorUtilities.jsm12
-rw-r--r--comm/suite/editor/moz.build22
-rw-r--r--comm/suite/editor/nsComposerCmdLineHandler.js64
-rw-r--r--comm/suite/editor/nsComposerCmdLineHandler.manifest3
-rw-r--r--comm/suite/editor/profile/composer.js70
213 files changed, 32335 insertions, 0 deletions
diff --git a/comm/suite/editor/base/content/ComposerCommands.js b/comm/suite/editor/base/content/ComposerCommands.js
new file mode 100644
index 0000000000..daff0d4563
--- /dev/null
+++ b/comm/suite/editor/base/content/ComposerCommands.js
@@ -0,0 +1,4051 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Implementations of nsIControllerCommand for composer commands */
+
+// Linting is disabled in chunks of this file because it contains code that never
+// runs in Thunderbird, and references things that don't exist in Thunderbird.
+
+/* import-globals-from editor.js */
+/* import-globals-from editorUtilities.js */
+/* globals CreatePublishDataFromUrl editPage FormatDirForPublishing getTopWin
+ goPreferences nsIPromptService openComposeWindow openNewPrivateWith
+ PrintPreviewListener SavePublishDataToPrefs SavePassword savePWObj */
+
+var gComposerJSCommandControllerID = 0;
+
+function SetupHTMLEditorCommands() {
+ var commandTable = GetComposerCommandTable();
+ if (!commandTable) {
+ return;
+ }
+
+ // Include everything a text editor does
+ SetupTextEditorCommands();
+
+ // dump("Registering HTML editor commands\n");
+
+ commandTable.registerCommand("cmd_renderedHTMLEnabler", nsDummyHTMLCommand);
+
+ commandTable.registerCommand("cmd_grid", nsGridCommand);
+
+ commandTable.registerCommand("cmd_listProperties", nsListPropertiesCommand);
+ commandTable.registerCommand("cmd_pageProperties", nsPagePropertiesCommand);
+ commandTable.registerCommand("cmd_colorProperties", nsColorPropertiesCommand);
+ commandTable.registerCommand("cmd_increaseFontStep", nsIncreaseFontCommand);
+ commandTable.registerCommand("cmd_decreaseFontStep", nsDecreaseFontCommand);
+ commandTable.registerCommand(
+ "cmd_advancedProperties",
+ nsAdvancedPropertiesCommand
+ );
+ commandTable.registerCommand(
+ "cmd_objectProperties",
+ nsObjectPropertiesCommand
+ );
+ commandTable.registerCommand(
+ "cmd_removeNamedAnchors",
+ nsRemoveNamedAnchorsCommand
+ );
+ commandTable.registerCommand("cmd_editLink", nsEditLinkCommand);
+
+ commandTable.registerCommand("cmd_form", nsFormCommand);
+ commandTable.registerCommand("cmd_inputtag", nsInputTagCommand);
+ commandTable.registerCommand("cmd_inputimage", nsInputImageCommand);
+ commandTable.registerCommand("cmd_textarea", nsTextAreaCommand);
+ commandTable.registerCommand("cmd_select", nsSelectCommand);
+ commandTable.registerCommand("cmd_button", nsButtonCommand);
+ commandTable.registerCommand("cmd_label", nsLabelCommand);
+ commandTable.registerCommand("cmd_fieldset", nsFieldSetCommand);
+ commandTable.registerCommand("cmd_image", nsImageCommand);
+ commandTable.registerCommand("cmd_hline", nsHLineCommand);
+ commandTable.registerCommand("cmd_link", nsLinkCommand);
+ commandTable.registerCommand("cmd_anchor", nsAnchorCommand);
+ commandTable.registerCommand(
+ "cmd_insertHTMLWithDialog",
+ nsInsertHTMLWithDialogCommand
+ );
+ commandTable.registerCommand(
+ "cmd_insertMathWithDialog",
+ nsInsertMathWithDialogCommand
+ );
+ commandTable.registerCommand("cmd_insertBreak", nsInsertBreakCommand);
+ commandTable.registerCommand("cmd_insertBreakAll", nsInsertBreakAllCommand);
+
+ commandTable.registerCommand("cmd_table", nsInsertOrEditTableCommand);
+ commandTable.registerCommand("cmd_editTable", nsEditTableCommand);
+ commandTable.registerCommand("cmd_SelectTable", nsSelectTableCommand);
+ commandTable.registerCommand("cmd_SelectRow", nsSelectTableRowCommand);
+ commandTable.registerCommand("cmd_SelectColumn", nsSelectTableColumnCommand);
+ commandTable.registerCommand("cmd_SelectCell", nsSelectTableCellCommand);
+ commandTable.registerCommand(
+ "cmd_SelectAllCells",
+ nsSelectAllTableCellsCommand
+ );
+ commandTable.registerCommand("cmd_InsertTable", nsInsertTableCommand);
+ commandTable.registerCommand(
+ "cmd_InsertRowAbove",
+ nsInsertTableRowAboveCommand
+ );
+ commandTable.registerCommand(
+ "cmd_InsertRowBelow",
+ nsInsertTableRowBelowCommand
+ );
+ commandTable.registerCommand(
+ "cmd_InsertColumnBefore",
+ nsInsertTableColumnBeforeCommand
+ );
+ commandTable.registerCommand(
+ "cmd_InsertColumnAfter",
+ nsInsertTableColumnAfterCommand
+ );
+ commandTable.registerCommand(
+ "cmd_InsertCellBefore",
+ nsInsertTableCellBeforeCommand
+ );
+ commandTable.registerCommand(
+ "cmd_InsertCellAfter",
+ nsInsertTableCellAfterCommand
+ );
+ commandTable.registerCommand("cmd_DeleteTable", nsDeleteTableCommand);
+ commandTable.registerCommand("cmd_DeleteRow", nsDeleteTableRowCommand);
+ commandTable.registerCommand("cmd_DeleteColumn", nsDeleteTableColumnCommand);
+ commandTable.registerCommand("cmd_DeleteCell", nsDeleteTableCellCommand);
+ commandTable.registerCommand(
+ "cmd_DeleteCellContents",
+ nsDeleteTableCellContentsCommand
+ );
+ commandTable.registerCommand("cmd_JoinTableCells", nsJoinTableCellsCommand);
+ commandTable.registerCommand("cmd_SplitTableCell", nsSplitTableCellCommand);
+ commandTable.registerCommand(
+ "cmd_TableOrCellColor",
+ nsTableOrCellColorCommand
+ );
+ commandTable.registerCommand("cmd_NormalizeTable", nsNormalizeTableCommand);
+ commandTable.registerCommand("cmd_smiley", nsSetSmiley);
+ commandTable.registerCommand("cmd_ConvertToTable", nsConvertToTable);
+}
+
+function SetupTextEditorCommands() {
+ var commandTable = GetComposerCommandTable();
+ if (!commandTable) {
+ return;
+ }
+
+ // dump("Registering plain text editor commands\n");
+
+ commandTable.registerCommand("cmd_findReplace", nsFindReplaceCommand);
+ commandTable.registerCommand("cmd_find", nsFindCommand);
+ commandTable.registerCommand("cmd_findNext", nsFindAgainCommand);
+ commandTable.registerCommand("cmd_findPrev", nsFindAgainCommand);
+ commandTable.registerCommand("cmd_rewrap", nsRewrapCommand);
+ commandTable.registerCommand("cmd_spelling", nsSpellingCommand);
+ commandTable.registerCommand("cmd_validate", nsValidateCommand);
+ commandTable.registerCommand("cmd_insertChars", nsInsertCharsCommand);
+}
+
+function SetupComposerWindowCommands() {
+ // Don't need to do this if already done
+ if (gComposerWindowControllerID) {
+ return;
+ }
+
+ // Create a command controller and register commands
+ // specific to Web Composer window (file-related commands, HTML Source...)
+ // We can't use the composer controller created on the content window else
+ // we can't process commands when in HTMLSource editor
+ // IMPORTANT: For each of these commands, the doCommand method
+ // must first call SetEditMode(gPreviousNonSourceDisplayMode);
+ // to go from HTML Source mode to any other edit mode
+
+ var windowControllers = window.controllers;
+
+ if (!windowControllers) {
+ return;
+ }
+
+ var commandTable;
+ var composerController;
+ var editorController;
+ try {
+ composerController = Cc[
+ "@mozilla.org/embedcomp/base-command-controller;1"
+ ].createInstance();
+
+ editorController = composerController.QueryInterface(
+ Ci.nsIControllerContext
+ );
+
+ // Get the nsIControllerCommandTable interface we need to register commands
+ var interfaceRequestor = composerController.QueryInterface(
+ Ci.nsIInterfaceRequestor
+ );
+ commandTable = interfaceRequestor.getInterface(
+ Ci.nsIControllerCommandTable
+ );
+ } catch (e) {
+ dump("Failed to create composerController\n");
+ return;
+ }
+
+ if (!commandTable) {
+ dump("Failed to get interface for nsIControllerCommandManager\n");
+ return;
+ }
+
+ // File-related commands
+ commandTable.registerCommand("cmd_open", nsOpenCommand);
+ commandTable.registerCommand("cmd_save", nsSaveCommand);
+ commandTable.registerCommand("cmd_saveAs", nsSaveAsCommand);
+ commandTable.registerCommand("cmd_exportToText", nsExportToTextCommand);
+ commandTable.registerCommand(
+ "cmd_saveAndChangeEncoding",
+ nsSaveAndChangeEncodingCommand
+ );
+ commandTable.registerCommand("cmd_publish", nsPublishCommand);
+ commandTable.registerCommand("cmd_publishAs", nsPublishAsCommand);
+ commandTable.registerCommand("cmd_publishSettings", nsPublishSettingsCommand);
+ commandTable.registerCommand("cmd_revert", nsRevertCommand);
+ commandTable.registerCommand("cmd_openRemote", nsOpenRemoteCommand);
+ commandTable.registerCommand("cmd_preview", nsPreviewCommand);
+ commandTable.registerCommand("cmd_editSendPage", nsSendPageCommand);
+ commandTable.registerCommand("cmd_print", nsPrintCommand);
+ commandTable.registerCommand("cmd_printpreview", nsPrintPreviewCommand);
+ commandTable.registerCommand("cmd_printSetup", nsPrintSetupCommand);
+ commandTable.registerCommand("cmd_close", nsCloseCommand);
+ commandTable.registerCommand("cmd_preferences", nsPreferencesCommand);
+
+ // Edit Mode commands
+ if (GetCurrentEditorType() == "html") {
+ commandTable.registerCommand("cmd_NormalMode", nsNormalModeCommand);
+ commandTable.registerCommand("cmd_AllTagsMode", nsAllTagsModeCommand);
+ commandTable.registerCommand("cmd_HTMLSourceMode", nsHTMLSourceModeCommand);
+ commandTable.registerCommand("cmd_PreviewMode", nsPreviewModeCommand);
+ commandTable.registerCommand("cmd_FinishHTMLSource", nsFinishHTMLSource);
+ commandTable.registerCommand("cmd_CancelHTMLSource", nsCancelHTMLSource);
+ commandTable.registerCommand(
+ "cmd_updateStructToolbar",
+ nsUpdateStructToolbarCommand
+ );
+ }
+
+ windowControllers.insertControllerAt(0, editorController);
+
+ // Store the controller ID so we can be sure to get the right one later
+ gComposerWindowControllerID = windowControllers.getControllerId(
+ editorController
+ );
+}
+
+function GetComposerCommandTable() {
+ var controller;
+ if (gComposerJSCommandControllerID) {
+ try {
+ controller = window.content.controllers.getControllerById(
+ gComposerJSCommandControllerID
+ );
+ } catch (e) {}
+ }
+ if (!controller) {
+ // create it
+ controller = Cc[
+ "@mozilla.org/embedcomp/base-command-controller;1"
+ ].createInstance();
+
+ var editorController = controller.QueryInterface(Ci.nsIControllerContext);
+ editorController.setCommandContext(GetCurrentEditorElement());
+ window.content.controllers.insertControllerAt(0, controller);
+
+ // Store the controller ID so we can be sure to get the right one later
+ gComposerJSCommandControllerID = window.content.controllers.getControllerId(
+ controller
+ );
+ }
+
+ if (controller) {
+ var interfaceRequestor = controller.QueryInterface(
+ Ci.nsIInterfaceRequestor
+ );
+ return interfaceRequestor.getInterface(Ci.nsIControllerCommandTable);
+ }
+ return null;
+}
+
+/* eslint-disable complexity */
+function goUpdateCommandState(command) {
+ try {
+ var controller = top.document.commandDispatcher.getControllerForCommand(
+ command
+ );
+ if (!(controller instanceof Ci.nsICommandController)) {
+ return;
+ }
+
+ var params = newCommandParams();
+ if (!params) {
+ return;
+ }
+
+ controller.getCommandStateWithParams(command, params);
+
+ switch (command) {
+ case "cmd_bold":
+ case "cmd_italic":
+ case "cmd_underline":
+ case "cmd_var":
+ case "cmd_samp":
+ case "cmd_code":
+ case "cmd_acronym":
+ case "cmd_abbr":
+ case "cmd_cite":
+ case "cmd_strong":
+ case "cmd_em":
+ case "cmd_superscript":
+ case "cmd_subscript":
+ case "cmd_strikethrough":
+ case "cmd_tt":
+ case "cmd_nobreak":
+ case "cmd_ul":
+ case "cmd_ol":
+ pokeStyleUI(command, params.getBooleanValue("state_all"));
+ break;
+
+ case "cmd_paragraphState":
+ case "cmd_align":
+ case "cmd_highlight":
+ case "cmd_backgroundColor":
+ case "cmd_fontColor":
+ case "cmd_fontFace":
+ case "cmd_fontSize":
+ case "cmd_absPos":
+ pokeMultiStateUI(command, params);
+ break;
+
+ case "cmd_decreaseZIndex":
+ case "cmd_increaseZIndex":
+ case "cmd_indent":
+ case "cmd_outdent":
+ case "cmd_increaseFont":
+ case "cmd_decreaseFont":
+ case "cmd_increaseFontStep":
+ case "cmd_decreaseFontStep":
+ case "cmd_removeStyles":
+ case "cmd_smiley":
+ break;
+
+ default:
+ dump("no update for command: " + command + "\n");
+ }
+ } catch (e) {
+ dump(
+ "An error occurred updating the " + command + " command: \n" + e + "\n"
+ );
+ }
+}
+/* eslint-enable complexity */
+
+function goUpdateComposerMenuItems(commandset) {
+ // dump("Updating commands for " + commandset.id + "\n");
+
+ for (var i = 0; i < commandset.childNodes.length; i++) {
+ var commandNode = commandset.childNodes[i];
+ var commandID = commandNode.id;
+ if (commandID) {
+ goUpdateCommand(commandID); // enable or disable
+ if (commandNode.hasAttribute("state")) {
+ goUpdateCommandState(commandID);
+ }
+ }
+ }
+}
+
+function goDoCommandParams(command, params) {
+ try {
+ var controller = top.document.commandDispatcher.getControllerForCommand(
+ command
+ );
+ if (controller && controller.isCommandEnabled(command)) {
+ if (controller instanceof Ci.nsICommandController) {
+ controller.doCommandWithParams(command, params);
+
+ // the following two lines should be removed when we implement observers
+ if (params) {
+ controller.getCommandStateWithParams(command, params);
+ }
+ } else {
+ controller.doCommand(command);
+ }
+ ResetStructToolbar();
+ }
+ } catch (e) {
+ dump("An error occurred executing the " + command + " command\n");
+ }
+}
+
+function pokeStyleUI(uiID, aDesiredState) {
+ try {
+ var commandNode = top.document.getElementById(uiID);
+ if (!commandNode) {
+ return;
+ }
+
+ var uiState = "true" == commandNode.getAttribute("state");
+ if (aDesiredState != uiState) {
+ commandNode.setAttribute("state", aDesiredState ? "true" : "false");
+ }
+ } catch (e) {
+ dump("poking UI for " + uiID + " failed: " + e + "\n");
+ }
+}
+
+function doStyleUICommand(cmdStr) {
+ try {
+ var cmdParams = newCommandParams();
+ goDoCommandParams(cmdStr, cmdParams);
+ if (cmdParams) {
+ pokeStyleUI(cmdStr, cmdParams.getBooleanValue("state_all"));
+ }
+
+ ResetStructToolbar();
+ } catch (e) {}
+}
+
+// Copied from jsmime.js.
+function stringToTypedArray(buffer) {
+ var typedarray = new Uint8Array(buffer.length);
+ for (var i = 0; i < buffer.length; i++) {
+ typedarray[i] = buffer.charCodeAt(i);
+ }
+ return typedarray;
+}
+
+function pokeMultiStateUI(uiID, cmdParams) {
+ try {
+ var commandNode = document.getElementById(uiID);
+ if (!commandNode) {
+ return;
+ }
+
+ var isMixed = cmdParams.getBooleanValue("state_mixed");
+ var desiredAttrib;
+ if (isMixed) {
+ desiredAttrib = "mixed";
+ } else {
+ var valuetype = cmdParams.getValueType("state_attribute");
+ if (valuetype == Ci.nsICommandParams.eStringType) {
+ desiredAttrib = cmdParams.getCStringValue("state_attribute");
+ // Decode UTF-8, for example for font names in Japanese.
+ desiredAttrib = new TextDecoder("UTF-8").decode(
+ stringToTypedArray(desiredAttrib)
+ );
+ } else {
+ desiredAttrib = cmdParams.getStringValue("state_attribute");
+ }
+ }
+
+ var uiState = commandNode.getAttribute("state");
+ if (desiredAttrib != uiState) {
+ commandNode.setAttribute("state", desiredAttrib);
+ }
+ } catch (e) {}
+}
+
+function doStatefulCommand(commandID, newState) {
+ var commandNode = document.getElementById(commandID);
+ if (commandNode) {
+ commandNode.setAttribute("state", newState);
+ }
+ gContentWindow.focus(); // needed for command dispatch to work
+
+ try {
+ var cmdParams = newCommandParams();
+ if (!cmdParams) {
+ return;
+ }
+
+ cmdParams.setStringValue("state_attribute", newState);
+ goDoCommandParams(commandID, cmdParams);
+
+ pokeMultiStateUI(commandID, cmdParams);
+
+ ResetStructToolbar();
+ } catch (e) {
+ dump("error thrown in doStatefulCommand: " + e + "\n");
+ }
+}
+
+function PrintObject(obj) {
+ dump("-----" + obj + "------\n");
+ var names = "";
+ for (var i in obj) {
+ if (i == "value") {
+ names += i + ": " + obj.value + "\n";
+ } else if (i == "id") {
+ names += i + ": " + obj.id + "\n";
+ } else {
+ names += i + "\n";
+ }
+ }
+
+ dump(names + "-----------\n");
+}
+
+function PrintNodeID(id) {
+ PrintObject(document.getElementById(id));
+}
+
+var nsDummyHTMLCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // do nothing
+ dump("Hey, who's calling the dummy command?\n");
+ },
+};
+
+var nsOpenCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ // We can always do this.
+ return true;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var fileType = IsHTMLEditor() ? "html" : "text";
+ var title = GetString(IsHTMLEditor() ? "OpenHTMLFile" : "OpenTextFile");
+
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ fp.init(window, title, nsIFilePicker.modeOpen);
+
+ SetFilePickerDirectory(fp, fileType);
+
+ // Direct user to prefer HTML files and/or text files depending on whether
+ // loading into Composer or Text editor, so we call separately to control
+ // the order of the filter list.
+ if (fileType == "html") {
+ fp.appendFilters(nsIFilePicker.filterHTML);
+ }
+ fp.appendFilters(nsIFilePicker.filterText);
+ fp.appendFilters(nsIFilePicker.filterAll);
+
+ fp.open(rv => {
+ if (rv == nsIFilePicker.returnCancel) {
+ return;
+ }
+ // editPage checks for already open window and activates it.
+ if (fp.fileURL.spec) {
+ SaveFilePickerDirectory(fp, fileType);
+ editPage(fp.fileURL.spec, fileType);
+ }
+ });
+ },
+};
+
+// STRUCTURE TOOLBAR
+//
+var nsUpdateStructToolbarCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ UpdateStructToolbar();
+ return true;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+ doCommand(aCommand) {},
+};
+
+// ******* File output commands and utilities ******** //
+var nsSaveCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ // Always allow saving when editing a remote document,
+ // otherwise the document modified state would prevent that
+ // when you first open a remote file.
+ try {
+ var docUrl = GetDocumentUrl();
+ return (
+ IsDocumentEditable() &&
+ (IsDocumentModified() ||
+ IsHTMLSourceChanged() ||
+ IsUrlAboutBlank(docUrl) ||
+ GetScheme(docUrl) != "file")
+ );
+ } catch (e) {
+ return false;
+ }
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var editor = GetCurrentEditor();
+ if (editor) {
+ if (IsHTMLEditor()) {
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ }
+ SaveDocument(
+ IsUrlAboutBlank(GetDocumentUrl()),
+ false,
+ editor.contentsMIMEType
+ );
+ }
+ },
+};
+
+var nsSaveAsCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var editor = GetCurrentEditor();
+ if (editor) {
+ if (IsHTMLEditor()) {
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ }
+ SaveDocument(true, false, editor.contentsMIMEType);
+ }
+ },
+};
+
+var nsExportToTextCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ if (GetCurrentEditor()) {
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ SaveDocument(true, true, "text/plain");
+ }
+ },
+};
+
+var nsSaveAndChangeEncodingCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ window.ok = false;
+ window.exportToText = false;
+ var oldTitle = GetDocumentTitle();
+ window.openDialog(
+ "chrome://editor/content/EditorSaveAsCharset.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal,resizable=yes"
+ );
+
+ if (GetDocumentTitle() != oldTitle) {
+ UpdateWindowTitle();
+ }
+
+ if (window.ok) {
+ if (window.exportToText) {
+ SaveDocument(true, true, "text/plain");
+ } else {
+ var editor = GetCurrentEditor();
+ SaveDocument(true, false, editor ? editor.contentsMIMEType : null);
+ }
+ }
+ },
+};
+
+var nsPublishCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ if (IsDocumentEditable()) {
+ // Always allow publishing when editing a local document,
+ // otherwise the document modified state would prevent that
+ // when you first open any local file.
+ try {
+ var docUrl = GetDocumentUrl();
+ return (
+ IsDocumentModified() ||
+ IsHTMLSourceChanged() ||
+ IsUrlAboutBlank(docUrl) ||
+ GetScheme(docUrl) == "file"
+ );
+ } catch (e) {
+ return false;
+ }
+ }
+ return false;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ if (GetCurrentEditor()) {
+ let docUrl = GetDocumentUrl();
+ let filename = GetFilename(docUrl);
+ let publishData;
+
+ // First check pref to always show publish dialog
+ let showPublishDialog = Services.prefs.getBoolPref(
+ "editor.always_show_publish_dialog"
+ );
+
+ if (!showPublishDialog && filename) {
+ // Try to get publish data from the document url
+ publishData = CreatePublishDataFromUrl(docUrl);
+
+ // If none, use default publishing site? Need a pref for this
+ // if (!publishData)
+ // publishData = GetPublishDataFromSiteName(GetDefaultPublishSiteName(), filename);
+ }
+
+ if (showPublishDialog || !publishData) {
+ // Show the publish dialog
+ publishData = {};
+ window.ok = false;
+ let oldTitle = GetDocumentTitle();
+ window.openDialog(
+ "chrome://editor/content/EditorPublish.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ "",
+ "",
+ publishData
+ );
+ if (GetDocumentTitle() != oldTitle) {
+ UpdateWindowTitle();
+ }
+
+ if (!window.ok) {
+ return false;
+ }
+ }
+ if (publishData) {
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ return Publish(publishData);
+ }
+ }
+ return false;
+ },
+};
+
+var nsPublishAsCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ if (GetCurrentEditor()) {
+ SetEditMode(gPreviousNonSourceDisplayMode);
+
+ window.ok = false;
+ var publishData = {};
+ var oldTitle = GetDocumentTitle();
+ window.openDialog(
+ "chrome://editor/content/EditorPublish.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ "",
+ "",
+ publishData
+ );
+ if (GetDocumentTitle() != oldTitle) {
+ UpdateWindowTitle();
+ }
+
+ if (window.ok) {
+ return Publish(publishData);
+ }
+ }
+ return false;
+ },
+};
+
+// ------- output utilities ----- //
+
+// returns a fileExtension string
+function GetExtensionBasedOnMimeType(aMIMEType) {
+ try {
+ var mimeService = null;
+ mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+
+ var fileExtension = mimeService.getPrimaryExtension(aMIMEType, null);
+
+ // the MIME service likes to give back ".htm" for text/html files,
+ // so do a special-case fix here.
+ if (fileExtension == "htm") {
+ fileExtension = "html";
+ }
+
+ return fileExtension;
+ } catch (e) {}
+ return "";
+}
+
+function GetSuggestedFileName(aDocumentURLString, aMIMEType) {
+ var extension = GetExtensionBasedOnMimeType(aMIMEType);
+ if (extension) {
+ extension = "." + extension;
+ }
+
+ // check for existing file name we can use
+ if (aDocumentURLString && !IsUrlAboutBlank(aDocumentURLString)) {
+ try {
+ let docURI = Services.io.newURI(
+ aDocumentURLString,
+ GetCurrentEditor().documentCharacterSet
+ );
+ docURI = docURI.QueryInterface(Ci.nsIURL);
+
+ // grab the file name
+ let url = validateFileName(decodeURIComponent(docURI.fileBaseName));
+ if (url) {
+ return url + extension;
+ }
+ } catch (e) {}
+ }
+
+ // Check if there is a title we can use to generate a valid filename,
+ // if we can't, use the default filename.
+ var title =
+ validateFileName(GetDocumentTitle()) ||
+ GetString("untitledDefaultFilename");
+ return title + extension;
+}
+
+/**
+ * @return {Promise} dialogResult
+ */
+function PromptForSaveLocation(
+ aDoSaveAsText,
+ aEditorType,
+ aMIMEType,
+ aDocumentURLString
+) {
+ var dialogResult = {};
+ dialogResult.filepickerClick = nsIFilePicker.returnCancel;
+ dialogResult.resultingURI = "";
+ dialogResult.resultingLocalFile = null;
+
+ var fp = null;
+ try {
+ fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ } catch (e) {}
+ if (!fp) {
+ return dialogResult;
+ }
+
+ // determine prompt string based on type of saving we'll do
+ var promptString;
+ if (aDoSaveAsText || aEditorType == "text") {
+ promptString = GetString("SaveTextAs");
+ } else {
+ promptString = GetString("SaveDocumentAs");
+ }
+
+ fp.init(window, promptString, nsIFilePicker.modeSave);
+
+ // Set filters according to the type of output
+ if (aDoSaveAsText) {
+ fp.appendFilters(nsIFilePicker.filterText);
+ } else {
+ fp.appendFilters(nsIFilePicker.filterHTML);
+ }
+ fp.appendFilters(nsIFilePicker.filterAll);
+
+ // now let's actually set the filepicker's suggested filename
+ var suggestedFileName = GetSuggestedFileName(aDocumentURLString, aMIMEType);
+ if (suggestedFileName) {
+ fp.defaultString = suggestedFileName;
+ }
+
+ // set the file picker's current directory
+ // assuming we have information needed (like prior saved location)
+ try {
+ var fileHandler = GetFileProtocolHandler();
+
+ var isLocalFile = true;
+ try {
+ let docURI = Services.io.newURI(
+ aDocumentURLString,
+ GetCurrentEditor().documentCharacterSet
+ );
+ isLocalFile = docURI.schemeIs("file");
+ } catch (e) {}
+
+ var parentLocation = null;
+ if (isLocalFile) {
+ var fileLocation = fileHandler.getFileFromURLSpec(aDocumentURLString); // this asserts if url is not local
+ parentLocation = fileLocation.parent;
+ }
+ if (parentLocation) {
+ // Save current filepicker's default location
+ if ("gFilePickerDirectory" in window) {
+ gFilePickerDirectory = fp.displayDirectory;
+ }
+
+ fp.displayDirectory = parentLocation;
+ } else {
+ // Initialize to the last-used directory for the particular type (saved in prefs)
+ SetFilePickerDirectory(fp, aEditorType);
+ }
+ } catch (e) {}
+
+ return new Promise(resolve => {
+ fp.open(rv => {
+ dialogResult.filepickerClick = rv;
+ if (rv != nsIFilePicker.returnCancel && fp.file) {
+ // Allow OK and replace.
+ // reset urlstring to new save location
+ dialogResult.resultingURIString = fileHandler.getURLSpecFromFile(
+ fp.file
+ );
+ dialogResult.resultingLocalFile = fp.file;
+ SaveFilePickerDirectory(fp, aEditorType);
+ resolve(dialogResult);
+ } else if ("gFilePickerDirectory" in window && gFilePickerDirectory) {
+ fp.displayDirectory = gFilePickerDirectory;
+ resolve(null);
+ }
+ });
+ });
+}
+
+/**
+ * If needed, prompt for document title and set the document title to the
+ * preferred value.
+ * @return true if the title was set up successfully;
+ * false if the user cancelled the title prompt
+ */
+function PromptAndSetTitleIfNone() {
+ if (GetDocumentTitle()) {
+ // we have a title; no need to prompt!
+ return true;
+ }
+
+ let result = { value: null };
+ let captionStr = GetString("DocumentTitle");
+ let msgStr = GetString("NeedDocTitle") + "\n" + GetString("DocTitleHelp");
+ let confirmed = Services.prompt.prompt(
+ window,
+ captionStr,
+ msgStr,
+ result,
+ null,
+ { value: 0 }
+ );
+ if (confirmed) {
+ SetDocumentTitle(TrimString(result.value));
+ }
+
+ return confirmed;
+}
+
+var gPersistObj;
+
+// Don't forget to do these things after calling OutputFileWithPersistAPI:
+// we need to update the uri before notifying listeners
+// if (doUpdateURI)
+// SetDocumentURI(docURI);
+// UpdateWindowTitle();
+// if (!aSaveCopy)
+// editor.resetModificationCount();
+// this should cause notification to listeners that document has changed
+
+const webPersist = Ci.nsIWebBrowserPersist;
+function OutputFileWithPersistAPI(
+ editorDoc,
+ aDestinationLocation,
+ aRelatedFilesParentDir,
+ aMimeType
+) {
+ gPersistObj = null;
+ var editor = GetCurrentEditor();
+ try {
+ editor.forceCompositionEnd();
+ } catch (e) {}
+
+ var isLocalFile = false;
+ try {
+ aDestinationLocation.QueryInterface(Ci.nsIFile);
+ isLocalFile = true;
+ } catch (e) {
+ try {
+ var tmp = aDestinationLocation.QueryInterface(Ci.nsIURI);
+ isLocalFile = tmp.schemeIs("file");
+ } catch (e) {}
+ }
+
+ try {
+ // we should supply a parent directory if/when we turn on functionality to save related documents
+ var persistObj = Cc[
+ "@mozilla.org/embedding/browser/nsWebBrowserPersist;1"
+ ].createInstance(webPersist);
+ persistObj.progressListener = gEditorOutputProgressListener;
+
+ var wrapColumn = GetWrapColumn();
+ var outputFlags = GetOutputFlags(aMimeType, wrapColumn);
+
+ // for 4.x parity as well as improving readability of file locally on server
+ // this will always send crlf for upload (http/ftp)
+ if (!isLocalFile) {
+ // if we aren't saving locally then send both cr and lf
+ outputFlags |=
+ webPersist.ENCODE_FLAGS_CR_LINEBREAKS |
+ webPersist.ENCODE_FLAGS_LF_LINEBREAKS;
+
+ // we want to serialize the output for all remote publishing
+ // some servers can handle only one connection at a time
+ // some day perhaps we can make this user-configurable per site?
+ persistObj.persistFlags =
+ persistObj.persistFlags | webPersist.PERSIST_FLAGS_SERIALIZE_OUTPUT;
+ }
+
+ // note: we always want to set the replace existing files flag since we have
+ // already given user the chance to not replace an existing file (file picker)
+ // or the user picked an option where the file is implicitly being replaced (save)
+ persistObj.persistFlags =
+ persistObj.persistFlags |
+ webPersist.PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS |
+ webPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
+ webPersist.PERSIST_FLAGS_DONT_FIXUP_LINKS |
+ webPersist.PERSIST_FLAGS_DONT_CHANGE_FILENAMES |
+ webPersist.PERSIST_FLAGS_FIXUP_ORIGINAL_DOM;
+ persistObj.saveDocument(
+ editorDoc,
+ aDestinationLocation,
+ aRelatedFilesParentDir,
+ aMimeType,
+ outputFlags,
+ wrapColumn
+ );
+ gPersistObj = persistObj;
+ } catch (e) {
+ dump("caught an error, bail\n");
+ return false;
+ }
+
+ return true;
+}
+
+// returns output flags based on mimetype, wrapCol and prefs
+function GetOutputFlags(aMimeType, aWrapColumn) {
+ var outputFlags = 0;
+ var editor = GetCurrentEditor();
+ var outputEntity =
+ editor && editor.documentCharacterSet == "ISO-8859-1"
+ ? webPersist.ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES
+ : webPersist.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES;
+ if (aMimeType == "text/plain") {
+ // When saving in "text/plain" format, always do formatting
+ outputFlags |= webPersist.ENCODE_FLAGS_FORMATTED;
+ } else {
+ // Should we prettyprint? Check the pref
+ if (Services.prefs.getBoolPref("editor.prettyprint")) {
+ outputFlags |= webPersist.ENCODE_FLAGS_FORMATTED;
+ }
+
+ try {
+ // How much entity names should we output? Check the pref
+ switch (Services.prefs.getCharPref("editor.encode_entity")) {
+ case "basic":
+ outputEntity = webPersist.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES;
+ break;
+ case "latin1":
+ outputEntity = webPersist.ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES;
+ break;
+ case "html":
+ outputEntity = webPersist.ENCODE_FLAGS_ENCODE_HTML_ENTITIES;
+ break;
+ case "none":
+ outputEntity = 0;
+ break;
+ }
+ } catch (e) {}
+ }
+ outputFlags |= outputEntity;
+
+ if (aWrapColumn > 0) {
+ outputFlags |= webPersist.ENCODE_FLAGS_WRAP;
+ }
+
+ return outputFlags;
+}
+
+// returns number of column where to wrap
+const nsIWebBrowserPersist = Ci.nsIWebBrowserPersist;
+function GetWrapColumn() {
+ try {
+ return GetCurrentEditor().wrapWidth;
+ } catch (e) {}
+ return 0;
+}
+
+const gShowDebugOutputStateChange = false;
+const gShowDebugOutputProgress = false;
+const gShowDebugOutputStatusChange = false;
+
+const gShowDebugOutputLocationChange = false;
+const gShowDebugOutputSecurityChange = false;
+
+const nsIWebProgressListener = Ci.nsIWebProgressListener;
+const nsIChannel = Ci.nsIChannel;
+
+const kErrorBindingAborted = 2152398850;
+const kErrorBindingRedirected = 2152398851;
+const kFileNotFound = 2152857618;
+
+var gEditorOutputProgressListener = {
+ /* eslint-disable complexity */
+ onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ var editor = GetCurrentEditor();
+
+ // Use this to access onStateChange flags
+ var requestSpec;
+ try {
+ var channel = aRequest.QueryInterface(nsIChannel);
+ requestSpec = StripUsernamePasswordFromURI(channel.URI);
+ } catch (e) {
+ if (gShowDebugOutputStateChange) {
+ dump("***** onStateChange; NO REQUEST CHANNEL\n");
+ }
+ }
+
+ var pubSpec;
+ if (gPublishData) {
+ pubSpec =
+ gPublishData.publishUrl + gPublishData.docDir + gPublishData.filename;
+ }
+
+ if (gShowDebugOutputStateChange) {
+ dump("\n***** onStateChange request: " + requestSpec + "\n");
+ dump(" state flags: ");
+
+ if (aStateFlags & nsIWebProgressListener.STATE_START) {
+ dump(" STATE_START, ");
+ }
+ if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
+ dump(" STATE_STOP, ");
+ }
+ if (aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+ dump(" STATE_IS_NETWORK ");
+ }
+
+ dump(
+ `\n * requestSpec=${requestSpec}, pubSpec=${pubSpec}, aStatus=${aStatus}\n`
+ );
+
+ DumpDebugStatus(aStatus);
+ }
+ // The rest only concerns publishing, so bail out if no dialog
+ if (!gProgressDialog) {
+ return;
+ }
+
+ // Detect start of file upload of any file:
+ // (We ignore any START messages after gPersistObj says publishing is finished
+ if (
+ aStateFlags & nsIWebProgressListener.STATE_START &&
+ gPersistObj &&
+ requestSpec &&
+ gPersistObj.currentState != gPersistObj.PERSIST_STATE_FINISHED
+ ) {
+ document
+ .getElementById("navigator-throbber")
+ .setAttribute("busy", "true");
+ try {
+ // Add url to progress dialog's list showing each file uploading
+ gProgressDialog.SetProgressStatus(GetFilename(requestSpec), "busy");
+ } catch (e) {}
+ }
+
+ // Detect end of file upload of any file:
+ if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
+ document.getElementById("navigator-throbber").removeAttribute("busy");
+ // ignore aStatus == kErrorBindingAborted; check http response for possible errors
+ try {
+ // check http channel for response: 200 range is ok; other ranges are not
+ var httpChannel = aRequest.QueryInterface(Ci.nsIHttpChannel);
+ var httpResponse = httpChannel.responseStatus;
+ if (httpResponse < 200 || httpResponse >= 300) {
+ // Not a real error but enough to pass check below.
+ aStatus = httpResponse;
+ } else if (aStatus == kErrorBindingAborted) {
+ aStatus = 0;
+ }
+
+ if (gShowDebugOutputStateChange) {
+ dump("http response is: " + httpResponse + "\n");
+ }
+ } catch (e) {
+ if (aStatus == kErrorBindingAborted) {
+ aStatus = 0;
+ }
+ }
+
+ // We abort publishing for all errors except if image src file is not found
+ var abortPublishing = aStatus != 0 && aStatus != kFileNotFound;
+
+ // Notify progress dialog when we receive the STOP
+ // notification for a file if there was an error
+ // or a successful finish
+ // (Check requestSpec to be sure message is for destination url)
+ if (
+ aStatus != 0 ||
+ (requestSpec &&
+ requestSpec.startsWith(GetScheme(gPublishData.publishUrl)))
+ ) {
+ try {
+ gProgressDialog.SetProgressFinished(
+ GetFilename(requestSpec),
+ aStatus
+ );
+ } catch (e) {}
+ }
+
+ if (abortPublishing) {
+ // Cancel publishing
+ gPersistObj.cancelSave();
+
+ // Don't do any commands after failure
+ gCommandAfterPublishing = null;
+
+ // Restore original document to undo image src url adjustments
+ if (gRestoreDocumentSource) {
+ try {
+ editor.rebuildDocumentFromSource(gRestoreDocumentSource);
+
+ // Clear transaction cache since we just did a potentially
+ // very large insert and this will eat up memory
+ editor.clearUndoRedo();
+ } catch (e) {}
+ }
+
+ // Notify progress dialog that we're finished
+ // and keep open to show error
+ gProgressDialog.SetProgressFinished(null, 0);
+
+ // We don't want to change location or reset mod count, etc.
+ return;
+ }
+
+ // XXX HACK: "file://" protocol is not supported in network code
+ // (bug 151867 filed to add this support, bug 151869 filed
+ // to remove this and other code in nsIWebBrowserPersist)
+ // nsIWebBrowserPersist *does* copy the file(s), but we don't
+ // get normal onStateChange messages.
+
+ // Case 1: If images are included, we get fairly normal
+ // STATE_START/STATE_STOP & STATE_IS_NETWORK messages associated with the image files,
+ // thus we must finish HTML file progress below
+
+ // Case 2: If just HTML file is uploaded, we get STATE_START and STATE_STOP
+ // notification with a null "requestSpec", and
+ // the gPersistObj is destroyed before we get here!
+ // So create an new object so we can flow through normal processing below
+ if (
+ !requestSpec &&
+ GetScheme(gPublishData.publishUrl) == "file" &&
+ (!gPersistObj ||
+ gPersistObj.currentState ==
+ nsIWebBrowserPersist.PERSIST_STATE_FINISHED)
+ ) {
+ aStateFlags |= nsIWebProgressListener.STATE_IS_NETWORK;
+ if (!gPersistObj) {
+ gPersistObj = {
+ result: aStatus,
+ currentState: nsIWebBrowserPersist.PERSIST_STATE_FINISHED,
+ };
+ }
+ }
+
+ // STATE_IS_NETWORK signals end of publishing, as does the gPersistObj.currentState
+ if (
+ aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK &&
+ gPersistObj.currentState == nsIWebBrowserPersist.PERSIST_STATE_FINISHED
+ ) {
+ if (GetScheme(gPublishData.publishUrl) == "file") {
+ // XXX "file://" hack: We don't get notified about the HTML file, so end progress for it
+ // (This covers both "Case 1 and 2" described above)
+ gProgressDialog.SetProgressFinished(
+ gPublishData.filename,
+ gPersistObj.result
+ );
+ }
+
+ if (gPersistObj.result == 0) {
+ // All files are finished and publishing succeeded (some images may have failed)
+ try {
+ // Make a new docURI from the "browse location" in case "publish location" was FTP
+ // We need to set document uri before notifying listeners
+ var docUrl = GetDocUrlFromPublishData(gPublishData);
+ SetDocumentURI(
+ Services.io.newURI(docUrl, editor.documentCharacterSet)
+ );
+
+ UpdateWindowTitle();
+
+ // this should cause notification to listeners that doc has changed
+ editor.resetModificationCount();
+
+ // Set UI based on whether we're editing a remote or local url
+ // Why is urlstring undefined?
+ /* eslint-disable-next-line no-undef */
+ SetSaveAndPublishUI(urlstring);
+ } catch (e) {}
+
+ // Save publishData to prefs
+ if (gPublishData) {
+ if (gPublishData.savePublishData) {
+ // We published successfully, so we can safely
+ // save docDir and otherDir to prefs
+ gPublishData.saveDirs = true;
+ SavePublishDataToPrefs(gPublishData);
+ } else {
+ SavePassword(gPublishData);
+ }
+ }
+
+ // Ask progress dialog to close, but it may not
+ // if user checked checkbox to keep it open
+ gProgressDialog.RequestCloseDialog();
+ } else {
+ // We previously aborted publishing because of error:
+ // Calling gPersistObj.cancelSave() resulted in a non-zero gPersistObj.result,
+ // so notify progress dialog we're finished
+ gProgressDialog.SetProgressFinished(null, 0);
+ }
+ }
+ }
+ },
+ /* eslint-enable complexity */
+
+ onProgressChange(
+ aWebProgress,
+ aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress
+ ) {
+ if (!gPersistObj) {
+ return;
+ }
+
+ if (gShowDebugOutputProgress) {
+ dump(
+ "\n onProgressChange: gPersistObj.result=" + gPersistObj.result + "\n"
+ );
+ try {
+ var channel = aRequest.QueryInterface(nsIChannel);
+ dump("***** onProgressChange request: " + channel.URI.spec + "\n");
+ } catch (e) {}
+ dump(
+ "***** self: " +
+ aCurSelfProgress +
+ " / " +
+ aMaxSelfProgress +
+ "\n"
+ );
+ dump(
+ "***** total: " +
+ aCurTotalProgress +
+ " / " +
+ aMaxTotalProgress +
+ "\n\n"
+ );
+
+ if (gPersistObj.currentState == gPersistObj.PERSIST_STATE_READY) {
+ dump(" Persister is ready to save data\n\n");
+ } else if (gPersistObj.currentState == gPersistObj.PERSIST_STATE_SAVING) {
+ dump(" Persister is saving data.\n\n");
+ } else if (
+ gPersistObj.currentState == gPersistObj.PERSIST_STATE_FINISHED
+ ) {
+ dump(" PERSISTER HAS FINISHED SAVING DATA\n\n\n");
+ }
+ }
+ },
+
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ if (gShowDebugOutputLocationChange) {
+ dump("***** onLocationChange: " + aLocation.spec + "\n");
+ try {
+ var channel = aRequest.QueryInterface(nsIChannel);
+ dump("***** request: " + channel.URI.spec + "\n");
+ } catch (e) {}
+ }
+ },
+
+ onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
+ if (gShowDebugOutputStatusChange) {
+ dump("***** onStatusChange: " + aMessage + "\n");
+ try {
+ var channel = aRequest.QueryInterface(nsIChannel);
+ dump("***** request: " + channel.URI.spec + "\n");
+ } catch (e) {
+ dump(" couldn't get request\n");
+ }
+
+ DumpDebugStatus(aStatus);
+
+ if (gPersistObj) {
+ if (gPersistObj.currentState == gPersistObj.PERSIST_STATE_READY) {
+ dump(" Persister is ready to save data\n\n");
+ } else if (
+ gPersistObj.currentState == gPersistObj.PERSIST_STATE_SAVING
+ ) {
+ dump(" Persister is saving data.\n\n");
+ } else if (
+ gPersistObj.currentState == gPersistObj.PERSIST_STATE_FINISHED
+ ) {
+ dump(" PERSISTER HAS FINISHED SAVING DATA\n\n\n");
+ }
+ }
+ }
+ },
+
+ onSecurityChange(aWebProgress, aRequest, state) {
+ if (gShowDebugOutputSecurityChange) {
+ try {
+ var channel = aRequest.QueryInterface(nsIChannel);
+ dump("***** onSecurityChange request: " + channel.URI.spec + "\n");
+ } catch (e) {}
+ }
+ },
+
+ onContentBlockingEvent(aWebProgress, aRequest, aEvent) {},
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ "nsIPrompt",
+ "nsIAuthPrompt",
+ ]),
+
+ // nsIPrompt
+ alert(dlgTitle, text) {
+ Services.prompt.alert(
+ gProgressDialog ? gProgressDialog : window,
+ dlgTitle,
+ text
+ );
+ },
+ alertCheck(dialogTitle, text, checkBoxLabel, checkObj) {
+ Services.prompt.alert(window, dialogTitle, text);
+ },
+ confirm(dlgTitle, text) {
+ return ConfirmWithTitle(dlgTitle, text, null, null);
+ },
+ confirmCheck(dlgTitle, text, checkBoxLabel, checkObj) {
+ Services.prompt.confirmEx(
+ window,
+ dlgTitle,
+ text,
+ nsIPromptService.STD_OK_CANCEL_BUTTONS,
+ "",
+ "",
+ "",
+ checkBoxLabel,
+ checkObj
+ );
+ },
+ confirmEx(
+ dlgTitle,
+ text,
+ btnFlags,
+ btn0Title,
+ btn1Title,
+ btn2Title,
+ checkBoxLabel,
+ checkVal
+ ) {
+ return Services.prompt.confirmEx(
+ window,
+ dlgTitle,
+ text,
+ btnFlags,
+ btn0Title,
+ btn1Title,
+ btn2Title,
+ checkBoxLabel,
+ checkVal
+ );
+ },
+
+ /** ***********************************************************************
+ * gEditorOutputProgressListener needs to implement both nsIPrompt *
+ * (providing alert) and nsIAuthPrompt (providing password saving). *
+ * Unfortunately, both interfaces specify prompt/promptPassword/ *
+ * promptUsernameAndPassword, albeit with conflicting method signatures. *
+ * Luckily, though, we only make use of their nsIAuthPrompt variants, *
+ * hence we can comment out the nsIPrompt ones here to avoid JavaScript *
+ * strict mode clutter. See bug 371174 for more information. *
+ *************************************************************************
+ prompt : function(dlgTitle, text, inoutText, checkBoxLabel, checkObj)
+ {
+ return Services.prompt.prompt(window, dlgTitle, text, inoutText, checkBoxLabel, checkObj);
+ },
+ promptPassword : function(dlgTitle, text, pwObj, checkBoxLabel, savePWObj)
+ {
+ var ret = false;
+ try {
+ // Note difference with nsIAuthPrompt::promptPassword, which has
+ // just "in" savePassword param, while nsIPrompt is "inout"
+ // Initialize with user's previous preference for this site
+ if (gPublishData)
+ savePWObj.value = gPublishData.savePassword;
+
+ ret = Services.prompt.promptPassword(gProgressDialog ? gProgressDialog : window,
+ dlgTitle, text, pwObj, checkBoxLabel, savePWObj);
+
+ if (!ret)
+ setTimeout(CancelPublishing, 0);
+
+ if (ret && gPublishData)
+ UpdateUsernamePasswordFromPrompt(gPublishData, gPublishData.username, pwObj.value, savePWObj.value);
+ } catch(e) {}
+
+ return ret;
+ },
+ promptUsernameAndPassword : function(dlgTitle, text, userObj, pwObj, checkBoxLabel, savePWObj)
+ {
+ var ret = PromptUsernameAndPassword(dlgTitle, text, savePWObj.value, userObj, pwObj);
+ if (!ret)
+ setTimeout(CancelPublishing, 0);
+
+ return ret;
+ },
+ *************************************************************************/
+
+ select(dlgTitle, text, selectList, outSelection) {
+ return Services.prompt.select(
+ window,
+ dlgTitle,
+ text,
+ selectList,
+ outSelection
+ );
+ },
+
+ // nsIAuthPrompt
+ prompt(dlgTitle, text, pwrealm, savePW, defaultText, result) {
+ var ret = Services.prompt.prompt(
+ gProgressDialog ? gProgressDialog : window,
+ dlgTitle,
+ text,
+ defaultText,
+ pwrealm,
+ savePWObj
+ );
+ if (!ret) {
+ setTimeout(CancelPublishing, 0);
+ }
+ return ret;
+ },
+
+ promptUsernameAndPassword(dlgTitle, text, pwrealm, savePW, userObj, pwObj) {
+ var ret = PromptUsernameAndPassword(dlgTitle, text, savePW, userObj, pwObj);
+ if (!ret) {
+ setTimeout(CancelPublishing, 0);
+ }
+ return ret;
+ },
+
+ promptPassword(dlgTitle, text, pwrealm, savePW, pwObj) {
+ var ret = false;
+ try {
+ // Note difference with nsIPrompt::promptPassword, which has
+ // "inout" savePassword param, while nsIAuthPrompt is just "in"
+ // Also nsIAuth doesn't supply "checkBoxLabel"
+ // Initialize with user's previous preference for this site
+ var savePWObj = { value: savePW };
+ // Initialize with user's previous preference for this site
+ if (gPublishData) {
+ savePWObj.value = gPublishData.savePassword;
+ }
+
+ ret = Services.prompt.promptPassword(
+ gProgressDialog ? gProgressDialog : window,
+ dlgTitle,
+ text,
+ pwObj,
+ GetString("SavePassword"),
+ savePWObj
+ );
+
+ if (!ret) {
+ setTimeout(CancelPublishing, 0);
+ }
+
+ if (ret && gPublishData) {
+ UpdateUsernamePasswordFromPrompt(
+ gPublishData,
+ gPublishData.username,
+ pwObj.value,
+ savePWObj.value
+ );
+ }
+ } catch (e) {}
+
+ return ret;
+ },
+};
+
+function PromptUsernameAndPassword(dlgTitle, text, savePW, userObj, pwObj) {
+ // HTTP prompts us twice even if user Cancels from 1st attempt!
+ // So never put up dialog if there's no publish data
+ if (!gPublishData) {
+ return false;
+ }
+
+ var ret = false;
+ try {
+ var savePWObj = { value: savePW };
+
+ // Initialize with user's previous preference for this site
+ if (gPublishData) {
+ // HTTP put uses this dialog if either username or password is bad,
+ // so prefill username input field with the previous value for modification
+ savePWObj.value = gPublishData.savePassword;
+ if (!userObj.value) {
+ userObj.value = gPublishData.username;
+ }
+ }
+
+ ret = Services.prompt.promptUsernameAndPassword(
+ gProgressDialog ? gProgressDialog : window,
+ dlgTitle,
+ text,
+ userObj,
+ pwObj,
+ GetString("SavePassword"),
+ savePWObj
+ );
+ if (ret && gPublishData) {
+ UpdateUsernamePasswordFromPrompt(
+ gPublishData,
+ userObj.value,
+ pwObj.value,
+ savePWObj.value
+ );
+ }
+ } catch (e) {}
+
+ return ret;
+}
+
+/* eslint-disable complexity */
+function DumpDebugStatus(aStatus) {
+ // see nsError.h and netCore.h and ftpCore.h
+
+ if (aStatus == kErrorBindingAborted) {
+ dump("***** status is NS_BINDING_ABORTED\n");
+ } else if (aStatus == kErrorBindingRedirected) {
+ dump("***** status is NS_BINDING_REDIRECTED\n");
+ } else if (aStatus == 2152398859) {
+ // in netCore.h 11
+ dump("***** status is ALREADY_CONNECTED\n");
+ } else if (aStatus == 2152398860) {
+ // in netCore.h 12
+ dump("***** status is NOT_CONNECTED\n");
+ } else if (aStatus == 2152398861) {
+ // in nsISocketTransportService.idl 13
+ dump("***** status is CONNECTION_REFUSED\n");
+ } else if (aStatus == 2152398862) {
+ // in nsISocketTransportService.idl 14
+ dump("***** status is NET_TIMEOUT\n");
+ } else if (aStatus == 2152398863) {
+ // in netCore.h 15
+ dump("***** status is IN_PROGRESS\n");
+ } else if (aStatus == 2152398864) {
+ // 0x804b0010 in netCore.h 16
+ dump("***** status is OFFLINE\n");
+ } else if (aStatus == 2152398865) {
+ // in netCore.h 17
+ dump("***** status is NO_CONTENT\n");
+ } else if (aStatus == 2152398866) {
+ // in netCore.h 18
+ dump("***** status is UNKNOWN_PROTOCOL\n");
+ } else if (aStatus == 2152398867) {
+ // in netCore.h 19
+ dump("***** status is PORT_ACCESS_NOT_ALLOWED\n");
+ } else if (aStatus == 2152398868) {
+ // in nsISocketTransportService.idl 20
+ dump("***** status is NET_RESET\n");
+ } else if (aStatus == 2152398869) {
+ // in ftpCore.h 21
+ dump("***** status is FTP_LOGIN\n");
+ } else if (aStatus == 2152398870) {
+ // in ftpCore.h 22
+ dump("***** status is FTP_CWD\n");
+ } else if (aStatus == 2152398871) {
+ // in ftpCore.h 23
+ dump("***** status is FTP_PASV\n");
+ } else if (aStatus == 2152398872) {
+ // in ftpCore.h 24
+ dump("***** status is FTP_PWD\n");
+ } else if (aStatus == 2152857601) {
+ dump("***** status is UNRECOGNIZED_PATH\n");
+ } else if (aStatus == 2152857602) {
+ dump("***** status is UNRESOLABLE SYMLINK\n");
+ } else if (aStatus == 2152857604) {
+ dump("***** status is UNKNOWN_TYPE\n");
+ } else if (aStatus == 2152857605) {
+ dump("***** status is DESTINATION_NOT_DIR\n");
+ } else if (aStatus == 2152857606) {
+ dump("***** status is TARGET_DOES_NOT_EXIST\n");
+ } else if (aStatus == 2152857608) {
+ dump("***** status is ALREADY_EXISTS\n");
+ } else if (aStatus == 2152857609) {
+ dump("***** status is INVALID_PATH\n");
+ } else if (aStatus == 2152857610) {
+ dump("***** status is DISK_FULL\n");
+ } else if (aStatus == 2152857612) {
+ dump("***** status is NOT_DIRECTORY\n");
+ } else if (aStatus == 2152857613) {
+ dump("***** status is IS_DIRECTORY\n");
+ } else if (aStatus == 2152857614) {
+ dump("***** status is IS_LOCKED\n");
+ } else if (aStatus == 2152857615) {
+ dump("***** status is TOO_BIG\n");
+ } else if (aStatus == 2152857616) {
+ dump("***** status is NO_DEVICE_SPACE\n");
+ } else if (aStatus == 2152857617) {
+ dump("***** status is NAME_TOO_LONG\n");
+ } else if (aStatus == 2152857618) {
+ // 80520012
+ dump("***** status is FILE_NOT_FOUND\n");
+ } else if (aStatus == 2152857619) {
+ dump("***** status is READ_ONLY\n");
+ } else if (aStatus == 2152857620) {
+ dump("***** status is DIR_NOT_EMPTY\n");
+ } else if (aStatus == 2152857621) {
+ dump("***** status is ACCESS_DENIED\n");
+ } else if (aStatus == 2152398878) {
+ dump("***** status is ? (No connection or time out?)\n");
+ } else {
+ dump("***** status is " + aStatus + "\n");
+ }
+}
+/* eslint-enable complexity */
+
+// Update any data that the user supplied in a prompt dialog
+function UpdateUsernamePasswordFromPrompt(
+ publishData,
+ username,
+ password,
+ savePassword
+) {
+ if (!publishData) {
+ return;
+ }
+
+ // Set flag to save publish data after publishing if it changed in dialog
+ // and the "SavePassword" checkbox was checked
+ // or we already had site data for this site
+ // (Thus we don't automatically create a site until user brings up Publish As dialog)
+ publishData.savePublishData =
+ (gPublishData.username != username || gPublishData.password != password) &&
+ (savePassword || !publishData.notInSiteData);
+
+ publishData.username = username;
+ publishData.password = password;
+ publishData.savePassword = savePassword;
+}
+
+const kSupportedTextMimeTypes = [
+ "text/plain",
+ "text/css",
+ "text/rdf",
+ "text/xsl",
+ "text/javascript", // obsolete type
+ "text/ecmascript", // obsolete type
+ "application/javascript",
+ "application/ecmascript",
+ "application/x-javascript", // obsolete type
+ "text/xul", // obsolete type
+ "application/vnd.mozilla.xul+xml", // obsolete type
+ "application/xhtml+xml",
+];
+
+function IsSupportedTextMimeType(aMimeType) {
+ for (var i = 0; i < kSupportedTextMimeTypes.length; i++) {
+ if (kSupportedTextMimeTypes[i] == aMimeType) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* eslint-disable complexity */
+// throws an error or returns true if user attempted save; false if user canceled save
+async function SaveDocument(aSaveAs, aSaveCopy, aMimeType) {
+ var editor = GetCurrentEditor();
+ if (!aMimeType || !editor) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED);
+ }
+
+ var editorDoc = editor.document;
+ if (!editorDoc) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED);
+ }
+
+ // if we don't have the right editor type bail (we handle text and html)
+ var editorType = GetCurrentEditorType();
+ if (!["text", "html", "htmlmail", "textmail"].includes(editorType)) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ }
+
+ var saveAsTextFile = IsSupportedTextMimeType(aMimeType);
+
+ // check if the file is to be saved is a format we don't understand; if so, bail
+ if (
+ aMimeType != kHTMLMimeType &&
+ aMimeType != kXHTMLMimeType &&
+ !saveAsTextFile
+ ) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ }
+
+ if (saveAsTextFile) {
+ aMimeType = "text/plain";
+ }
+
+ var urlstring = GetDocumentUrl();
+ var mustShowFileDialog =
+ aSaveAs || IsUrlAboutBlank(urlstring) || urlstring == "";
+
+ // If editing a remote URL, force SaveAs dialog
+ if (!mustShowFileDialog && GetScheme(urlstring) != "file") {
+ mustShowFileDialog = true;
+ }
+
+ var doUpdateURI = false;
+ var tempLocalFile = null;
+
+ if (mustShowFileDialog) {
+ try {
+ // Prompt for title if we are saving to HTML
+ if (!saveAsTextFile && editorType == "html") {
+ var userContinuing = PromptAndSetTitleIfNone(); // not cancel
+ if (!userContinuing) {
+ return false;
+ }
+ }
+
+ var dialogResult = await PromptForSaveLocation(
+ saveAsTextFile,
+ editorType,
+ aMimeType,
+ urlstring
+ );
+ if (!dialogResult) {
+ return false;
+ }
+
+ // What is this unused 'replacing' var supposed to be doing?
+ /* eslint-disable-next-line no-unused-vars */
+ var replacing =
+ dialogResult.filepickerClick == nsIFilePicker.returnReplace;
+
+ urlstring = dialogResult.resultingURIString;
+ tempLocalFile = dialogResult.resultingLocalFile;
+
+ // update the new URL for the webshell unless we are saving a copy
+ if (!aSaveCopy) {
+ doUpdateURI = true;
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ return false;
+ }
+ } // mustShowFileDialog
+
+ var success = true;
+ try {
+ // if somehow we didn't get a local file but we did get a uri,
+ // attempt to create the localfile if it's a "file" url
+ var docURI;
+ if (!tempLocalFile) {
+ docURI = Services.io.newURI(urlstring, editor.documentCharacterSet);
+
+ if (docURI.schemeIs("file")) {
+ var fileHandler = GetFileProtocolHandler();
+ tempLocalFile = fileHandler
+ .getFileFromURLSpec(urlstring)
+ .QueryInterface(Ci.nsIFile);
+ }
+ }
+
+ // this is the location where the related files will go
+ var relatedFilesDir = null;
+
+ // Only change links or move files if pref is set
+ // and we are saving to a new location
+ if (Services.prefs.getBoolPref("editor.save_associated_files") && aSaveAs) {
+ try {
+ if (tempLocalFile) {
+ // if we are saving to the same parent directory, don't set relatedFilesDir
+ // grab old location, chop off file
+ // grab new location, chop off file, compare
+ var oldLocation = GetDocumentUrl();
+ var oldLocationLastSlash = oldLocation.lastIndexOf("/");
+ if (oldLocationLastSlash != -1) {
+ oldLocation = oldLocation.slice(0, oldLocationLastSlash);
+ }
+
+ var relatedFilesDirStr = urlstring;
+ var newLocationLastSlash = relatedFilesDirStr.lastIndexOf("/");
+ if (newLocationLastSlash != -1) {
+ relatedFilesDirStr = relatedFilesDirStr.slice(
+ 0,
+ newLocationLastSlash
+ );
+ }
+ if (
+ oldLocation == relatedFilesDirStr ||
+ IsUrlAboutBlank(oldLocation)
+ ) {
+ relatedFilesDir = null;
+ } else {
+ relatedFilesDir = tempLocalFile.parent;
+ }
+ } else {
+ var lastSlash = urlstring.lastIndexOf("/");
+ if (lastSlash != -1) {
+ var relatedFilesDirString = urlstring.slice(0, lastSlash + 1); // include last slash
+ relatedFilesDir = Services.io.newURI(
+ relatedFilesDirString,
+ editor.documentCharacterSet
+ );
+ }
+ }
+ } catch (e) {
+ relatedFilesDir = null;
+ }
+ }
+
+ let destinationLocation = tempLocalFile ? tempLocalFile : docURI;
+
+ success = OutputFileWithPersistAPI(
+ editorDoc,
+ destinationLocation,
+ relatedFilesDir,
+ aMimeType
+ );
+ } catch (e) {
+ success = false;
+ }
+
+ if (success) {
+ try {
+ if (doUpdateURI) {
+ // If a local file, we must create a new uri from nsIFile
+ if (tempLocalFile) {
+ docURI = GetFileProtocolHandler().newFileURI(tempLocalFile);
+ }
+
+ // We need to set new document uri before notifying listeners
+ SetDocumentURI(docURI);
+ }
+
+ // Update window title to show possibly different filename
+ // This also covers problem that after undoing a title change,
+ // window title loses the extra [filename] part that this adds
+ UpdateWindowTitle();
+
+ if (!aSaveCopy) {
+ editor.resetModificationCount();
+ }
+ // this should cause notification to listeners that document has changed
+
+ // Set UI based on whether we're editing a remote or local url
+ SetSaveAndPublishUI(urlstring);
+ } catch (e) {}
+ } else {
+ Services.prompt.alert(
+ window,
+ GetString("SaveDocument"),
+ GetString("SaveFileFailed")
+ );
+ }
+ return success;
+}
+/* eslint-enable complexity */
+
+function SetDocumentURI(uri) {
+ try {
+ // XXX WE'LL NEED TO GET "CURRENT" CONTENT FRAME ONCE MULTIPLE EDITORS ARE ALLOWED
+ GetCurrentEditorElement().docShell.setCurrentURI(uri);
+ } catch (e) {
+ dump("SetDocumentURI:\n" + e + "\n");
+ }
+}
+
+// ------------------------------- Publishing
+var gPublishData;
+var gProgressDialog;
+var gCommandAfterPublishing = null;
+var gRestoreDocumentSource;
+
+function Publish(publishData) {
+ if (!publishData) {
+ return false;
+ }
+
+ // Set data in global for username password requests
+ // and to do "post saving" actions after monitoring nsIWebProgressListener messages
+ // and we are sure file transfer was successful
+ gPublishData = publishData;
+
+ gPublishData.docURI = CreateURIFromPublishData(publishData, true);
+ if (!gPublishData.docURI) {
+ Services.prompt.alert(
+ window,
+ GetString("Publish"),
+ GetString("PublishFailed")
+ );
+ return false;
+ }
+
+ if (gPublishData.publishOtherFiles) {
+ gPublishData.otherFilesURI = CreateURIFromPublishData(publishData, false);
+ } else {
+ gPublishData.otherFilesURI = null;
+ }
+
+ if (gShowDebugOutputStateChange) {
+ dump(
+ "\n *** publishData: PublishUrl=" +
+ publishData.publishUrl +
+ ", BrowseUrl=" +
+ publishData.browseUrl +
+ ", Username=" +
+ publishData.username +
+ ", Dir=" +
+ publishData.docDir +
+ ", Filename=" +
+ publishData.filename +
+ "\n"
+ );
+ dump(
+ " * gPublishData.docURI.spec w/o pass=" +
+ StripPassword(gPublishData.docURI.spec) +
+ ", PublishOtherFiles=" +
+ gPublishData.publishOtherFiles +
+ "\n"
+ );
+ }
+
+ // XXX Missing username will make FTP fail
+ // and it won't call us for prompt dialog (bug 132320)
+ // (It does prompt if just password is missing)
+ // So we should do the prompt ourselves before trying to publish
+ if (GetScheme(publishData.publishUrl) == "ftp" && !publishData.username) {
+ var message = GetString("PromptFTPUsernamePassword").replace(
+ /%host%/,
+ GetHost(publishData.publishUrl)
+ );
+ var savePWobj = { value: publishData.savePassword };
+ var userObj = { value: publishData.username };
+ var pwObj = { value: publishData.password };
+ if (
+ !PromptUsernameAndPassword(
+ GetString("Prompt"),
+ message,
+ savePWobj,
+ userObj,
+ pwObj
+ )
+ ) {
+ // User canceled out of dialog.
+ return false;
+ }
+
+ // Reset data in URI objects
+ gPublishData.docURI.username = publishData.username;
+ gPublishData.docURI.password = publishData.password;
+
+ if (gPublishData.otherFilesURI) {
+ gPublishData.otherFilesURI.username = publishData.username;
+ gPublishData.otherFilesURI.password = publishData.password;
+ }
+ }
+
+ try {
+ // We launch dialog as a dependent
+ // Don't allow editing document!
+ SetDocumentEditable(false);
+
+ // Start progress monitoring
+ gProgressDialog = window.openDialog(
+ "chrome://editor/content/EditorPublishProgress.xhtml",
+ "_blank",
+ "chrome,dependent,titlebar",
+ gPublishData,
+ gPersistObj
+ );
+ } catch (e) {}
+
+ // Network transfer is often too quick for the progress dialog to be initialized
+ // and we can completely miss messages for quickly-terminated bad URLs,
+ // so we can't call OutputFileWithPersistAPI right away.
+ // StartPublishing() is called at the end of the dialog's onload method
+ return true;
+}
+
+function StartPublishing() {
+ var editor = GetCurrentEditor();
+ if (editor && gPublishData && gPublishData.docURI && gProgressDialog) {
+ gRestoreDocumentSource = null;
+
+ // Save backup document since nsIWebBrowserPersist changes image src urls
+ // but we only need to do this if publishing images and other related files
+ if (gPublishData.otherFilesURI) {
+ try {
+ gRestoreDocumentSource = editor.outputToString(
+ editor.contentsMIMEType,
+ kOutputEncodeW3CEntities
+ );
+ } catch (e) {}
+ }
+
+ OutputFileWithPersistAPI(
+ editor.document,
+ gPublishData.docURI,
+ gPublishData.otherFilesURI,
+ editor.contentsMIMEType
+ );
+ return gPersistObj;
+ }
+ return null;
+}
+
+function CancelPublishing() {
+ try {
+ gPersistObj.cancelSave();
+ gProgressDialog.SetProgressStatusCancel();
+ } catch (e) {}
+
+ // If canceling publishing do not do any commands after this
+ gCommandAfterPublishing = null;
+
+ if (gProgressDialog) {
+ // Close Progress dialog
+ // (this will call FinishPublishing())
+ gProgressDialog.CloseDialog();
+ } else {
+ FinishPublishing();
+ }
+}
+
+function FinishPublishing() {
+ SetDocumentEditable(true);
+ gProgressDialog = null;
+ gPublishData = null;
+ gRestoreDocumentSource = null;
+
+ if (gCommandAfterPublishing) {
+ // Be sure to null out the global now in case of trouble when executing command
+ var command = gCommandAfterPublishing;
+ gCommandAfterPublishing = null;
+ goDoCommand(command);
+ }
+}
+
+// Create a nsIURI object filled in with all required publishing info
+function CreateURIFromPublishData(publishData, doDocUri) {
+ if (!publishData || !publishData.publishUrl) {
+ return null;
+ }
+
+ var URI;
+ try {
+ var spec = publishData.publishUrl;
+ if (doDocUri) {
+ spec += FormatDirForPublishing(publishData.docDir) + publishData.filename;
+ } else {
+ spec += FormatDirForPublishing(publishData.otherDir);
+ }
+
+ URI = Services.io.newURI(spec, GetCurrentEditor().documentCharacterSet);
+
+ if (publishData.username) {
+ URI.username = publishData.username;
+ }
+ if (publishData.password) {
+ URI.password = publishData.password;
+ }
+ } catch (e) {}
+
+ return URI;
+}
+
+// Resolve the correct "http:" document URL when publishing via ftp
+function GetDocUrlFromPublishData(publishData) {
+ if (!publishData || !publishData.filename || !publishData.publishUrl) {
+ return "";
+ }
+
+ // If user was previously editing an "ftp" url, then keep that as the new scheme
+ var url;
+
+ // Always use the "HTTP" address if available
+ // XXX Should we do some more validation here for bad urls???
+ // Let's at least check for a scheme!
+ if (!GetScheme(publishData.browseUrl)) {
+ url = publishData.publishUrl;
+ } else {
+ url = publishData.browseUrl;
+ }
+
+ url += FormatDirForPublishing(publishData.docDir) + publishData.filename;
+
+ if (GetScheme(url) == "ftp") {
+ url = InsertUsernameIntoUrl(url, publishData.username);
+ }
+
+ return url;
+}
+
+function SetSaveAndPublishUI(urlstring) {
+ // Be sure enabled state of toolbar buttons are correct
+ goUpdateCommand("cmd_save");
+ goUpdateCommand("cmd_publish");
+}
+
+function SetDocumentEditable(isDocEditable) {
+ var editor = GetCurrentEditor();
+ if (editor && editor.document) {
+ try {
+ var flags = editor.flags;
+ editor.flags = isDocEditable
+ ? (flags &= ~Ci.nsIEditor.eEditorReadonlyMask)
+ : flags | Ci.nsIEditor.eEditorReadonlyMask;
+ } catch (e) {}
+
+ // update all commands
+ window.updateCommands("create");
+ }
+}
+
+// ****** end of save / publish **********//
+
+var nsPublishSettingsCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ if (GetCurrentEditor()) {
+ // Launch Publish Settings dialog
+
+ window.ok = window.openDialog(
+ "chrome://editor/content/EditorPublishSettings.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ ""
+ );
+ return window.ok;
+ }
+ return false;
+ },
+};
+
+var nsRevertCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return (
+ IsDocumentEditable() &&
+ IsDocumentModified() &&
+ !IsUrlAboutBlank(GetDocumentUrl())
+ );
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // Confirm with the user to abandon current changes
+ // Put the page title in the message string
+ let title = GetDocumentTitle();
+ let msg = GetString("AbandonChanges").replace(/%title%/, title);
+
+ let result = Services.prompt.confirmEx(
+ window,
+ GetString("RevertCaption"),
+ msg,
+ Services.prompt.BUTTON_TITLE_REVERT * Services.prompt.BUTTON_POS_0 +
+ Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1,
+ null,
+ null,
+ null,
+ null,
+ { value: 0 }
+ );
+
+ // Reload page if first button (Revert) was pressed
+ if (result == 0) {
+ CancelHTMLSource();
+ EditorLoadUrl(GetDocumentUrl());
+ }
+ },
+};
+
+var nsCloseCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return GetCurrentEditor() != null;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ CloseWindow();
+ },
+};
+
+async function CloseWindow() {
+ // Check to make sure document is saved. "true" means allow "Don't Save" button,
+ // so user can choose to close without saving
+ if (await CheckAndSaveDocument("cmd_close", true)) {
+ if (window.InsertCharWindow) {
+ SwitchInsertCharToAnotherEditorOrClose();
+ }
+
+ try {
+ var basewin = window
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIBaseWindow);
+ basewin.destroy();
+ } catch (e) {}
+ }
+}
+
+var nsOpenRemoteCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ // We can always do this.
+ return true;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var params = { action: "2", url: "" };
+ openDialog(
+ "chrome://communicator/content/openLocation.xhtml",
+ "_blank",
+ "chrome,modal,titlebar",
+ params
+ );
+ var win = getTopWin();
+ switch (params.action) {
+ case "0": // current window
+ win.focus();
+ win.loadURI(params.url, null, null, true);
+ break;
+ case "1": // new window
+ openDialog(
+ getBrowserURL(),
+ "_blank",
+ "all,dialog=no",
+ params.url,
+ null,
+ null,
+ null,
+ true
+ );
+ break;
+ case "2": // edit
+ editPage(params.url);
+ break;
+ case "3": // new tab
+ win.focus();
+ var browser = win.getBrowser();
+ browser.selectedTab = browser.addTab(params.url, {
+ allowThirdPartyFixup: true,
+ });
+ break;
+ case "4": // private
+ openNewPrivateWith(params.url);
+ break;
+ default:
+ break;
+ }
+ },
+};
+
+var nsPreviewCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return (
+ IsDocumentEditable() &&
+ IsHTMLEditor() &&
+ (DocumentHasBeenSaved() || IsDocumentModified())
+ );
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ async doCommand(aCommand) {
+ // Don't continue if user canceled during prompt for saving
+ // DocumentHasBeenSaved will test if we have a URL and suppress "Don't Save" button if not
+ if (!(await CheckAndSaveDocument("cmd_preview", DocumentHasBeenSaved()))) {
+ return;
+ }
+
+ // Check if we saved again just in case?
+ if (DocumentHasBeenSaved()) {
+ let browser;
+ try {
+ // Find a browser with this URL
+ let enumerator = Services.wm.getEnumerator("navigator:browser");
+
+ var documentURI = GetDocumentUrl();
+ while (enumerator.hasMoreElements()) {
+ browser = enumerator.getNext();
+ if (
+ browser &&
+ !browser.closed &&
+ documentURI == browser.getBrowser().currentURI.spec
+ ) {
+ break;
+ }
+
+ browser = null;
+ }
+ } catch (ex) {}
+
+ // If none found, open a new browser
+ if (!browser) {
+ browser = window.openDialog(
+ getBrowserURL(),
+ "_blank",
+ "chrome,all,dialog=no",
+ documentURI
+ );
+ } else {
+ try {
+ browser.BrowserReloadSkipCache();
+ browser.focus();
+ } catch (ex) {}
+ }
+ }
+ },
+};
+
+var nsSendPageCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return (
+ IsDocumentEditable() && (DocumentHasBeenSaved() || IsDocumentModified())
+ );
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ async doCommand(aCommand) {
+ // Don't continue if user canceled during prompt for saving
+ // DocumentHasBeenSaved will test if we have a URL and suppress "Don't Save" button if not
+ if (
+ !(await CheckAndSaveDocument("cmd_editSendPage", DocumentHasBeenSaved()))
+ ) {
+ return;
+ }
+
+ // Check if we saved again just in case?
+ if (DocumentHasBeenSaved()) {
+ // Launch Messenger Composer window with current page as contents
+ try {
+ openComposeWindow(GetDocumentUrl(), GetDocumentTitle());
+ } catch (ex) {
+ dump("Cannot Send Page: " + ex + "\n");
+ }
+ }
+ },
+};
+
+var nsPrintCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return true; // we can always do this
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // In editor.js
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ try {
+ let browser = GetCurrentEditorElement();
+ PrintUtils.printWindow(browser.outerWindowID, browser);
+ } catch (e) {}
+ },
+};
+
+var nsPrintPreviewCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ // We can always do this.
+ return true;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // In editor.js
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ try {
+ PrintUtils.printPreview("editor", PrintPreviewListener);
+ } catch (e) {}
+ },
+};
+
+var nsPrintSetupCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return true; // we can always do this
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // In editor.js
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ PrintUtils.showPageSetup();
+ },
+};
+
+var nsFindReplaceCommand = {
+ isCommandEnabled(aCommand, editorElement) {
+ return editorElement.getEditor(editorElement.contentWindow) != null;
+ },
+
+ getCommandStateParams(aCommand, aParams, editorElement) {},
+ doCommandParams(aCommand, aParams, editorElement) {},
+
+ doCommand(aCommand, editorElement) {
+ window.openDialog(
+ "chrome://editor/content/EdReplace.xhtml",
+ "_blank",
+ "chrome,modal,titlebar",
+ editorElement
+ );
+ },
+};
+
+var nsFindCommand = {
+ isCommandEnabled(aCommand, editorElement) {
+ return editorElement.getEditor(editorElement.contentWindow) != null;
+ },
+
+ getCommandStateParams(aCommand, aParams, editorElement) {},
+ doCommandParams(aCommand, aParams, editorElement) {},
+
+ doCommand(aCommand, editorElement) {
+ document.getElementById("FindToolbar").onFindCommand();
+ },
+};
+
+var nsFindAgainCommand = {
+ isCommandEnabled(aCommand, editorElement) {
+ // we can only do this if the search pattern is non-empty. Not sure how
+ // to get that from here
+ return editorElement.getEditor(editorElement.contentWindow) != null;
+ },
+
+ getCommandStateParams(aCommand, aParams, editorElement) {},
+ doCommandParams(aCommand, aParams, editorElement) {},
+
+ doCommand(aCommand, editorElement) {
+ let findPrev = aCommand == "cmd_findPrev";
+ document.getElementById("FindToolbar").onFindAgainCommand(findPrev);
+ },
+};
+
+var nsRewrapCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return (
+ IsDocumentEditable() &&
+ !IsInHTMLSourceMode() &&
+ GetCurrentEditor() instanceof Ci.nsIEditorMailSupport
+ );
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // We only want to respect new lines when using the web composer.
+ let respectNewLines = IsWebComposer();
+ GetCurrentEditor()
+ .QueryInterface(Ci.nsIEditorMailSupport)
+ .rewrap(respectNewLines);
+ },
+};
+
+var nsSpellingCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return (
+ IsDocumentEditable() && !IsInHTMLSourceMode() && IsSpellCheckerInstalled()
+ );
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.cancelSendMessage = false;
+ try {
+ var skipBlockQuotes =
+ window.document.documentElement.getAttribute("windowtype") ==
+ "msgcompose";
+ window.openDialog(
+ "chrome://editor/content/EdSpellCheck.xhtml",
+ "_blank",
+ "dialog,close,titlebar,modal,resizable",
+ false,
+ skipBlockQuotes,
+ true
+ );
+ } catch (ex) {}
+ },
+};
+
+// Validate using http://validator.w3.org/file-upload.html
+var URL2Validate;
+var nsValidateCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return GetCurrentEditor() != null;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ async doCommand(aCommand) {
+ // If the document hasn't been modified,
+ // then just validate the current url.
+ if (IsDocumentModified() || IsHTMLSourceChanged()) {
+ if (!(await CheckAndSaveDocument("cmd_validate", false))) {
+ return;
+ }
+
+ // Check if we saved again just in case?
+ if (!DocumentHasBeenSaved()) {
+ // user hit cancel?
+ return;
+ }
+ }
+
+ URL2Validate = GetDocumentUrl();
+ // See if it's a file:
+ var ifile;
+ try {
+ var fileHandler = GetFileProtocolHandler();
+ ifile = fileHandler.getFileFromURLSpec(URL2Validate);
+ // nsIFile throws an exception if it's not a file url
+ } catch (e) {
+ ifile = null;
+ }
+ if (ifile) {
+ URL2Validate = ifile.path;
+ var vwin = window.open(
+ "http://validator.w3.org/file-upload.html",
+ "EditorValidate"
+ );
+ // Window loads asynchronously, so pass control to the load listener:
+ vwin.addEventListener("load", this.validateFilePageLoaded);
+ } else {
+ window.open(
+ `http://validator.w3.org/check?uri=${URL2Validate}&doctype=Inline`,
+ "EditorValidate"
+ );
+ // This does the validation, no need to wait for page loaded.
+ }
+ },
+ validateFilePageLoaded(event) {
+ event.target.forms[0].uploaded_file.value = URL2Validate;
+ },
+};
+
+var nsFormCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdFormProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsInputTagCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdInputProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsInputImageCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdInputImage.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsTextAreaCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdTextAreaProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsSelectCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdSelectProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsButtonCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdButtonProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsLabelCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var tagName = "label";
+ try {
+ var editor = GetCurrentEditor();
+ // Find selected label or if start/end of selection is in label
+ var labelElement = editor.getSelectedElement(tagName);
+ if (!labelElement) {
+ labelElement = editor.getElementOrParentByTagName(
+ tagName,
+ editor.selection.anchorNode
+ );
+ }
+ if (!labelElement) {
+ labelElement = editor.getElementOrParentByTagName(
+ tagName,
+ editor.selection.focusNode
+ );
+ }
+ if (labelElement) {
+ // We only open the dialog for an existing label
+ window.openDialog(
+ "chrome://editor/content/EdLabelProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ labelElement
+ );
+ } else {
+ EditorSetTextProperty(tagName, "", "");
+ }
+ } catch (e) {}
+ },
+};
+
+var nsFieldSetCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdFieldSetProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsImageCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdImageProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsHLineCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // Inserting an HLine is different in that we don't use properties dialog
+ // unless we are editing an existing line's attributes
+ // We get the last-used attributes from the prefs and insert immediately
+
+ var tagName = "hr";
+ var editor = GetCurrentEditor();
+
+ var hLine;
+ try {
+ hLine = editor.getSelectedElement(tagName);
+ } catch (e) {
+ return;
+ }
+
+ if (hLine) {
+ // We only open the dialog for an existing HRule
+ window.openDialog(
+ "chrome://editor/content/EdHLineProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ } else {
+ try {
+ hLine = editor.createElementWithDefaults(tagName);
+
+ // We change the default attributes to those saved in the user prefs
+ let align = Services.prefs.getIntPref("editor.hrule.align");
+ if (align == 0) {
+ editor.setAttributeOrEquivalent(hLine, "align", "left", true);
+ } else if (align == 2) {
+ editor.setAttributeOrEquivalent(hLine, "align", "right", true);
+ }
+
+ // Note: Default is center (don't write attribute)
+
+ let width = Services.prefs.getIntPref("editor.hrule.width");
+ if (Services.prefs.getBoolPref("editor.hrule.width_percent")) {
+ width = width + "%";
+ }
+
+ editor.setAttributeOrEquivalent(hLine, "width", width, true);
+
+ let height = Services.prefs.getIntPref("editor.hrule.height");
+ editor.setAttributeOrEquivalent(hLine, "size", String(height), true);
+
+ if (Services.prefs.getBoolPref("editor.hrule.shading")) {
+ hLine.removeAttribute("noshade");
+ } else {
+ hLine.setAttribute("noshade", "noshade");
+ }
+
+ editor.insertElementAtSelection(hLine, true);
+ } catch (e) {}
+ }
+ },
+};
+
+var nsLinkCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // If selected element is an image, launch that dialog instead
+ // since last tab panel handles link around an image
+ var element = GetObjectForProperties();
+ if (element && element.nodeName.toLowerCase() == "img") {
+ window.openDialog(
+ "chrome://editor/content/EdImageProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ null,
+ true
+ );
+ } else {
+ window.openDialog(
+ "chrome://editor/content/EdLinkProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ }
+ },
+};
+
+var nsAnchorCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdNamedAnchorProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ ""
+ );
+ },
+};
+
+var nsInsertHTMLWithDialogCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdInsSrc.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal,resizable",
+ ""
+ );
+ },
+};
+
+var nsInsertMathWithDialogCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdInsertMath.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal,resizable",
+ ""
+ );
+ },
+};
+
+var nsInsertCharsCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ EditorFindOrCreateInsertCharWindow();
+ },
+};
+
+var nsInsertBreakCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentEditor().insertHTML("<br>");
+ } catch (e) {}
+ },
+};
+
+var nsInsertBreakAllCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentEditor().insertHTML("<br clear='all'>");
+ } catch (e) {}
+ },
+};
+
+var nsGridCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdSnapToGrid.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsListPropertiesCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdListProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ },
+};
+
+var nsPagePropertiesCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var oldTitle = GetDocumentTitle();
+ window.openDialog(
+ "chrome://editor/content/EdPageProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ ""
+ );
+
+ // Update main window title and
+ // recent menu data in prefs if doc title changed
+ if (GetDocumentTitle() != oldTitle) {
+ UpdateWindowTitle();
+ }
+ },
+};
+
+var nsObjectPropertiesCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ var isEnabled = false;
+ if (IsDocumentEditable() && IsEditingRenderedHTML()) {
+ isEnabled =
+ GetObjectForProperties() != null ||
+ GetCurrentEditor().getSelectedElement("href") != null;
+ }
+ return isEnabled;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // Launch Object properties for appropriate selected element
+ var element = GetObjectForProperties();
+ if (element) {
+ var name = element.nodeName.toLowerCase();
+ switch (name) {
+ case "img":
+ goDoCommand("cmd_image");
+ break;
+ case "hr":
+ goDoCommand("cmd_hline");
+ break;
+ case "form":
+ goDoCommand("cmd_form");
+ break;
+ case "input":
+ var type = element.getAttribute("type");
+ if (type && type.toLowerCase() == "image") {
+ goDoCommand("cmd_inputimage");
+ } else {
+ goDoCommand("cmd_inputtag");
+ }
+ break;
+ case "textarea":
+ goDoCommand("cmd_textarea");
+ break;
+ case "select":
+ goDoCommand("cmd_select");
+ break;
+ case "button":
+ goDoCommand("cmd_button");
+ break;
+ case "label":
+ goDoCommand("cmd_label");
+ break;
+ case "fieldset":
+ goDoCommand("cmd_fieldset");
+ break;
+ case "table":
+ EditorInsertOrEditTable(false);
+ break;
+ case "td":
+ case "th":
+ EditorTableCellProperties();
+ break;
+ case "ol":
+ case "ul":
+ case "dl":
+ case "li":
+ goDoCommand("cmd_listProperties");
+ break;
+ case "a":
+ if (element.name) {
+ goDoCommand("cmd_anchor");
+ } else if (element.href) {
+ goDoCommand("cmd_link");
+ }
+ break;
+ case "math":
+ goDoCommand("cmd_insertMathWithDialog");
+ break;
+ default:
+ doAdvancedProperties(element);
+ break;
+ }
+ } else {
+ // We get a partially-selected link if asked for specifically
+ try {
+ element = GetCurrentEditor().getSelectedElement("href");
+ } catch (e) {}
+ if (element) {
+ goDoCommand("cmd_link");
+ }
+ }
+ },
+};
+
+var nsSetSmiley = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {
+ var smileyCode = aParams.getStringValue("state_attribute");
+
+ var strSml;
+ switch (smileyCode) {
+ case ":-)":
+ strSml = "s1";
+ break;
+ case ":-(":
+ strSml = "s2";
+ break;
+ case ";-)":
+ strSml = "s3";
+ break;
+ case ":-P":
+ case ":-p":
+ case ":-b":
+ strSml = "s4";
+ break;
+ case ":-D":
+ strSml = "s5";
+ break;
+ case ":-[":
+ strSml = "s6";
+ break;
+ case ":-/":
+ case ":/":
+ case ":-\\":
+ case ":\\":
+ strSml = "s7";
+ break;
+ case "=-O":
+ case "=-o":
+ strSml = "s8";
+ break;
+ case ":-*":
+ strSml = "s9";
+ break;
+ case ">:o":
+ case ">:-o":
+ strSml = "s10";
+ break;
+ case "8-)":
+ strSml = "s11";
+ break;
+ case ":-$":
+ strSml = "s12";
+ break;
+ case ":-!":
+ strSml = "s13";
+ break;
+ case "O:-)":
+ case "o:-)":
+ strSml = "s14";
+ break;
+ case ":'(":
+ strSml = "s15";
+ break;
+ case ":-X":
+ case ":-x":
+ strSml = "s16";
+ break;
+ default:
+ strSml = "";
+ break;
+ }
+
+ try {
+ var editor = GetCurrentEditor();
+ var extElement = editor.createElementWithDefaults("span");
+ extElement.setAttribute("class", "moz-smiley-" + strSml);
+
+ var intElement = editor.createElementWithDefaults("span");
+ if (!intElement) {
+ return;
+ }
+
+ var txtElement = editor.document.createTextNode(smileyCode);
+ if (!txtElement) {
+ return;
+ }
+
+ intElement.appendChild(txtElement);
+ extElement.appendChild(intElement);
+
+ editor.insertElementAtSelection(extElement, true);
+ window.content.focus();
+ } catch (e) {
+ dump("Exception occurred in smiley InsertElementAtSelection\n");
+ }
+ },
+ // This is now deprecated in favor of "doCommandParams"
+ doCommand(aCommand) {},
+};
+
+function doAdvancedProperties(element) {
+ if (element) {
+ window.openDialog(
+ "chrome://editor/content/EdAdvancedEdit.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal,resizable=yes",
+ "",
+ element
+ );
+ }
+}
+
+var nsAdvancedPropertiesCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // Launch AdvancedEdit dialog for the selected element
+ try {
+ var element = GetCurrentEditor().getSelectedElement("");
+ doAdvancedProperties(element);
+ } catch (e) {}
+ },
+};
+
+var nsColorPropertiesCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ window.openDialog(
+ "chrome://editor/content/EdColorProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ ""
+ );
+ UpdateDefaultColors();
+ },
+};
+
+var nsIncreaseFontCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ if (!(IsDocumentEditable() && IsEditingRenderedHTML())) {
+ return false;
+ }
+ var setIndex = getFontSizeIndex();
+ return setIndex >= 0 && setIndex < 5;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var setIndex = getFontSizeIndex();
+ if (setIndex < 0 || setIndex >= 5) {
+ return;
+ }
+ var sizes = ["x-small", "small", "medium", "large", "x-large", "xx-large"];
+ EditorSetFontSize(sizes[setIndex + 1]);
+ },
+};
+
+var nsDecreaseFontCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ if (!(IsDocumentEditable() && IsEditingRenderedHTML())) {
+ return false;
+ }
+ var setIndex = getFontSizeIndex();
+ return setIndex > 0;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var setIndex = getFontSizeIndex();
+ if (setIndex <= 0) {
+ return;
+ }
+ var sizes = ["x-small", "small", "medium", "large", "x-large", "xx-large"];
+ EditorSetFontSize(sizes[setIndex - 1]);
+ },
+};
+
+var nsRemoveNamedAnchorsCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ // We could see if there's any link in selection, but it doesn't seem worth the work!
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ EditorRemoveTextProperty("name", "");
+ window.content.focus();
+ },
+};
+
+var nsEditLinkCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ // Not really used -- this command is only in context menu, and we do enabling there
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ var element = GetCurrentEditor().getSelectedElement("href");
+ if (element) {
+ editPage(element.href);
+ }
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsNormalModeCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsHTMLEditor() && IsDocumentEditable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ SetEditMode(kDisplayModeNormal);
+ },
+};
+
+var nsAllTagsModeCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsHTMLEditor();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ SetEditMode(kDisplayModeAllTags);
+ },
+};
+
+var nsHTMLSourceModeCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsHTMLEditor();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ SetEditMode(kDisplayModeSource);
+ },
+};
+
+var nsPreviewModeCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsHTMLEditor();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ SetEditMode(kDisplayModePreview);
+ },
+};
+
+var nsInsertOrEditTableCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ if (IsInTableCell()) {
+ EditorTableCellProperties();
+ } else {
+ EditorInsertOrEditTable(true);
+ }
+ },
+};
+
+var nsEditTableCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ EditorInsertOrEditTable(false);
+ },
+};
+
+var nsSelectTableCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().selectTable();
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsSelectTableRowCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().selectTableRow();
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsSelectTableColumnCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().selectTableColumn();
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsSelectTableCellCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().selectTableCell();
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsSelectAllTableCellsCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().selectAllTableCells();
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsInsertTableCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ EditorInsertTable();
+ },
+};
+
+var nsInsertTableRowAboveCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().insertTableRow(1, false);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsInsertTableRowBelowCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().insertTableRow(1, true);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsInsertTableColumnBeforeCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().insertTableColumn(1, false);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsInsertTableColumnAfterCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().insertTableColumn(1, true);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsInsertTableCellBeforeCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().insertTableCell(1, false);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsInsertTableCellAfterCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().insertTableCell(1, true);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsDeleteTableCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().deleteTable();
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsDeleteTableRowCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var rows = GetNumberOfContiguousSelectedRows();
+ // Delete at least one row
+ if (rows == 0) {
+ rows = 1;
+ }
+
+ try {
+ var editor = GetCurrentTableEditor();
+ editor.beginTransaction();
+
+ // Loop to delete all blocks of contiguous, selected rows
+ while (rows) {
+ editor.deleteTableRow(rows);
+ rows = GetNumberOfContiguousSelectedRows();
+ }
+ } finally {
+ editor.endTransaction();
+ }
+ window.content.focus();
+ },
+};
+
+var nsDeleteTableColumnCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ var columns = GetNumberOfContiguousSelectedColumns();
+ // Delete at least one column
+ if (columns == 0) {
+ columns = 1;
+ }
+
+ try {
+ var editor = GetCurrentTableEditor();
+ editor.beginTransaction();
+
+ // Loop to delete all blocks of contiguous, selected columns
+ while (columns) {
+ editor.deleteTableColumn(columns);
+ columns = GetNumberOfContiguousSelectedColumns();
+ }
+ } finally {
+ editor.endTransaction();
+ }
+ window.content.focus();
+ },
+};
+
+var nsDeleteTableCellCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().deleteTableCell(1);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsDeleteTableCellContentsCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTableCell();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().deleteTableCellContents();
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsNormalizeTableCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // Use nullptr to let editor find table enclosing current selection
+ try {
+ GetCurrentTableEditor().normalizeTable(null);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsJoinTableCellsCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ if (IsDocumentEditable() && IsEditingRenderedHTML()) {
+ try {
+ var editor = GetCurrentTableEditor();
+ var tagNameObj = { value: "" };
+ var countObj = { value: 0 };
+ var cell = editor.getSelectedOrParentTableElement(tagNameObj, countObj);
+
+ // We need a cell and either > 1 selected cell or a cell to the right
+ // (this cell may originate in a row spanned from above current row)
+ // Note that editor returns "td" for "th" also.
+ // (this is a pain! Editor and gecko use lowercase tagNames, JS uses uppercase!)
+ if (cell && tagNameObj.value == "td") {
+ // Selected cells
+ if (countObj.value > 1) {
+ return true;
+ }
+
+ var colSpan = cell.getAttribute("colspan");
+
+ // getAttribute returns string, we need number
+ // no attribute means colspan = 1
+ if (!colSpan) {
+ colSpan = Number(1);
+ } else {
+ colSpan = Number(colSpan);
+ }
+
+ var rowObj = { value: 0 };
+ var colObj = { value: 0 };
+ editor.getCellIndexes(cell, rowObj, colObj);
+
+ // Test if cell exists to the right of current cell
+ // (cells with 0 span should never have cells to the right
+ // if there is, user can select the 2 cells to join them)
+ return (
+ colSpan &&
+ editor.getCellAt(null, rowObj.value, colObj.value + colSpan)
+ );
+ }
+ } catch (e) {}
+ }
+ return false;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // Param: Don't merge non-contiguous cells
+ try {
+ GetCurrentTableEditor().joinTableCells(false);
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsSplitTableCellCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ if (IsDocumentEditable() && IsEditingRenderedHTML()) {
+ var tagNameObj = { value: "" };
+ var countObj = { value: 0 };
+ var cell;
+ try {
+ cell = GetCurrentTableEditor().getSelectedOrParentTableElement(
+ tagNameObj,
+ countObj
+ );
+ } catch (e) {}
+
+ // We need a cell parent and there's just 1 selected cell
+ // or selection is entirely inside 1 cell
+ if (
+ cell &&
+ tagNameObj.value == "td" &&
+ countObj.value <= 1 &&
+ IsSelectionInOneCell()
+ ) {
+ var colSpan = cell.getAttribute("colspan");
+ var rowSpan = cell.getAttribute("rowspan");
+ if (!colSpan) {
+ colSpan = 1;
+ }
+ if (!rowSpan) {
+ rowSpan = 1;
+ }
+ return colSpan > 1 || rowSpan > 1 || colSpan == 0 || rowSpan == 0;
+ }
+ }
+ return false;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentTableEditor().splitTableCell();
+ } catch (e) {}
+ window.content.focus();
+ },
+};
+
+var nsTableOrCellColorCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsInTable();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ EditorSelectColor("TableOrCell");
+ },
+};
+
+var nsPreferencesCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return true;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ goPreferences("composer_pane");
+ },
+};
+
+var nsFinishHTMLSource = {
+ isCommandEnabled(aCommand, dummy) {
+ return true;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // In editor.js
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ },
+};
+
+var nsCancelHTMLSource = {
+ isCommandEnabled(aCommand, dummy) {
+ return true;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ // In editor.js
+ CancelHTMLSource();
+ },
+};
+
+var nsConvertToTable = {
+ isCommandEnabled(aCommand, dummy) {
+ if (IsDocumentEditable() && IsEditingRenderedHTML()) {
+ var selection;
+ try {
+ selection = GetCurrentEditor().selection;
+ } catch (e) {}
+
+ if (selection && !selection.isCollapsed) {
+ // Don't allow if table or cell is the selection
+ var element;
+ try {
+ element = GetCurrentEditor().getSelectedElement("");
+ } catch (e) {}
+ if (element) {
+ var name = element.nodeName.toLowerCase();
+ if (
+ name == "td" ||
+ name == "th" ||
+ name == "caption" ||
+ name == "table"
+ ) {
+ return false;
+ }
+ }
+
+ // Selection start and end must be in the same cell
+ // in same cell or both are NOT in a cell
+ if (
+ GetParentTableCell(selection.focusNode) !=
+ GetParentTableCell(selection.anchorNode)
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+ return false;
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ if (this.isCommandEnabled()) {
+ window.openDialog(
+ "chrome://editor/content/EdConvertToTable.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal"
+ );
+ }
+ },
+};
diff --git a/comm/suite/editor/base/content/EditorAllTags.css b/comm/suite/editor/base/content/EditorAllTags.css
new file mode 100644
index 0000000000..656dffcdee
--- /dev/null
+++ b/comm/suite/editor/base/content/EditorAllTags.css
@@ -0,0 +1,802 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Styles to alter look of things in the Editor content window
+ * for the "All Tags Edit Mode" Every HTML tag shows up as an icon.
+*/
+
+/* For "userdefined" or "unknown" tags
+ (Note that "_" must be escaped)
+*/
+
+*:not(a):not(abbr):not(acronym):not(address):not(applet):not(area):not(b):not(base):not(basefont):not(bdo):not(bgsound):not(big):not(blink):not(blockquote):not(body):not(br):not(button):not(canvas):not(caption):not(center):not(cite):not(code):not(col):not(colgroup):not(dd):not(del):not(dfn):not(dir):not(div):not(dl):not(dt):not(em):not(embed):not(fieldset):not(font):not(form):not(frame):not(frameset):not(h1):not(h2):not(h3):not(h4):not(h5):not(h6):not(head):not(hr):not(html):not(i):not(iframe):not(image):not(img):not(input):not(ins):not(isindex):not(kbd):not(keygen):not(label):not(legend):not(li):not(link):not(listing):not(map):not(marquee):not(menu):not(meta):not(multicol):not(nobr):not(noembed):not(noframes):not(noscript):not(object):not(ol):not(optgroup):not(option):not(p):not(param):not(plaintext):not(pre):not(q):not(s):not(samp):not(script):not(select):not(server):not(small):not(sound):not(spacer):not(span):not(strike):not(strong):not(style):not(sub):not(sup):not(table):not(tbody):not(td):not(textarea):not(tfoot):not(th):not(thead):not(title):not(tr):not(tt):not(u):not(ul):not(var):not(wbr):not(xmp) {
+ display: inline;
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 16px;
+ background-image: url(chrome://editor/content/images/tag-userdefined.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+a:not([\_moz_anonclass]) {
+ min-height: 16px; margin-left: 2px; margin-top: 2px;
+ padding-left: 20px;
+ background-image: url(chrome://editor/content/images/tag-a.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+abbr {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 35px;
+ background-image: url(chrome://editor/content/images/tag-abr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+
+acronym {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 57px;
+ background-image: url(chrome://editor/content/images/tag-acr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+address {
+ min-height: 44px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-adr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+applet {
+ min-height: 35px; margin-top: 2px;
+ padding-left: 47px;
+ background-image: url(chrome://editor/content/images/tag-app.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+area {
+ min-height: 35px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-ara.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+b {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 20px;
+ background-image: url(chrome://editor/content/images/tag-b.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+basefont {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 57px;
+ background-image: url(chrome://editor/content/images/tag-bsf.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+bdo {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-bdo.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+big {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-big.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+blockquote {
+ min-height: 44px; margin-left: 2px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-blq.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+body {
+ min-height: 36px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-body.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+br {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 23px;
+ background-image: url(chrome://editor/content/images/tag-br.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+button {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 57px;
+ background-image: url(chrome://editor/content/images/tag-btn.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+caption {
+ min-height: 35px; margin-top: 2px;
+ padding-left: 55px;
+ background-image: url(chrome://editor/content/images/tag-cpt.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+center {
+ min-height: 44px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-ctr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+cite {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-cit.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+code {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-cod.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+col {
+ min-height: 35px; margin-left: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-col.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+colgroup {
+ min-height: 35px; margin-left: 2px;
+ padding-left: 51px;
+ background-image: url(chrome://editor/content/images/tag-clg.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+dd {
+ min-height: 35px; margin-top: 2px;
+ padding-left: 23px;
+ background-image: url(chrome://editor/content/images/tag-dd.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+del {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-del.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+dfn {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-dfn.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+dir {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-dir.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+div {
+ min-height: 24px; margin-top: 2px;
+ /* TEMPORARY TO COMPENSATE FOR BUG */
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-div.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+input div {
+ min-height: 0px; margin-left: 0px; margin-top: 0px;
+ padding-left: 0px;
+ background-image: none;
+}
+
+dl {
+ min-height: 20px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-dl.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+dt {
+ min-height: 35px; margin-top: 2px;
+ padding-left: 23px;
+ background-image: url(chrome://editor/content/images/tag-dt.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+em {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 23px;
+ background-image: url(chrome://editor/content/images/tag-em.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+fieldset {
+ min-height: 44px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-fld.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+font {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-fnt.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+form {
+ min-height: 36px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-for.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+frame {
+ min-height: 40px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-frm.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+frameset {
+ min-height: 44px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-fst.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+h1 {
+ min-height: 20px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-h1.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+h2 {
+ min-height: 20px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-h2.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+h3 {
+ min-height: 20px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-h3.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+h4 {
+ min-height: 20px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-h4.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+h5 {
+ min-height: 20px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-h5.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+h6 {
+ min-height: 20px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-h6.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+hr {
+ min-height: 20px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-hr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+i {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 20px;
+ background-image: url(chrome://editor/content/images/tag-i.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+iframe {
+ min-height: 35px; margin-left: 2px;
+ padding-left: 47px;
+ background-image: url(chrome://editor/content/images/tag-ifr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+img:not([\_moz_anonclass]) {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-img.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+input {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-inp.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+ins {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-ins.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+isindex {
+ min-height: 40px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-isx.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+kbd {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-kbd.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+label {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-lbl.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+legend {
+ min-height: 35px; margin-top: 2px;
+ padding-left: 49px;
+ background-image: url(chrome://editor/content/images/tag-lgn.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+li {
+ min-height: 35px; margin-top: 2px;
+ padding-left: 23px;
+ background-image: url(chrome://editor/content/images/tag-li.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+listing {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 57px;
+ background-image: url(chrome://editor/content/images/tag-lst.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+map {
+ min-height: 35px; margin-left: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-map.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+menu {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-men.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+nobr {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-nbr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+noframes {
+ min-height: 44px; margin-left: 2px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-nfr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+noscript {
+ min-height: 44px; margin-left: 2px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-nsc.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+object {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 49px;
+ background-image: url(chrome://editor/content/images/tag-obj.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+ol {
+ min-height: 38px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-ol.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+optgroup {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 51px;
+ background-image: url(chrome://editor/content/images/tag-opg.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+option {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 47px;
+ background-image: url(chrome://editor/content/images/tag-opt.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+p {
+ min-height: 38px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-p.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+param {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 43px;
+ background-image: url(chrome://editor/content/images/tag-prm.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+plaintext {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 57px;
+ background-image: url(chrome://editor/content/images/tag-pln.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+pre {
+ min-height: 24px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-pre.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+q {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 20px;
+ background-image: url(chrome://editor/content/images/tag-q.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+s {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 20px;
+ background-image: url(chrome://editor/content/images/tag-s.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+samp {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-smp.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+script {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 45px;
+ background-image: url(chrome://editor/content/images/tag-scr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+select {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 47px;
+ background-image: url(chrome://editor/content/images/tag-slc.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+small {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 41px;
+ background-image: url(chrome://editor/content/images/tag-sml.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+span:not([\_moz_anonclass]) {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ /* TEMPORARY TO COMPENSATE FOR BUG */
+ padding-left: 39px;
+ background-image: url(chrome://editor/content/images/tag-spn.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+strike {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 45px;
+ background-image: url(chrome://editor/content/images/tag-stk.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+strong {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 51px;
+ background-image: url(chrome://editor/content/images/tag-stn.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+sub {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-sub.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+sup {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-sup.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+/* The background image technique is not working for
+ some table elements. Trying the "before" strategy
+*/
+
+table {
+ min-height: 40px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-tbl.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+tbody {
+ min-height: 42px; margin-left: 2px; margin-top: 1px;
+ padding-left: 17px;
+ content: url(chrome://editor/content/images/tag-tbd.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+td {
+ min-height: 22px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-td.png);
+
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+textarea {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 59px;
+ background-image: url(chrome://editor/content/images/tag-txt.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+tfoot {
+ min-height: 42px; margin-left: 2px; margin-top: 1px;
+ padding-left: 17px;
+ content: url(chrome://editor/content/images/tag-tft.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+th {
+ min-height: 22px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-th.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+thead {
+ min-height: 42px; margin-left: 2px; margin-top: 1px;
+ padding-left: 17px;
+ content: url(chrome://editor/content/images/tag-thd.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+tr {
+ min-height: 22px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-tr.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+tt {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 23px;
+ background-image: url(chrome://editor/content/images/tag-tt.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+u {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 23px;
+ background-image: url(chrome://editor/content/images/tag-u.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+ul {
+ min-height: 20px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-ul.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+var {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-var.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+xmp {
+ min-height: 35px; margin-left: 2px; margin-top: 2px;
+ padding-left: 31px;
+ background-image: url(chrome://editor/content/images/tag-xmp.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+
+/* These are tags that we DON'T want to show icons for.
+ We have images for them in case we want to utilize them
+ for some other purpose than the "All Tags" editor mode
+
+html {
+ min-height: 36px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-html.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+head {
+ min-height: 36px; margin-left: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-hed.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+These are tags that are ONLY allowed as children of HEAD:
+
+title {
+ min-height: 40px; margin-left: 2px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-ttl.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+base {
+ min-height: 36px; margin-left: 2px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-bas.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+style {
+ min-height: 40px; margin-left: 2px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-stl.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+meta {
+ min-height: 36px; margin-left: 2px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-met.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+link {
+ min-height: 30px; margin-left: 2px; margin-top: 2px;
+ padding-left: 17px;
+ background-image: url(chrome://editor/content/images/tag-lnk.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+*/
diff --git a/comm/suite/editor/base/content/EditorContent.css b/comm/suite/editor/base/content/EditorContent.css
new file mode 100644
index 0000000000..fee8af21de
--- /dev/null
+++ b/comm/suite/editor/base/content/EditorContent.css
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Styles to alter look of things in the Editor content window
+ * for the "Normal Edit Mode" These settings will be removed
+ * when we display in completely WYSIWYG "Edit Preview" mode
+ * Anything that should never change, like cursors, should be
+ * place in EditorOverride.css, instead of here.
+*/
+
+@import url(chrome://communicator/skin/smileys.css);
+
+a[name] {
+ min-height: 17px; margin-left: 2px; margin-top: 2px;
+ padding-left: 20px;
+ background-image: url(chrome://editor/content/images/tag-anchor.png);
+ background-repeat: no-repeat;
+ background-position: top left;
+}
+
+/* Force border display for empty cells
+ and tables with 0 border
+*/
+table {
+ empty-cells: show;
+}
+
+/* give a red dotted border to tables and cells with no border
+ otherwise they are invisible
+*/
+table[empty-cells],
+ table[border="0"],
+ /* next two selectors on line below for the case where tbody is omitted */
+ table[border="0"] > tr > td, table[border="0"] > tr > th,
+ table[border="0"] > thead > tr > td, table[border="0"] > tbody > tr > td, table[border="0"] > tfoot > tr > td,
+ table[border="0"] > thead > tr > th, table[border="0"] > tbody > tr > th, table[border="0"] > tfoot > tr > th,
+ table:not([border]),
+ /* next two selectors on line below for the case where tbody is omitted */
+ table:not([border]) > tr > td, table:not([border]) > tr > th,
+ table:not([border]) > thead > tr > td, table:not([border]) > tbody > tr > td, table:not([border]) > tfoot > tr > td,
+ table:not([border]) > thead > tr > th, table:not([border]) > tbody > tr > th, table:not([border]) > tfoot > tr > th
+{
+ border: 1px dotted red;
+}
+
+/* give a green dashed border to forms otherwise they are invisible
+*/
+form
+{
+ border: 2px dashed green;
+}
+/* give a green dotted border to labels otherwise they are invisible
+*/
+label
+{
+ border: 1px dotted green;
+}
+
+img {
+ -moz-force-broken-image-icon: 1;
+}
diff --git a/comm/suite/editor/base/content/EditorContextMenu.js b/comm/suite/editor/base/content/EditorContextMenu.js
new file mode 100644
index 0000000000..93dedfc0b1
--- /dev/null
+++ b/comm/suite/editor/base/content/EditorContextMenu.js
@@ -0,0 +1,122 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "InlineSpellCheckerUI", function() {
+ let tmp = {};
+ ChromeUtils.import("resource://gre/modules/InlineSpellChecker.jsm", tmp);
+ return new tmp.InlineSpellChecker();
+});
+
+// Overrides the main contentAreaContext onpopupshowing so needs to do
+// everything that does plus call Composer specific code.
+function editorContextPopupShowing(aNode)
+{
+ gContextMenu = new nsContextMenu(aNode);
+ if (gContextMenu.shouldDisplay)
+ {
+ var showExtra = top.document.commandDispatcher.focusedWindow == content;
+ gContextMenu.initEditorItems(showExtra);
+ return true;
+ }
+ return false;
+}
+
+// Extends the main nsContextMenu for Composer.
+nsContextMenu.prototype.initEditorItems = function (aShow)
+{
+ var isInLink = false;
+ var objectName;
+ var inSourceMode = IsInHTMLSourceMode();
+ var showSpell = !inSourceMode && !IsInPreviewMode() &&
+ InlineSpellCheckerUI.canSpellCheck;
+ this.showItem("spell-check-enabled", showSpell);
+ this.showItem("spell-separator", showSpell);
+
+ aShow = aShow && !inSourceMode;
+ this.hideDisabledItem("menu_pasteNoFormatting_cm", aShow);
+
+ // Only do this stuff when not in source mode or sidebar.
+ if (aShow)
+ {
+ // Setup object property command element.
+ objectName = InitObjectPropertiesMenuitem();
+ isInLink = objectName == "href";
+
+ InitRemoveStylesMenuitems("removeStylesMenuitem_cm",
+ "removeLinksMenuitem_cm",
+ "removeNamedAnchorsMenuitem_cm");
+
+ // Set appropriate text for join cells command.
+ InitJoinCellMenuitem("joinTableCells_cm");
+
+ // Update enable states for all table commands.
+ goUpdateTableMenuItems(document.getElementById("composerTableMenuItems"));
+
+ this.hideDisabledItem("context-undo", true);
+ this.hideDisabledItem("context-redo", true);
+ this.hideDisabledItem("context-cut", true);
+ this.hideDisabledItem("context-copy", true);
+ this.hideDisabledItem("context-paste", true);
+ this.hideDisabledItem("context-delete", true);
+
+ this.showItem("context-sep-undo",
+ this.shouldShowSeparator("context-sep-undo"));
+ this.showItem("context-sep-paste",
+ this.shouldShowSeparator("context-sep-paste"));
+ }
+
+ this.hideDisabledItem("objectProperties_cm", aShow);
+
+ // Show "Create Link" if not in a link and not in source mode or sidebar.
+ this.showItem("createLink_cm", aShow && !isInLink);
+
+ // Show "Edit link in new Composer" if in a link and
+ // not in source mode or sidebar.
+ this.showItem("editLink_cm", aShow && isInLink);
+
+ this.hideDisabledItem("removeStylesMenuitem_cm", aShow);
+ this.hideDisabledItem("removeLinksMenuitem_cm", aShow);
+ this.hideDisabledItem("removeNamedAnchorsMenuitem_cm", aShow);
+
+ this.hideDisabledItem("joinTableCells_cm", aShow);
+ this.hideDisabledItem("splitTableCell_cm", aShow);
+ this.hideDisabledItem("tableOrCellColor_cm", aShow);
+
+ var inCell = aShow && IsInTableCell();
+ // Remove table submenus if not in table.
+ this.showItem("tableInsertMenu_cm", inCell);
+ this.showItem("tableSelectMenu_cm", inCell);
+ this.showItem("tableDeleteMenu_cm", inCell);
+
+ this.showItem("context-sep-selectall", aShow);
+ this.showItem("context-sep-properites", aShow && !!objectName);
+ this.showItem("frame-sep", aShow && IsInTable());
+};
+
+nsContextMenu.prototype.hideDisabledItem = function(aId, aShow)
+{
+ this.showItem(aId, aShow && IsItemOrCommandEnabled(aId));
+};
+
+function IsItemOrCommandEnabled(aId)
+{
+ var item = document.getElementById(aId);
+ if (!item)
+ return false;
+
+ var command = item.getAttribute("command");
+ if (command) {
+ // If possible, query the command controller directly
+ var controller = document.commandDispatcher
+ .getControllerForCommand(command);
+ if (controller)
+ return controller.isCommandEnabled(command);
+ }
+
+ // Fall back on the inefficient observed disabled attribute
+ return item.getAttribute("disabled") != "true";
+}
diff --git a/comm/suite/editor/base/content/EditorContextMenuOverlay.xhtml b/comm/suite/editor/base/content/EditorContextMenuOverlay.xhtml
new file mode 100644
index 0000000000..36c23f0c5e
--- /dev/null
+++ b/comm/suite/editor/base/content/EditorContextMenuOverlay.xhtml
@@ -0,0 +1,171 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay SYSTEM "chrome://editor/locale/editorOverlay.dtd">
+
+<overlay id="ComposerContextMenuOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+<script src="chrome://editor/content/EditorContextMenu.js"/>
+<script src="chrome://editor/content/StructBarContextMenu.js"/>
+
+ <menupopup id="contentAreaContextMenu"
+ onpopupshowing="return event.target != this ||
+ editorContextPopupShowing(this);">
+ <menuitem id="menu_pasteNoFormatting_cm"
+ insertafter="context-paste"
+ command="cmd_pasteNoFormatting"/>
+
+ <!-- label and accesskey set at runtime from strings -->
+ <menuitem id="removeStylesMenuitem_cm"
+ insertafter="context-sep-selectall"
+ command="cmd_removeStyles"/>
+ <menuitem id="createLink_cm"
+ insertafter="removeStylesMenuitem_cm"
+ label="&createLinkCmd.label;"
+ accesskey="&createLinkCmd.accesskey;"
+ command="cmd_link"/>
+ <!-- label and accesskey set at runtime from strings -->
+ <menuitem id="removeLinksMenuitem_cm"
+ insertafter="createLink_cm"
+ command="cmd_removeLinks"/>
+ <menuitem id="removeNamedAnchorsMenuitem_cm"
+ insertafter="removeLinksMenuitem_cm"
+ label="&formatRemoveNamedAnchors.label;"
+ accesskey="&formatRemoveNamedAnchors.accesskey;"
+ command="cmd_removeNamedAnchors"/>
+
+ <!-- label and accesskey are set in InitObjectProperties -->
+ <menuitem id="objectProperties_cm"
+ insertafter="context-sep-properties"
+ command="cmd_objectProperties"/>
+ <menuitem id="editLink_cm"
+ insertafter="objectProperties_cm"
+ label="&editLinkCmd.label;"
+ accesskey="&editLinkCmd.accesskey;"
+ command="cmd_editLink"/>
+
+ <!-- Can't get submenus to load from a shared overlay -->
+ <menu id="tableInsertMenu_cm"
+ insertafter="frame-sep"
+ label="&tableInsertMenu2.label;"
+ accesskey="&tableInsertMenu2.accesskey;">
+ <menupopup>
+ <menuitem label="&insertTableCmd.label;"
+ accesskey="&insertTableCmd.accesskey;"
+ command="cmd_InsertTable"/>
+ <menuseparator/>
+ <menuitem label="&tableRowAbove.label;"
+ accesskey="&tableRowAbove.accesskey;"
+ command="cmd_InsertRowAbove"/>
+ <menuitem label="&tableRowBelow.label;"
+ accesskey="&tableRowBelow.accesskey;"
+ command="cmd_InsertRowBelow"/>
+ <menuseparator/>
+ <menuitem label="&tableColumnBefore.label;"
+ accesskey="&tableColumnBefore.accesskey;"
+ command="cmd_InsertColumnBefore"/>
+ <menuitem label="&tableColumnAfter.label;"
+ accesskey="&tableColumnAfter.accesskey;"
+ command="cmd_InsertColumnAfter"/>
+ <menuseparator/>
+ <menuitem label="&tableCellBefore.label;"
+ accesskey="&tableCellBefore.accesskey;"
+ command="cmd_InsertCellBefore"/>
+ <menuitem label="&tableCellAfter.label;"
+ accesskey="&tableCellAfter.accesskey;"
+ command="cmd_InsertCellAfter"/>
+ </menupopup>
+ </menu>
+ <menu id="tableSelectMenu_cm"
+ insertafter="tableInsertMenu_cm"
+ label="&tableSelectMenu2.label;"
+ accesskey="&tableSelectMenu2.accesskey;">
+ <menupopup>
+ <menuitem id="menu_SelectTable_cm"
+ label="&tableTable.label;"
+ accesskey="&tableTable.accesskey;"
+ command="cmd_SelectTable"/>
+ <menuitem id="menu_SelectRow_cm"
+ label="&tableRow.label;"
+ accesskey="&tableRow.accesskey;"
+ command="cmd_SelectRow"/>
+ <menuitem id="menu_SelectColumn_cm"
+ label="&tableColumn.label;"
+ accesskey="&tableColumn.accesskey;"
+ command="cmd_SelectColumn"/>
+ <menuitem id="menu_SelectCell_cm"
+ label="&tableCell.label;"
+ accesskey="&tableCell.accesskey;"
+ command="cmd_SelectCell"/>
+ <menuitem id="menu_SelectAllCells_cm"
+ label="&tableAllCells.label;"
+ accesskey="&tableAllCells.accesskey;"
+ command="cmd_SelectAllCells"/>
+ </menupopup>
+ </menu>
+ <menu id="tableDeleteMenu_cm"
+ insertafter="tableSelectMenu_cm"
+ label="&tableDeleteMenu2.label;"
+ accesskey="&tableDeleteMenu2.accesskey;">
+ <menupopup>
+ <menuitem id="menu_DeleteTable_cm"
+ label="&tableTable.label;"
+ accesskey="&tableTable.accesskey;"
+ command="cmd_DeleteTable"/>
+ <menuitem id="menu_DeleteRow_cm"
+ label="&tableRows.label;"
+ accesskey="&tableRow.accesskey;"
+ command="cmd_DeleteRow"/>
+ <menuitem id="menu_DeleteColumn_cm"
+ label="&tableColumns.label;"
+ accesskey="&tableColumn.accesskey;"
+ command="cmd_DeleteColumn"/>
+ <menuitem id="menu_DeleteCell_cm"
+ label="&tableCells.label;"
+ accesskey="&tableCell.accesskey;"
+ command="cmd_DeleteCell"/>
+ <menuitem id="menu_DeleteCellContents_cm"
+ label="&tableCellContents.label;"
+ accesskey="&tableCellContents.accesskey;"
+ command="cmd_DeleteCellContents"/>
+ </menupopup>
+ </menu>
+ <!-- menu label is set in InitTableMenu -->
+ <menuitem id="joinTableCells_cm"
+ insertafter="tableDeleteMenu_cm"
+ label="&tableJoinCells.label;"
+ accesskey="&tableJoinCells.accesskey;"
+ command="cmd_JoinTableCells"/>
+ <menuitem id="splitTableCell_cm"
+ insertafter="joinTableCells_cm"
+ label="&tableSplitCell.label;"
+ accesskey="&tableSplitCell.accesskey;"
+ command="cmd_SplitTableCell"/>
+ <menuitem id="tableOrCellColor_cm"
+ insertafter="splitTableCell_cm"
+ label="&tableOrCellColor.label;"
+ accesskey="&tableOrCellColor.accesskey;"
+ command="cmd_TableOrCellColor"/>
+ </menupopup>
+
+ <menupopup id="structToolbarContext">
+ <menuitem id="structSelect" label="&structSelect.label;"
+ accesskey="&structSelect.accesskey;"
+ oncommand="StructSelectTag()"/>
+ <menuseparator/>
+ <menuitem id="structRemoveTag" label="&structRemoveTag.label;"
+ accesskey="&structRemoveTag.accesskey;"
+ oncommand="StructRemoveTag()"/>
+ <menuitem id="structChangeTag" label="&structChangeTag.label;"
+ accesskey="&structChangeTag.accesskey;"
+ oncommand="StructChangeTag()"/>
+ <menuseparator/>
+ <menuitem id="advancedPropsTag" label="&advancedPropertiesCmd.label;"
+ accesskey="&advancedPropertiesCmd.accesskey;"
+ oncommand="OpenAdvancedProperties()"/>
+ </menupopup>
+
+</overlay>
diff --git a/comm/suite/editor/base/content/StructBarContextMenu.js b/comm/suite/editor/base/content/StructBarContextMenu.js
new file mode 100644
index 0000000000..19f5498355
--- /dev/null
+++ b/comm/suite/editor/base/content/StructBarContextMenu.js
@@ -0,0 +1,179 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gContextMenuNode;
+var gContextMenuFiringDocumentElement;
+
+function InitStructBarContextMenu(button, docElement)
+{
+ gContextMenuFiringDocumentElement = docElement;
+ gContextMenuNode = button;
+
+ var tag = docElement.nodeName.toLowerCase();
+
+ var structRemoveTag = document.getElementById("structRemoveTag");
+ var enableRemove;
+
+ switch (tag) {
+ case "body":
+ case "tbody":
+ case "thead":
+ case "tfoot":
+ case "col":
+ case "colgroup":
+ case "tr":
+ case "th":
+ case "td":
+ case "caption":
+ enableRemove = false;
+ break;
+ default:
+ enableRemove = true;
+ break;
+ }
+ SetElementEnabled(structRemoveTag, enableRemove);
+
+ var structChangeTag = document.getElementById("structChangeTag");
+ SetElementEnabled(structChangeTag, (tag != "body"));
+}
+
+function TableCellFilter(node)
+{
+ switch (node.nodeName.toLowerCase())
+ {
+ case "td":
+ case "th":
+ case "caption":
+ return NodeFilter.FILTER_ACCEPT;
+ break;
+ default:
+ return NodeFilter.FILTER_SKIP;
+ break;
+ }
+ return NodeFilter.FILTER_SKIP;
+}
+
+function StructRemoveTag()
+{
+ var editor = GetCurrentEditor();
+ if (!editor) return;
+
+ var element = gContextMenuFiringDocumentElement;
+ var offset = 0;
+ var childNodes = element.parentNode.childNodes;
+
+ while (childNodes[offset] != element) {
+ ++offset;
+ }
+
+ editor.beginTransaction();
+
+ try {
+
+ var tag = element.nodeName.toLowerCase();
+ if (tag != "table") {
+ MoveChildNodesAfterElement(editor, element, element, offset);
+ }
+ else {
+
+ var nodeIterator = document.createTreeWalker(element,
+ NodeFilter.SHOW_ELEMENT,
+ TableCellFilter,
+ true);
+ var node = nodeIterator.lastChild();
+ while (node) {
+ MoveChildNodesAfterElement(editor, node, element, offset);
+ node = nodeIterator.previousSibling();
+ }
+
+ }
+ editor.deleteNode(element);
+ }
+ catch (e) {};
+
+ editor.endTransaction();
+}
+
+function MoveChildNodesAfterElement(editor, element, targetElement, targetOffset)
+{
+ var childNodes = element.childNodes;
+ var childNodesLength = childNodes.length;
+ var i;
+ for (i = childNodesLength - 1; i >= 0; i--) {
+ var clone = childNodes.item(i).cloneNode(true);
+ editor.insertNode(clone, targetElement.parentNode, targetOffset + 1);
+ }
+}
+
+function StructChangeTag()
+{
+ var textbox = document.createXULElement("textbox");
+ textbox.setAttribute("value", gContextMenuNode.getAttribute("value"));
+ textbox.setAttribute("width", gContextMenuNode.getBoundingClientRect().width);
+ textbox.className = "struct-textbox";
+
+ gContextMenuNode.parentNode.replaceChild(textbox, gContextMenuNode);
+
+ textbox.addEventListener("keypress", OnKeyPress);
+ textbox.addEventListener("blur", ResetStructToolbar, true);
+
+ textbox.select();
+}
+
+function StructSelectTag()
+{
+ SelectFocusNodeAncestor(gContextMenuFiringDocumentElement);
+}
+
+function OpenAdvancedProperties()
+{
+ doAdvancedProperties(gContextMenuFiringDocumentElement);
+}
+
+function OnKeyPress(event)
+{
+ var editor = GetCurrentEditor();
+
+ var keyCode = event.keyCode;
+ if (keyCode == 13) {
+ var newTag = event.target.value;
+
+ var element = gContextMenuFiringDocumentElement;
+
+ var offset = 0;
+ var childNodes = element.parentNode.childNodes;
+ while (childNodes.item(offset) != element) {
+ offset++;
+ }
+
+ editor.beginTransaction();
+
+ try {
+ var newElt = editor.document.createXULElement(newTag);
+ if (newElt) {
+ childNodes = element.childNodes;
+ var childNodesLength = childNodes.length;
+ var i;
+ for (i = 0; i < childNodesLength; i++) {
+ var clone = childNodes.item(i).cloneNode(true);
+ newElt.appendChild(clone);
+ }
+ editor.insertNode(newElt, element.parentNode, offset+1);
+ editor.deleteNode(element);
+ editor.selectElement(newElt);
+
+ window.content.focus();
+ }
+ }
+ catch (e) {}
+
+ editor.endTransaction();
+
+ }
+ else if (keyCode == 27) {
+ // if the user hits Escape, we discard the changes
+ window.content.focus();
+ }
+}
diff --git a/comm/suite/editor/base/content/composerOverlay.xhtml b/comm/suite/editor/base/content/composerOverlay.xhtml
new file mode 100644
index 0000000000..1d36db50b1
--- /dev/null
+++ b/comm/suite/editor/base/content/composerOverlay.xhtml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay [
+<!ENTITY % editorDTD SYSTEM "chrome://editor/locale/editor.dtd">
+%editorDTD;
+]>
+
+<overlay id="composerOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <!-- Items in the File menu used only by Composer app -->
+ <menupopup id="menu_FilePopup">
+ <menuitem id="fileExportToText"
+ insertafter="sep_print"
+ command="cmd_exportToText"/>
+ <menuitem id="previewInBrowser"
+ label="&previewCmd.label;"
+ accesskey="&previewCmd.accesskey;"
+ insertafter="fileExportToText"
+ command="cmd_preview"/>
+ <!-- menuitem id="menu_SendPage" is merged here from mailEditorOverlay.xhtml,
+ where "position" is assumed to be just after 'previewInBrowser' -->
+ </menupopup>
+
+</overlay>
diff --git a/comm/suite/editor/base/content/editingOverlay.js b/comm/suite/editor/base/content/editingOverlay.js
new file mode 100644
index 0000000000..19b41d321b
--- /dev/null
+++ b/comm/suite/editor/base/content/editingOverlay.js
@@ -0,0 +1,387 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gUntitledString;
+
+function TextEditorOnLoad()
+{
+ var url = "about:blank";
+ // See if argument was passed.
+ if (window.arguments && window.arguments[0])
+ {
+ // Opened via window.openDialog with URL as argument.
+ url = window.arguments[0];
+ }
+ // Continue with normal startup.
+ EditorStartup(url);
+}
+
+function EditorOnLoad()
+{
+ var url = "about:blank";
+ var charset;
+ // See if argument was passed.
+ if (window.arguments)
+ {
+ if (window.arguments[0])
+ {
+ // Opened via window.openDialog with URL as argument.
+ url = window.arguments[0];
+ }
+
+ // get default character set if provided
+ if (window.arguments.length > 1 && window.arguments[1])
+ {
+ if (window.arguments[1].includes("charset="))
+ {
+ var arrayArgComponents = window.arguments[1].split("=");
+ if (arrayArgComponents)
+ charset = arrayArgComponents[1];
+ }
+ }
+ }
+
+ // XUL elements we use when switching from normal editor to edit source.
+ gContentWindowDeck = document.getElementById("ContentWindowDeck");
+ gFormatToolbar = document.getElementById("FormatToolbar");
+
+ // Continue with normal startup.
+ EditorStartup(url, charset);
+
+ // Hide Highlight button if we are in an HTML editor with CSS mode off
+ // and tell the editor if a CR in a paragraph creates a new paragraph.
+ var cmd = document.getElementById("cmd_highlight");
+ if (cmd) {
+ if (!Services.prefs.getBoolPref(kUseCssPref))
+ cmd.collapsed = true;
+ }
+
+ // Initialize our source text <editor>
+ try {
+ gSourceContentWindow = document.getElementById("content-source");
+ gSourceContentWindow.makeEditable("text", false);
+ gSourceTextEditor = gSourceContentWindow.getEditor(gSourceContentWindow.contentWindow);
+ gSourceTextEditor.enableUndo(false);
+ gSourceTextEditor.rootElement.style.fontFamily = "-moz-fixed";
+ gSourceTextEditor.rootElement.style.whiteSpace = "pre";
+ gSourceTextEditor.rootElement.style.margin = 0;
+ var controller = Cc["@mozilla.org/embedcomp/base-command-controller;1"]
+ .createInstance(Ci.nsIControllerContext);
+ controller.setCommandContext(gSourceContentWindow);
+ gSourceContentWindow.contentWindow.controllers.insertControllerAt(0, controller);
+ var commandTable = controller.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIControllerCommandTable);
+ commandTable.registerCommand("cmd_findReplace", nsFindReplaceCommand);
+ commandTable.registerCommand("cmd_find", nsFindCommand);
+ commandTable.registerCommand("cmd_findNext", nsFindAgainCommand);
+ commandTable.registerCommand("cmd_findPrev", nsFindAgainCommand);
+ } catch (e) {
+ dump("makeEditable failed: "+e+"\n");
+ }
+}
+
+function toggleAffectedChrome(aHide)
+{
+ // chrome to toggle includes:
+ // (*) menubar
+ // (*) toolbox
+ // (*) sidebar
+ // (*) statusbar
+
+ if (!gChromeState)
+ gChromeState = new Object;
+
+ var statusbar = document.getElementById("status-bar");
+
+ // sidebar states map as follows:
+ // hidden => hide/show nothing
+ // collapsed => hide/show only the splitter
+ // shown => hide/show the splitter and the box
+ if (aHide)
+ {
+ // going into print preview mode
+ gChromeState.sidebar = SidebarGetState();
+ SidebarSetState("hidden");
+
+ // deal with the Status Bar
+ gChromeState.statusbarWasHidden = statusbar.hidden;
+ statusbar.hidden = true;
+ }
+ else
+ {
+ // restoring normal mode (i.e., leaving print preview mode)
+ SidebarSetState(gChromeState.sidebar);
+
+ // restore the Status Bar
+ statusbar.hidden = gChromeState.statusbarWasHidden;
+ }
+
+ // if we are unhiding and sidebar used to be there rebuild it
+ if (!aHide && gChromeState.sidebar == "visible")
+ SidebarRebuild();
+
+ document.getElementById("EditorToolbox").hidden = aHide;
+ document.getElementById("appcontent").collapsed = aHide;
+}
+
+var PrintPreviewListener = {
+ getPrintPreviewBrowser: function () {
+ var browser = document.getElementById("ppBrowser");
+ if (!browser) {
+ browser = document.createXULElement("browser");
+ browser.setAttribute("id", "ppBrowser");
+ browser.setAttribute("flex", "1");
+ browser.setAttribute("disablehistory", "true");
+ browser.setAttribute("disablesecurity", "true");
+ browser.setAttribute("type", "content");
+ document.getElementById("sidebar-parent").
+ insertBefore(browser, document.getElementById("appcontent"));
+ }
+ return browser;
+ },
+ getSourceBrowser: function () {
+ return GetCurrentEditorElement();
+ },
+ getNavToolbox: function () {
+ return document.getElementById("EditorToolbox");
+ },
+ onEnter: function () {
+ toggleAffectedChrome(true);
+ },
+ onExit: function () {
+ document.getElementById("ppBrowser").collapsed = true;
+ toggleAffectedChrome(false);
+ }
+}
+
+function EditorStartup(aUrl, aCharset)
+{
+ gUntitledString = GetFormattedString("untitledTitle", GetNextUntitledValue());
+
+ var ds = GetCurrentEditorElement().docShell;
+ ds.useErrorPages = false;
+ var root = ds.QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem.QueryInterface(Ci.nsIDocShell);
+
+ root.QueryInterface(Ci.nsIDocShell).appType =
+ Ci.nsIDocShell.APP_TYPE_EDITOR;
+
+ // EditorSharedStartup also used by Message Composer.
+ EditorSharedStartup();
+
+ // Commands specific to the Composer Application window,
+ // (i.e., not embedded editors)
+ // such as file-related commands, HTML Source editing, Edit Modes...
+ SetupComposerWindowCommands();
+
+ gCSSPrefListener = new nsPrefListener(kUseCssPref);
+ gReturnInParagraphPrefListener = new nsPrefListener(kCRInParagraphsPref);
+ Services.obs.addObserver(EditorCanClose, "quit-application-requested");
+
+ root.charset = aCharset;
+
+ // Get url for editor content and load it. The editor gets instantiated by
+ // the editingSession when the URL has finished loading.
+ EditorLoadUrl(aUrl);
+
+ // Before and after callbacks for the customizeToolbar code.
+ var editorToolbox = getEditorToolbox();
+ editorToolbox.customizeInit = EditorToolboxCustomizeInit;
+ editorToolbox.customizeDone = EditorToolboxCustomizeDone;
+ editorToolbox.customizeChange = EditorToolboxCustomizeChange;
+}
+
+function EditorShutdown()
+{
+ Services.obs.removeObserver(EditorCanClose, "quit-application-requested");
+
+ gCSSPrefListener.shutdown();
+ gReturnInParagraphPrefListener.shutdown();
+
+ try
+ {
+ var commandManager = GetCurrentCommandManager();
+ commandManager.removeCommandObserver(gEditorDocumentObserver,
+ "obs_documentCreated");
+ commandManager.removeCommandObserver(gEditorDocumentObserver,
+ "obs_documentWillBeDestroyed");
+ commandManager.removeCommandObserver(gEditorDocumentObserver,
+ "obs_documentLocationChanged");
+ } catch (e) { dump (e); }
+}
+
+// --------------------------- File menu ---------------------------
+
+// Check for changes to document and allow saving before closing
+// This is hooked up to the OS's window close widget (e.g., "X" for Windows)
+async function EditorCanClose(aCancelQuit, aTopic, aData)
+{
+ if (aTopic == "quit-application-requested" &&
+ aCancelQuit instanceof Ci.nsISupportsPRBool &&
+ aCancelQuit.data)
+ return false;
+
+ // Returns FALSE only if user cancels save action
+
+ // "true" means allow "Don't Save" button
+ var canClose = await CheckAndSaveDocument("cmd_close", true);
+
+ // This is our only hook into closing via the "X" in the caption
+ // or "Quit" (or other paths?)
+ // so we must shift association to another
+ // editor or close any non-modal windows now
+ if (canClose && "InsertCharWindow" in window && window.InsertCharWindow)
+ SwitchInsertCharToAnotherEditorOrClose();
+
+ if (!canClose && aTopic == "quit-application-requested")
+ aCancelQuit.data = true;
+
+ return canClose;
+}
+
+function BuildRecentPagesMenu()
+{
+ var editor = GetCurrentEditor();
+ if (!editor)
+ return;
+
+ var popup = document.getElementById("menupopup_RecentFiles");
+ if (!popup || !editor.document)
+ return;
+
+ // Delete existing menu
+ while (popup.hasChildNodes())
+ popup.lastChild.remove();
+
+ // Current page is the "0" item in the list we save in prefs,
+ // but we don't include it in the menu.
+ var curUrl = StripPassword(GetDocumentUrl());
+ var historyCount = Services.prefs.getIntPref("editor.history.url_maximum", 10);
+
+ var menuIndex = 1;
+ for (var i = 0; i < historyCount; i++)
+ {
+ var url = Services.prefs.getStringPref("editor.history_url_" + i, "");
+
+ // Skip over current url
+ if (url && url != curUrl)
+ {
+ // Build the menu
+ var title = Services.prefs.getStringPref("editor.history_title_" + i, "");
+ var fileType = Services.prefs.getStringPref("editor.history_type_" + i, "");
+ AppendRecentMenuitem(popup, title, url, fileType, menuIndex);
+ menuIndex++;
+ }
+ }
+}
+
+function AppendRecentMenuitem(aPopup, aTitle, aUrl, aFileType, aIndex)
+{
+ if (!aPopup)
+ return;
+
+ var menuItem = document.createXULElement("menuitem");
+ if (!menuItem)
+ return;
+
+ var accessKey = aIndex <= 10 ? String(aIndex % 10) : " ";
+
+ // Show "title [url]" or just the URL.
+ var itemString = aTitle ? aTitle + " [" + aUrl + "]" : aUrl;
+
+ menuItem.setAttribute("label", accessKey + " " + itemString);
+ menuItem.setAttribute("crop", "center");
+ menuItem.setAttribute("tooltiptext", aUrl);
+ menuItem.setAttribute("value", aUrl);
+ menuItem.setAttribute("fileType", aFileType);
+ if (accessKey != " ")
+ menuItem.setAttribute("accesskey", accessKey);
+ aPopup.appendChild(menuItem);
+}
+
+function EditorInitFileMenu()
+{
+ // Disable "Save" menuitem when editing remote url. User should use "Save As"
+
+ var docUrl = GetDocumentUrl();
+ var scheme = GetScheme(docUrl);
+ if (scheme && scheme != "file")
+ SetElementEnabledById("menu_saveCmd", false);
+
+ // Enable recent pages submenu if there are any history entries in prefs.
+ var historyUrl = "";
+
+ if (Services.prefs.getIntPref("editor.history.url_maximum", 10))
+ {
+ historyUrl = Services.prefs.getStringPref("editor.history_url_0", "");
+
+ // See if there's more if current file is only entry in history list.
+ if (historyUrl && historyUrl == docUrl)
+ historyUrl = Services.prefs.getStringPref("editor.history_url_1", "");
+ }
+ SetElementEnabledById("menu_RecentFiles", historyUrl != "");
+}
+
+function EditorUpdateCharsetMenu(aMenuPopup)
+{
+ if (IsDocumentModified() && !IsDocumentEmpty())
+ {
+ for (var i = 0; i < aMenuPopup.childNodes.length; i++)
+ aMenuPopup.childNodes[i].setAttribute("disabled", "true");
+ }
+
+ UpdateCharsetMenu(content.document.characterSet, aMenuPopup);
+}
+
+// Zoom support.
+function getBrowser()
+{
+ return IsInHTMLSourceMode() ? gSourceContentWindow : GetCurrentEditorElement();
+}
+
+// override the site-specific zoom object in viewZoomOverlay.js
+var FullZoom = {
+ init: function() {},
+ reduce: function() { ZoomManager.reduce(); },
+ enlarge: function() { ZoomManager.enlarge(); },
+ zoom: function(aZoomValue) { ZoomManager.zoom = aZoomValue; },
+ reset: function() { ZoomManager.zoom = 1; },
+ setOther: function() { openZoomDialog(); }
+};
+
+function hideEditorUI(aHide) {
+ for (let id of ["EditModeToolbar", "content-source", "content-frame"]) {
+ let element = document.getElementById(id);
+ if (!element)
+ continue;
+
+ if (aHide) {
+ element.setAttribute("moz-collapsed", true);
+ } else {
+ element.removeAttribute("moz-collapsed");
+ }
+ }
+}
+
+function getEditorToolbox() {
+ return document.getElementById("EditorToolbox");
+}
+
+function EditorToolboxCustomizeInit() {
+ if (document.commandDispatcher.focusedWindow == content)
+ window.focus();
+ hideEditorUI(true);
+ toolboxCustomizeInit("main-menubar");
+}
+
+function EditorToolboxCustomizeDone(aToolboxChanged) {
+ toolboxCustomizeDone("main-menubar", getEditorToolbox(), aToolboxChanged);
+ hideEditorUI(false);
+ gContentWindow.focus();
+}
+
+function EditorToolboxCustomizeChange(aEvent) {
+ toolboxCustomizeChange(getEditorToolbox(), aEvent);
+}
diff --git a/comm/suite/editor/base/content/editingOverlay.xhtml b/comm/suite/editor/base/content/editingOverlay.xhtml
new file mode 100644
index 0000000000..21833857a6
--- /dev/null
+++ b/comm/suite/editor/base/content/editingOverlay.xhtml
@@ -0,0 +1,247 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xul-overlay href="chrome://communicator/content/viewZoomOverlay.xhtml"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://editor/locale/editingOverlay.dtd">
+
+<overlay id="editingOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <script src="chrome://editor/content/editingOverlay.js"/>
+
+ <stringbundleset>
+ <stringbundle id="bundle_viewZoom"/>
+ </stringbundleset>
+
+ <keyset id="editorKeys">
+ <key id="key_openRemoteEditor"
+ key="&openRemoteCmd.key;"
+ command="cmd_openRemote"
+ modifiers="accel,shift"/>
+ <key id="key_openEditor"
+ key="&openFileCmd.key;"
+ command="cmd_open"
+ modifiers="accel"/>
+ <key id="key_publish"
+ key="&publishCmd.key;"
+ command="cmd_publish"
+ modifiers="accel,shift"/>
+ <keyset id="viewZoomKeys"/>
+ </keyset>
+
+ <!-- commands updated when the editor gets created -->
+ <commandset id="commonEditorMenuItems">
+ <command id="cmd_open"
+ oncommand="goDoCommand('cmd_open');"/>
+ <command id="cmd_openRemote"
+ oncommand="goDoCommand('cmd_openRemote');"/>
+ </commandset>
+
+ <commandset id="composerSaveMenuItems"
+ commandupdater="true"
+ events="create, save"
+ oncommandupdate="goUpdateComposerMenuItems(this);">
+ <command id="cmd_save"
+ label="&saveCmd.label;"
+ oncommand="goDoCommand('cmd_save');"/>
+ <command id="cmd_saveAs"
+ oncommand="goDoCommand('cmd_saveAs');"/>
+ <command id="cmd_saveAndChangeEncoding"
+ oncommand="goDoCommand('cmd_saveAndChangeEncoding');"/>
+ <command id="cmd_publish"
+ label="&publishCmd.label;"
+ oncommand="goDoCommand('cmd_publish');"/>
+ <command id="cmd_publishAs"
+ oncommand="goDoCommand('cmd_publishAs');"/>
+ <command id="cmd_revert"
+ oncommand="goDoCommand('cmd_revert');"/>
+ </commandset>
+
+ <commandset id="composerEditMenuItems">
+ <command id="cmd_publishSettings"
+ oncommand="goDoCommand('cmd_publishSettings');"/>
+ </commandset>
+
+ <commandset id="composerMenuItems">
+ <command id="cmd_form"
+ oncommand="goDoCommand('cmd_form');"/>
+ <command id="cmd_inputtag"
+ oncommand="goDoCommand('cmd_inputtag');"/>
+ <command id="cmd_inputimage"
+ oncommand="goDoCommand('cmd_inputimage');"/>
+ <command id="cmd_textarea"
+ oncommand="goDoCommand('cmd_textarea');"/>
+ <command id="cmd_select"
+ oncommand="goDoCommand('cmd_select');"/>
+ <command id="cmd_button"
+ oncommand="goDoCommand('cmd_button');"/>
+ <command id="cmd_label"
+ oncommand="goDoCommand('cmd_label');"/>
+ <command id="cmd_fieldset"
+ oncommand="goDoCommand('cmd_fieldset');"/>
+ </commandset>
+
+ <commandset id="editorCommands">
+ <command id="cmd_CustomizeToolbars"
+ oncommand="goCustomizeToolbar(getEditorToolbox());"/>
+ <commandset id="viewZoomCommands"/>
+ </commandset>
+
+ <!-- File menu items -->
+ <menu id="menu_File">
+ <menupopup id="menu_FilePopup" onpopupshowing="EditorInitFileMenu();">
+ <menu id="menu_New" class="menu-iconic">
+ <menupopup id="menu_NewPopup">
+ <menuitem id="menu_newEditor"/>
+ <menuseparator id="sep_NewPopup"/>
+ <menuitem id="menu_newNavigator"/>
+ <menuitem id="menu_newPrivateWindow"/>
+ </menupopup>
+ </menu>
+ <menuitem id="menu_openRemote"
+ label="&openRemoteCmd.label;"
+ accesskey="&openRemoteCmd.accesskey;"
+ key="key_openRemoteEditor"
+ command="cmd_openRemote"/>
+ <menuitem id="menu_openFile"
+ label="&openFileCmd.label;"
+ accesskey="&openFileCmd.accesskey;"
+ key="key_openEditor"
+ command="cmd_open"/>
+ <menu id="menu_RecentFiles"
+ label="&fileRecentMenu.label;"
+ accesskey="&fileRecentMenu.accesskey;"
+ onpopupshowing="BuildRecentPagesMenu();">
+ <menupopup id="menupopup_RecentFiles"
+ oncommand="editPage(event.target.getAttribute('value'),
+ event.target.getAttribute('fileType'));"/>
+ <!-- menuitems appended at runtime -->
+ </menu>
+ <menuitem id="menu_close" class="menuitem-iconic"/>
+ <menuseparator id="sep_close"/>
+ <menuitem id="menu_saveCmd"
+ accesskey="&saveCmd.accesskey;"
+ key="key_save"
+ command="cmd_save"
+ class="menuitem-iconic"/>
+ <menuitem id="menu_saveAsCmd"
+ label="&saveAsCmd.label;"
+ accesskey="&saveAsCmd.accesskey;"
+ command="cmd_saveAs"
+ class="menuitem-iconic"/>
+ <menuitem id="menu_saveAsChangeEncoding"
+ label="&saveAsChangeEncodingCmd2.label;"
+ accesskey="&saveAsChangeEncodingCmd2.accesskey;"
+ command="cmd_saveAndChangeEncoding"/>
+ <menuseparator id="sep_saveCmd"/>
+ <menuitem id="menu_publish"
+ accesskey="&publishCmd.accesskey;"
+ key="key_publish"
+ command="cmd_publish"/>
+ <menuitem id="menu_publishAs"
+ label="&publishAsCmd.label;"
+ accesskey="&publishAsCmd.accesskey;"
+ command="cmd_publishAs"/>
+ <menuseparator id="sep_publishAs"/>
+ <menuitem id="menu_fileRevert"
+ label="&fileRevert.label;"
+ accesskey="&fileRevert.accesskey;"
+ command="cmd_revert"/>
+ <menuseparator id="sep_print"/>
+ <!-- menuitems are merged in here from composerOverlay.xhtml -->
+ <menuitem id="menu_printSetup"/>
+ <menuitem id="menu_printPreview" class="menuitem-iconic"/>
+ <menuitem id="menu_print" class="menuitem-iconic"/>
+ <!-- The Exit/Quit item is merged from platformGlobalOverlay.xhtml -->
+ </menupopup>
+ </menu>
+
+ <!-- Edit menu items -->
+ <menupopup id="menu_EditPopup">
+ <menuitem id="menu_inlineSpellCheck"
+ oncommand="InlineSpellCheckerUI.enabled = !InlineSpellCheckerUI.enabled"
+ class="menuitem-iconic"/>
+ <menuitem id="menu_publishSettings"
+ insertafter="sep_preferences"
+ label="&publishSettings.label;"
+ accesskey="&publishSettings.accesskey;"
+ command="cmd_publishSettings"/>
+ </menupopup>
+
+ <menupopup id="menu_View_Popup">
+ <menu id="menu_zoom" insertbefore="charsetMenu"/>
+ </menupopup>
+
+ <menupopup id="insertMenuPopup">
+ <menu id="insertFormMenu"
+ insertafter="insertTOC"
+ label="&insertFormMenu.label;"
+ accesskey="&insertFormMenu.accesskey;">
+ <menupopup id="formMenuPopup">
+ <menuitem label="&insertFormCmd.label;"
+ accesskey="&insertFormCmd.accesskey;"
+ command="cmd_form"/>
+ <menuseparator/>
+ <menuitem label="&insertInputTagCmd.label;"
+ accesskey="&insertInputTagCmd.accesskey;"
+ command="cmd_inputtag"/>
+ <menuitem label="&insertInputImageCmd.label;"
+ accesskey="&insertInputImageCmd.accesskey;"
+ command="cmd_inputimage"/>
+ <menuitem label="&insertTextAreaCmd.label;"
+ accesskey="&insertTextAreaCmd.accesskey;"
+ command="cmd_textarea"/>
+ <menuitem label="&insertSelectCmd.label;"
+ accesskey="&insertSelectCmd.accesskey;"
+ command="cmd_select"/>
+ <menuitem label="&insertButtonCmd.label;"
+ accesskey="&insertButtonCmd.accesskey;"
+ command="cmd_button"/>
+ <menuitem label="&insertLabelCmd.label;"
+ accesskey="&insertLabelCmd.accesskey;"
+ command="cmd_label"/>
+ <menuitem label="&insertFieldSetCmd.label;"
+ accesskey="&insertFieldSetCmd.accesskey;"
+ command="cmd_fieldset"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+
+ <!-- Toolbar buttons/items -->
+ <toolbarbutton id="newButton"
+ class="toolbarbutton-1"
+ label="&newToolbarCmd.label;"
+ removable="true"
+ command="cmd_newEditor"
+ tooltiptext="&newToolbarCmd.tooltip;"/>
+ <toolbarbutton id="openButton"
+ class="toolbarbutton-1"
+ label="&openToolbarCmd.label;"
+ removable="true"
+ command="cmd_open"
+ tooltiptext="&openToolbarCmd.tooltip;"/>
+ <toolbarbutton id="saveButton"
+ class="toolbarbutton-1"
+ removable="true"
+ command="cmd_save"
+ tooltiptext="&saveToolbarCmd.tooltip;"/>
+ <toolbarbutton id="publishButton"
+ class="toolbarbutton-1"
+ removable="true"
+ command="cmd_publish"
+ tooltiptext="&publishToolbarCmd.tooltip;"/>
+ <toolbarbutton id="print-button"
+ label="&printToolbarCmd.label;"
+ removable="true"
+ tooltiptext="&printToolbarCmd.tooltip;"/>
+ <!-- 'print-button' is merged in here from utilityOverlay.xhtml -->
+ <toolbarbutton id="formButton"
+ class="toolbarbutton-1"
+ removable="true"
+ label="&formToolbarCmd.label;"
+ command="cmd_form"
+ tooltiptext="&formToolbarCmd.tooltip;"/>
+</overlay>
diff --git a/comm/suite/editor/base/content/editor.js b/comm/suite/editor/base/content/editor.js
new file mode 100644
index 0000000000..34270d057a
--- /dev/null
+++ b/comm/suite/editor/base/content/editor.js
@@ -0,0 +1,3383 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../../../mail/base/content/utilityOverlay.js */
+/* import-globals-from ComposerCommands.js */
+/* import-globals-from editorUtilities.js */
+/* globals InlineSpellCheckerUI */
+
+var { GetNextUntitledValue } = ChromeUtils.import(
+ "resource:///modules/editorUtilities.jsm"
+);
+var { Async } = ChromeUtils.import("resource://services-common/async.js");
+var { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+/* Main Composer window UI control */
+
+var gComposerWindowControllerID = 0;
+var prefAuthorString = "";
+
+var kDisplayModeNormal = 0;
+var kDisplayModeAllTags = 1;
+var kDisplayModeSource = 2;
+var kDisplayModePreview = 3;
+
+const kDisplayModeMenuIDs = [
+ "viewNormalMode",
+ "viewAllTagsMode",
+ "viewSourceMode",
+ "viewPreviewMode",
+];
+const kDisplayModeTabIDS = [
+ "NormalModeButton",
+ "TagModeButton",
+ "SourceModeButton",
+ "PreviewModeButton",
+];
+const kNormalStyleSheet = "chrome://editor/content/EditorContent.css";
+const kAllTagsStyleSheet = "chrome://editor/content/EditorAllTags.css";
+const kContentEditableStyleSheet = "resource://gre/res/contenteditable.css";
+
+var kTextMimeType = "text/plain";
+var kHTMLMimeType = "text/html";
+var kXHTMLMimeType = "application/xhtml+xml";
+
+var gPreviousNonSourceDisplayMode = 1;
+var gEditorDisplayMode = -1;
+var gDocWasModified = false; // Check if clean document, if clean then unload when user "Opens"
+var gContentWindow = 0;
+var gSourceContentWindow = 0;
+var gSourceTextEditor = null;
+var gContentWindowDeck;
+var gFormatToolbar;
+var gFormatToolbarHidden = false;
+var gChromeState;
+var gColorObj = {
+ LastTextColor: "",
+ LastBackgroundColor: "",
+ LastHighlightColor: "",
+ Type: "",
+ SelectedType: "",
+ NoDefault: false,
+ Cancel: false,
+ HighlightColor: "",
+ BackgroundColor: "",
+ PageColor: "",
+ TextColor: "",
+ TableColor: "",
+ CellColor: "",
+};
+var gDefaultTextColor = "";
+var gDefaultBackgroundColor = "";
+var gCSSPrefListener;
+var gReturnInParagraphPrefListener;
+var gLocalFonts = null;
+
+var gLastFocusNode = null;
+var gLastFocusNodeWasSelected = false;
+
+// These must be kept in synch with the XUL <options> lists
+var gFontSizeNames = [
+ "xx-small",
+ "x-small",
+ "small",
+ "medium",
+ "large",
+ "x-large",
+ "xx-large",
+];
+
+var nsIFilePicker = Ci.nsIFilePicker;
+
+var kUseCssPref = "editor.use_css";
+var kCRInParagraphsPref = "editor.CR_creates_new_p";
+
+function nsPrefListener(prefName) {
+ this.startup(prefName);
+}
+
+// implements nsIObserver
+nsPrefListener.prototype = {
+ domain: "",
+ startup(prefName) {
+ this.domain = prefName;
+ try {
+ Services.prefs.addObserver(this.domain, this);
+ } catch (ex) {
+ dump("Failed to observe prefs: " + ex + "\n");
+ }
+ },
+ shutdown() {
+ try {
+ Services.prefs.removeObserver(this.domain, this);
+ } catch (ex) {
+ dump("Failed to remove pref observers: " + ex + "\n");
+ }
+ },
+ observe(subject, topic, prefName) {
+ if (!IsHTMLEditor()) {
+ return;
+ }
+ // verify that we're changing a button pref
+ if (topic != "nsPref:changed") {
+ return;
+ }
+
+ let editor = GetCurrentEditor();
+ if (prefName == kUseCssPref) {
+ let cmd = document.getElementById("cmd_highlight");
+ if (cmd) {
+ let useCSS = Services.prefs.getBoolPref(prefName);
+
+ if (useCSS && editor) {
+ let mixedObj = {};
+ let state = editor.getHighlightColorState(mixedObj);
+ cmd.setAttribute("state", state);
+ cmd.collapsed = false;
+ } else {
+ cmd.setAttribute("state", "transparent");
+ cmd.collapsed = true;
+ }
+
+ if (editor) {
+ editor.isCSSEnabled = useCSS;
+ }
+ }
+ } else if (editor && prefName == kCRInParagraphsPref) {
+ editor.returnInParagraphCreatesNewParagraph = Services.prefs.getBoolPref(
+ prefName
+ );
+ }
+ },
+};
+
+const gSourceTextListener = {
+ NotifyDocumentCreated() {},
+ NotifyDocumentWillBeDestroyed() {},
+ NotifyDocumentStateChanged(isChanged) {
+ window.updateCommands("save");
+ },
+};
+
+const gSourceTextObserver = {
+ observe(aSubject, aTopic, aData) {
+ // we currently only use this to update undo
+ window.updateCommands("undo");
+ },
+};
+
+// This should be called by all editor users when they close their window.
+function EditorCleanup() {
+ SwitchInsertCharToAnotherEditorOrClose();
+}
+
+var DocumentReloadListener = {
+ NotifyDocumentCreated() {},
+ NotifyDocumentWillBeDestroyed() {},
+
+ NotifyDocumentStateChanged(isNowDirty) {
+ var editor = GetCurrentEditor();
+ try {
+ // unregister the listener to prevent multiple callbacks
+ editor.removeDocumentStateListener(DocumentReloadListener);
+
+ var charset = editor.documentCharacterSet;
+
+ // update the META charset with the current presentation charset
+ editor.documentCharacterSet = charset;
+ } catch (e) {}
+ },
+};
+
+// implements nsIObserver
+var gEditorDocumentObserver = {
+ observe(aSubject, aTopic, aData) {
+ // Should we allow this even if NOT the focused editor?
+ var commandManager = GetCurrentCommandManager();
+ if (commandManager != aSubject) {
+ return;
+ }
+
+ var editor = GetCurrentEditor();
+ switch (aTopic) {
+ case "obs_documentCreated":
+ // Just for convenience
+ gContentWindow = window.content;
+
+ // Get state to see if document creation succeeded
+ var params = newCommandParams();
+ if (!params) {
+ return;
+ }
+
+ try {
+ commandManager.getCommandState(aTopic, gContentWindow, params);
+ var errorStringId = 0;
+ var editorStatus = params.getLongValue("state_data");
+ if (!editor && editorStatus == nsIEditingSession.eEditorOK) {
+ dump(
+ "\n ****** NO EDITOR BUT NO EDITOR ERROR REPORTED ******* \n\n"
+ );
+ editorStatus = nsIEditingSession.eEditorErrorUnknown;
+ }
+
+ switch (editorStatus) {
+ case nsIEditingSession.eEditorErrorCantEditFramesets:
+ errorStringId = "CantEditFramesetMsg";
+ break;
+ case nsIEditingSession.eEditorErrorCantEditMimeType:
+ errorStringId = "CantEditMimeTypeMsg";
+ break;
+ case nsIEditingSession.eEditorErrorUnknown:
+ errorStringId = "CantEditDocumentMsg";
+ break;
+ // Note that for "eEditorErrorFileNotFound,
+ // network code popped up an alert dialog, so we don't need to
+ }
+ if (errorStringId) {
+ Services.prompt.alert(window, "", GetString(errorStringId));
+ }
+ } catch (e) {
+ dump("EXCEPTION GETTING obs_documentCreated state " + e + "\n");
+ }
+
+ // We have a bad editor -- nsIEditingSession will rebuild an editor
+ // with a blank page, so simply abort here
+ if (editorStatus) {
+ return;
+ }
+
+ if (!("InsertCharWindow" in window)) {
+ window.InsertCharWindow = null;
+ }
+
+ try {
+ editor.QueryInterface(nsIEditorStyleSheets);
+
+ // and extra styles for showing anchors, table borders, smileys, etc
+ editor.addOverrideStyleSheet(kNormalStyleSheet);
+
+ // remove contenteditable stylesheets if they were applied by the
+ // editingSession
+ editor.removeOverrideStyleSheet(kContentEditableStyleSheet);
+ } catch (e) {}
+
+ // Things for just the Web Composer application
+ if (IsWebComposer()) {
+ InlineSpellCheckerUI.init(editor);
+ document
+ .getElementById("menu_inlineSpellCheck")
+ .setAttribute("disabled", !InlineSpellCheckerUI.canSpellCheck);
+
+ editor.returnInParagraphCreatesNewParagraph = Services.prefs.getBoolPref(
+ kCRInParagraphsPref
+ );
+
+ // Set focus to content window if not a mail composer
+ // Race conditions prevent us from setting focus here
+ // when loading a url into blank window
+ setTimeout(SetFocusOnStartup, 0);
+
+ // Call EditorSetDefaultPrefsAndDoctype first so it gets the default author before initing toolbars
+ editor.enableUndo(false);
+ EditorSetDefaultPrefsAndDoctype();
+ editor.resetModificationCount();
+ editor.enableUndo(true);
+
+ // We may load a text document into an html editor,
+ // so be sure editortype is set correctly
+ // XXX We really should use the "real" plaintext editor for this!
+ if (editor.contentsMIMEType == "text/plain") {
+ try {
+ GetCurrentEditorElement().editortype = "text";
+ } catch (e) {
+ dump(e) + "\n";
+ }
+
+ // Hide or disable UI not used for plaintext editing
+ HideItem("FormatToolbar");
+ HideItem("EditModeToolbar");
+ HideItem("formatMenu");
+ HideItem("tableMenu");
+ HideItem("menu_validate");
+ HideItem("sep_validate");
+ HideItem("previewButton");
+ HideItem("imageButton");
+ HideItem("linkButton");
+ HideItem("namedAnchorButton");
+ HideItem("hlineButton");
+ HideItem("tableButton");
+
+ HideItem("fileExportToText");
+ HideItem("previewInBrowser");
+
+ /* XXX When paste actually converts formatted rich text to pretty formatted plain text
+ and pasteNoFormatting is fixed to paste the text without formatting (what paste
+ currently does), then this item shouldn't be hidden: */
+ HideItem("menu_pasteNoFormatting");
+
+ HideItem("cmd_viewEditModeToolbar");
+
+ HideItem("viewSep1");
+ HideItem("viewNormalMode");
+ HideItem("viewAllTagsMode");
+ HideItem("viewSourceMode");
+ HideItem("viewPreviewMode");
+
+ HideItem("structSpacer");
+
+ // Hide everything in "Insert" except for "Symbols"
+ let menuPopupChildren = document.querySelectorAll(
+ '[id="insertMenuPopup"] > :not(#insertChars)'
+ );
+ for (let i = 0; i < menuPopupChildren.length; i++) {
+ menuPopupChildren.item(i).hidden = true;
+ }
+ }
+
+ // Set window title
+ UpdateWindowTitle();
+
+ // We must wait until document is created to get proper Url
+ // (Windows may load with local file paths)
+ SetSaveAndPublishUI(GetDocumentUrl());
+
+ // Start in "Normal" edit mode
+ SetDisplayMode(kDisplayModeNormal);
+ }
+
+ // Add mouse click watcher if right type of editor
+ if (IsHTMLEditor()) {
+ // Force color widgets to update
+ onFontColorChange();
+ onBackgroundColorChange();
+ }
+ break;
+
+ case "cmd_setDocumentModified":
+ window.updateCommands("save");
+ break;
+
+ case "obs_documentWillBeDestroyed":
+ dump("obs_documentWillBeDestroyed notification\n");
+ break;
+
+ case "obs_documentLocationChanged":
+ // Ignore this when editor doesn't exist,
+ // which happens once when page load starts
+ if (editor) {
+ try {
+ editor.updateBaseURL();
+ } catch (e) {
+ dump(e);
+ }
+ }
+ break;
+
+ case "cmd_bold":
+ // Update all style items
+ // cmd_bold is a proxy; see EditorSharedStartup (above) for details
+ window.updateCommands("style");
+ window.updateCommands("undo");
+ break;
+ }
+ },
+};
+
+function SetFocusOnStartup() {
+ gContentWindow.focus();
+}
+
+function EditorLoadUrl(url) {
+ try {
+ if (url) {
+ let loadURIOptions = {
+ loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE,
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ };
+ GetCurrentEditorElement().webNavigation.loadURI(url, loadURIOptions);
+ }
+ } catch (e) {
+ dump(" EditorLoadUrl failed: " + e + "\n");
+ }
+}
+
+// This should be called by all Composer types
+function EditorSharedStartup() {
+ // Just for convenience
+ gContentWindow = window.content;
+
+ // Disable DNS Prefetching on the docshell - we don't need it for composer
+ // type windows.
+ GetCurrentEditorElement().docShell.allowDNSPrefetch = false;
+
+ // Set up the mime type and register the commands.
+ if (IsHTMLEditor()) {
+ SetupHTMLEditorCommands();
+ } else {
+ SetupTextEditorCommands();
+ }
+
+ // add observer to be called when document is really done loading
+ // and is modified
+ // Note: We're really screwed if we fail to install this observer!
+ try {
+ var commandManager = GetCurrentCommandManager();
+ commandManager.addCommandObserver(
+ gEditorDocumentObserver,
+ "obs_documentCreated"
+ );
+ commandManager.addCommandObserver(
+ gEditorDocumentObserver,
+ "cmd_setDocumentModified"
+ );
+ commandManager.addCommandObserver(
+ gEditorDocumentObserver,
+ "obs_documentWillBeDestroyed"
+ );
+ commandManager.addCommandObserver(
+ gEditorDocumentObserver,
+ "obs_documentLocationChanged"
+ );
+
+ // Until nsIControllerCommandGroup-based code is implemented,
+ // we will observe just the bold command to trigger update of
+ // all toolbar style items
+ commandManager.addCommandObserver(gEditorDocumentObserver, "cmd_bold");
+ } catch (e) {
+ dump(e);
+ }
+
+ var isMac = AppConstants.platform == "macosx";
+
+ // Set platform-specific hints for how to select cells
+ // Mac uses "Cmd", all others use "Ctrl"
+ var tableKey = GetString(isMac ? "XulKeyMac" : "TableSelectKey");
+ var dragStr = tableKey + GetString("Drag");
+ var clickStr = tableKey + GetString("Click");
+
+ var delStr = GetString(isMac ? "Clear" : "Del");
+
+ SafeSetAttribute("menu_SelectCell", "acceltext", clickStr);
+ SafeSetAttribute("menu_SelectRow", "acceltext", dragStr);
+ SafeSetAttribute("menu_SelectColumn", "acceltext", dragStr);
+ SafeSetAttribute("menu_SelectAllCells", "acceltext", dragStr);
+ // And add "Del" or "Clear"
+ SafeSetAttribute("menu_DeleteCellContents", "acceltext", delStr);
+
+ // Set text for indent, outdent keybinding
+
+ // hide UI that we don't have components for
+ RemoveInapplicableUIElements();
+
+ // Use browser colors as initial values for editor's default colors
+ var BrowserColors = GetDefaultBrowserColors();
+ if (BrowserColors) {
+ gDefaultTextColor = BrowserColors.TextColor;
+ gDefaultBackgroundColor = BrowserColors.BackgroundColor;
+ }
+
+ // For new window, no default last-picked colors
+ gColorObj.LastTextColor = "";
+ gColorObj.LastBackgroundColor = "";
+ gColorObj.LastHighlightColor = "";
+}
+
+function SafeSetAttribute(nodeID, attributeName, attributeValue) {
+ var theNode = document.getElementById(nodeID);
+ if (theNode) {
+ theNode.setAttribute(attributeName, attributeValue);
+ }
+}
+
+function DocumentHasBeenSaved() {
+ var fileurl = "";
+ try {
+ fileurl = GetDocumentUrl();
+ } catch (e) {
+ return false;
+ }
+
+ if (!fileurl || IsUrlAboutBlank(fileurl)) {
+ return false;
+ }
+
+ // We have a file URL already
+ return true;
+}
+
+async function CheckAndSaveDocument(command, allowDontSave) {
+ var document;
+ try {
+ // if we don't have an editor or an document, bail
+ var editor = GetCurrentEditor();
+ document = editor.document;
+ if (!document) {
+ return true;
+ }
+ } catch (e) {
+ return true;
+ }
+
+ if (!IsDocumentModified() && !IsHTMLSourceChanged()) {
+ return true;
+ }
+
+ // call window.focus, since we need to pop up a dialog
+ // and therefore need to be visible (to prevent user confusion)
+ top.document.commandDispatcher.focusedWindow.focus();
+
+ var scheme = GetScheme(GetDocumentUrl());
+ var doPublish = scheme && scheme != "file";
+
+ var strID;
+ switch (command) {
+ case "cmd_close":
+ strID = "BeforeClosing";
+ break;
+ case "cmd_preview":
+ strID = "BeforePreview";
+ break;
+ case "cmd_editSendPage":
+ strID = "SendPageReason";
+ break;
+ case "cmd_validate":
+ strID = "BeforeValidate";
+ break;
+ }
+
+ var reasonToSave = strID ? GetString(strID) : "";
+
+ var title = document.title || GetString("untitledDefaultFilename");
+
+ var dialogTitle = GetString(doPublish ? "PublishPage" : "SaveDocument");
+ var dialogMsg = GetString(doPublish ? "PublishPrompt" : "SaveFilePrompt");
+ dialogMsg = dialogMsg
+ .replace(/%title%/, title)
+ .replace(/%reason%/, reasonToSave);
+
+ let result = { value: 0 };
+ let promptFlags =
+ Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1;
+ let button1Title = null;
+ let button3Title = null;
+
+ if (doPublish) {
+ promptFlags +=
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0;
+ button1Title = GetString("Publish");
+ button3Title = GetString("DontPublish");
+ } else {
+ promptFlags +=
+ Services.prompt.BUTTON_TITLE_SAVE * Services.prompt.BUTTON_POS_0;
+ }
+
+ // If allowing "Don't..." button, add that
+ if (allowDontSave) {
+ promptFlags += doPublish
+ ? Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_2
+ : Services.prompt.BUTTON_TITLE_DONT_SAVE * Services.prompt.BUTTON_POS_2;
+ }
+
+ result = Services.prompt.confirmEx(
+ window,
+ dialogTitle,
+ dialogMsg,
+ promptFlags,
+ button1Title,
+ null,
+ button3Title,
+ null,
+ { value: 0 }
+ );
+
+ if (result == 0) {
+ // Save, but first finish HTML source mode
+ SetEditMode(gPreviousNonSourceDisplayMode);
+ if (doPublish) {
+ // We save the command the user wanted to do in a global
+ // and return as if user canceled because publishing is asynchronous
+ // This command will be fired when publishing finishes
+ gCommandAfterPublishing = command;
+ goDoCommand("cmd_publish");
+ return false;
+ }
+
+ // Save to local disk
+ return SaveDocument(false, false, editor.contentsMIMEType);
+ }
+
+ if (result == 2) {
+ // "Don't Save"
+ return true;
+ }
+
+ // Default or result == 1 (Cancel)
+ return false;
+}
+
+// --------------------------- View menu ---------------------------
+
+function EditorSetCharacterSet(aEvent) {
+ try {
+ var editor = GetCurrentEditor();
+ if (aEvent.target.hasAttribute("charset")) {
+ editor.documentCharacterSet = aEvent.target.getAttribute("charset");
+ }
+ var docUrl = GetDocumentUrl();
+ if (!IsUrlAboutBlank(docUrl)) {
+ // reloading the document will reverse any changes to the META charset,
+ // we need to put them back in, which is achieved by a dedicated listener
+ editor.addDocumentStateListener(DocumentReloadListener);
+ EditorLoadUrl(docUrl);
+ }
+ } catch (e) {}
+}
+
+// --------------------------- Text style ---------------------------
+
+function onParagraphFormatChange(paraMenuList, commandID) {
+ if (!paraMenuList) {
+ return;
+ }
+
+ var commandNode = document.getElementById(commandID);
+ var state = commandNode.getAttribute("state");
+
+ // force match with "normal"
+ if (state == "body") {
+ state = "";
+ }
+
+ if (state == "mixed") {
+ // Selection is the "mixed" ( > 1 style) state
+ paraMenuList.selectedItem = null;
+ paraMenuList.setAttribute("label", GetString("Mixed"));
+ } else {
+ var menuPopup = document.getElementById("ParagraphPopup");
+ var menuItems = menuPopup.childNodes;
+ for (var i = 0; i < menuItems.length; i++) {
+ var menuItem = menuItems.item(i);
+ if ("value" in menuItem && menuItem.value == state) {
+ paraMenuList.selectedItem = menuItem;
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * Selects the current font face in the menulist.
+ *
+ * @param fontFaceMenuList The menulist element containing the list of fonts.
+ * @param commandID The commandID which holds the current font name
+ * in its "state" attribute.
+ */
+function onFontFaceChange(fontFaceMenuList, commandID) {
+ var commandNode = document.getElementById(commandID);
+ var editorFont = commandNode.getAttribute("state");
+
+ // Strip quotes in font names. Experiments have shown that we only
+ // ever get double quotes around the font name, never single quotes,
+ // even if they were in the HTML source. Also single or double
+ // quotes within the font name are never returned.
+ editorFont = editorFont.replace(/"/g, "");
+
+ switch (editorFont) {
+ case "mixed":
+ // Selection is the "mixed" ( > 1 style) state.
+ fontFaceMenuList.selectedItem = null;
+ fontFaceMenuList.setAttribute("label", GetString("Mixed"));
+ return;
+ case "":
+ case "serif":
+ case "sans-serif":
+ // Generic variable width.
+ fontFaceMenuList.selectedIndex = 0;
+ return;
+ case "tt":
+ case "monospace":
+ // Generic fixed width.
+ fontFaceMenuList.selectedIndex = 1;
+ return;
+ default:
+ }
+
+ let menuPopup = fontFaceMenuList.menupopup;
+ let menuItems = menuPopup.childNodes;
+
+ const genericFamilies = [
+ "serif",
+ "sans-serif",
+ "monospace",
+ "fantasy",
+ "cursive",
+ ];
+ // Bug 1139524: Normalise before we compare: Make it lower case
+ // and replace ", " with "," so that entries like
+ // "Helvetica, Arial, sans-serif" are always recognised correctly
+ let editorFontToLower = editorFont.toLowerCase().replace(/, /g, ",");
+ let foundFont = null;
+ let exactMatch = false;
+ let usedFontsSep = menuPopup.querySelector(
+ "menuseparator.fontFaceMenuAfterUsedFonts"
+ );
+ let editorFontOptions = editorFontToLower.split(",");
+ let editorOptionsCount = editorFontOptions.length;
+ let matchedFontIndex = editorOptionsCount; // initialise to high invalid value
+
+ // The font menu has this structure:
+ // 0: Variable Width
+ // 1: Fixed Width
+ // 2: Separator
+ // 3: Helvetica, Arial (stored as Helvetica, Arial, sans-serif)
+ // 4: Times (stored as Times New Roman, Times, serif)
+ // 5: Courier (stored as Courier New, Courier, monospace)
+ // 6: Separator, "menuseparator.fontFaceMenuAfterDefaultFonts"
+ // from 7: Used Font Section (for quick selection)
+ // followed by separator, "menuseparator.fontFaceMenuAfterUsedFonts"
+ // followed by all other available fonts.
+ // The following variable keeps track of where we are when we loop over the menu.
+ let afterUsedFontSection = false;
+
+ // The menu items not only have "label" and "value", but also some other attributes:
+ // "value_parsed": Is the toLowerCase() and space-stripped value.
+ // "value_cache": Is a concatenation of all editor fonts that were ever mapped
+ // onto this menu item. This is done for optimization.
+ // "used": This item is in the used font section.
+
+ for (let i = 0; i < menuItems.length; i++) {
+ let menuItem = menuItems.item(i);
+ if (
+ menuItem.hasAttribute("label") &&
+ menuItem.hasAttribute("value_parsed")
+ ) {
+ // The element seems to represent a font <menuitem>.
+ let fontMenuValue = menuItem.getAttribute("value_parsed");
+ if (
+ fontMenuValue == editorFontToLower ||
+ (menuItem.hasAttribute("value_cache") &&
+ menuItem
+ .getAttribute("value_cache")
+ .split("|")
+ .includes(editorFontToLower))
+ ) {
+ // This menuitem contains the font we are looking for.
+ foundFont = menuItem;
+ exactMatch = true;
+ break;
+ } else if (editorOptionsCount > 1 && afterUsedFontSection) {
+ // Once we are in the list of all other available fonts,
+ // we will find the one that best matches one of the options.
+ let matchPos = editorFontOptions.indexOf(fontMenuValue);
+ if (matchPos >= 0 && matchPos < matchedFontIndex) {
+ // This menu font comes earlier in the list of options,
+ // so prefer it.
+ matchedFontIndex = matchPos;
+ foundFont = menuItem;
+ // If we matched the first option, we don't need to look for
+ // a better match.
+ if (matchPos == 0) {
+ break;
+ }
+ }
+ }
+ } else if (menuItem == usedFontsSep) {
+ // Some other element type.
+ // We have now passed the section of used fonts and are now in the list of all.
+ afterUsedFontSection = true;
+ }
+ }
+
+ if (foundFont) {
+ let defaultFontsSep = menuPopup.querySelector(
+ "menuseparator.fontFaceMenuAfterDefaultFonts"
+ );
+ if (exactMatch) {
+ if (afterUsedFontSection) {
+ // Copy the matched font into the section of used fonts.
+ // We insert after the separator following the default fonts,
+ // so right at the beginning of the used fonts section.
+ let copyItem = foundFont.cloneNode(true);
+ menuPopup.insertBefore(copyItem, defaultFontsSep.nextSibling);
+ usedFontsSep.hidden = false;
+ foundFont = copyItem;
+ foundFont.setAttribute("used", "true");
+ }
+ } else {
+ // Keep only the found font and generic families in the font string.
+ editorFont = editorFont
+ .replace(/, /g, ",")
+ .split(",")
+ .filter(
+ font =>
+ font.toLowerCase() == foundFont.getAttribute("value_parsed") ||
+ genericFamilies.includes(font)
+ )
+ .join(",");
+
+ // Check if such an item is already in the used font section.
+ if (afterUsedFontSection) {
+ foundFont = menuPopup.querySelector(
+ 'menuitem[used="true"][value_parsed="' +
+ editorFont.toLowerCase() +
+ '"]'
+ );
+ }
+ // If not, create a new entry which will be inserted into that section.
+ if (!foundFont) {
+ foundFont = createFontFaceMenuitem(editorFont, editorFont, menuPopup);
+ }
+
+ // Add the editor font string into the 'cache' attribute in the element
+ // so we can later find it quickly without building the reduced string again.
+ let fontCache = "";
+ if (foundFont.hasAttribute("value_cache")) {
+ fontCache = foundFont.getAttribute("value_cache");
+ }
+ foundFont.setAttribute(
+ "value_cache",
+ fontCache + "|" + editorFontToLower
+ );
+
+ // If we created a new item, set it up and insert.
+ if (!foundFont.hasAttribute("used")) {
+ foundFont.setAttribute("used", "true");
+ usedFontsSep.hidden = false;
+ menuPopup.insertBefore(foundFont, defaultFontsSep.nextSibling);
+ }
+ }
+ } else {
+ // The editor encountered a font that is not installed on this system.
+ // Add it to the font menu now, in the used-fonts section right at the
+ // bottom before the separator of the section.
+ let fontLabel = GetFormattedString("NotInstalled", editorFont);
+ foundFont = createFontFaceMenuitem(fontLabel, editorFont, menuPopup);
+ foundFont.setAttribute("used", "true");
+ usedFontsSep.hidden = false;
+ menuPopup.insertBefore(foundFont, usedFontsSep);
+ }
+ fontFaceMenuList.selectedItem = foundFont;
+}
+
+/**
+ * Clears the used fonts list from all the font face menulists.
+ */
+function ClearUsedFonts() {
+ let userFontSeps = document.querySelectorAll(
+ "menuseparator.fontFaceMenuAfterDefaultFonts"
+ );
+ for (let userFontSep of userFontSeps) {
+ while (true) {
+ let nextNode = userFontSep.nextSibling;
+ if (nextNode.tagName != "menuseparator") {
+ nextNode.remove();
+ } else if (nextNode.classList.contains("fontFaceMenuAfterUsedFonts")) {
+ nextNode.hidden = true;
+ break;
+ }
+ }
+ }
+}
+
+function EditorSelectFontSize() {
+ var select = document.getElementById("FontSizeSelect");
+ if (select) {
+ if (select.selectedIndex == -1) {
+ return;
+ }
+
+ EditorSetFontSize(gFontSizeNames[select.selectedIndex]);
+ }
+}
+
+function onFontSizeChange(fontSizeMenulist, commandID) {
+ // If we don't match anything, set to "0 (normal)"
+ var newIndex = 2;
+ var size = fontSizeMenulist.getAttribute("size");
+ if (size == "mixed") {
+ // No single type selected
+ newIndex = -1;
+ } else {
+ for (var i = 0; i < gFontSizeNames.length; i++) {
+ if (gFontSizeNames[i] == size) {
+ newIndex = i;
+ break;
+ }
+ }
+ }
+ if (fontSizeMenulist.selectedIndex != newIndex) {
+ fontSizeMenulist.selectedIndex = newIndex;
+ }
+}
+
+function EditorSetFontSize(size) {
+ if (size == "0" || size == "normal" || size == "medium") {
+ EditorRemoveTextProperty("font", "size");
+ // Also remove big and small,
+ // else it will seem like size isn't changing correctly
+ EditorRemoveTextProperty("small", "");
+ EditorRemoveTextProperty("big", "");
+ } else {
+ // Temp: convert from new CSS size strings to old HTML size strings
+ switch (size) {
+ case "xx-small":
+ case "x-small":
+ size = "-2";
+ break;
+ case "small":
+ size = "-1";
+ break;
+ case "large":
+ size = "+1";
+ break;
+ case "x-large":
+ size = "+2";
+ break;
+ case "xx-large":
+ size = "+3";
+ break;
+ }
+ EditorSetTextProperty("font", "size", size);
+ }
+ gContentWindow.focus();
+}
+
+function initFontFaceMenu(menuPopup) {
+ initLocalFontFaceMenu(menuPopup);
+
+ if (menuPopup) {
+ var children = menuPopup.childNodes;
+ if (!children) {
+ return;
+ }
+
+ var mixed = { value: false };
+ var editorFont = GetCurrentEditor().getFontFaceState(mixed);
+
+ // Strip quotes in font names. Experiments have shown that we only
+ // ever get double quotes around the font name, never single quotes,
+ // even if they were in the HTML source. Also single or double
+ // quotes within the font name are never returned.
+ editorFont = editorFont.replace(/"/g, "");
+
+ if (!mixed.value) {
+ switch (editorFont) {
+ case "":
+ case "serif":
+ case "sans-serif":
+ // Generic variable width.
+ editorFont = "";
+ break;
+ case "tt":
+ case "monospace":
+ // Generic fixed width.
+ editorFont = "tt";
+ break;
+ default:
+ editorFont = editorFont.toLowerCase().replace(/, /g, ","); // bug 1139524
+ }
+ }
+
+ var editorFontOptions = editorFont.split(",");
+ var matchedOption = editorFontOptions.length; // initialise to high invalid value
+ for (var i = 0; i < children.length; i++) {
+ var menuItem = children[i];
+ if (menuItem.localName == "menuitem") {
+ var matchFound = false;
+ if (!mixed.value) {
+ var menuFont = menuItem
+ .getAttribute("value")
+ .toLowerCase()
+ .replace(/, /g, ",");
+
+ // First compare the entire font string to match items that contain commas.
+ if (menuFont == editorFont) {
+ menuItem.setAttribute("checked", "true");
+ break;
+ } else if (editorFontOptions.length > 1) {
+ // Next compare the individual options.
+ var matchPos = editorFontOptions.indexOf(menuFont);
+ if (matchPos >= 0 && matchPos < matchedOption) {
+ // This menu font comes earlier in the list of options,
+ // so prefer it.
+ menuItem.setAttribute("checked", "true");
+
+ // If we matched the first option, we don't need to look for
+ // a better match.
+ if (matchPos == 0) {
+ break;
+ }
+
+ matchedOption = matchPos;
+ matchFound = true;
+ }
+ }
+ }
+
+ // In case this item doesn't match, make sure we've cleared the checkmark.
+ if (!matchFound) {
+ menuItem.removeAttribute("checked");
+ }
+ }
+ }
+ }
+}
+
+// Number of fixed font face menuitems, these are:
+// Variable Width
+// Fixed Width
+// ==separator
+// Helvetica, Arial
+// Times
+// Courier
+// ==separator
+// ==separator
+const kFixedFontFaceMenuItems = 8;
+
+function initLocalFontFaceMenu(menuPopup) {
+ if (!gLocalFonts) {
+ // Build list of all local fonts once per editor
+ try {
+ var enumerator = Cc["@mozilla.org/gfx/fontenumerator;1"].getService(
+ Ci.nsIFontEnumerator
+ );
+ gLocalFonts = enumerator.EnumerateAllFonts();
+ } catch (e) {}
+ }
+
+ // Don't use radios for menulists.
+ let useRadioMenuitems = menuPopup.parentNode.localName == "menu";
+ menuPopup.setAttribute("useRadios", useRadioMenuitems);
+ if (menuPopup.childNodes.length == kFixedFontFaceMenuItems) {
+ if (gLocalFonts.length == 0) {
+ menuPopup.querySelector(".fontFaceMenuAfterDefaultFonts").hidden = true;
+ }
+ for (let i = 0; i < gLocalFonts.length; ++i) {
+ // Remove Linux system generic fonts that collide with CSS generic fonts.
+ if (
+ gLocalFonts[i] != "" &&
+ gLocalFonts[i] != "serif" &&
+ gLocalFonts[i] != "sans-serif" &&
+ gLocalFonts[i] != "monospace"
+ ) {
+ let itemNode = createFontFaceMenuitem(
+ gLocalFonts[i],
+ gLocalFonts[i],
+ menuPopup
+ );
+ menuPopup.appendChild(itemNode);
+ }
+ }
+ }
+}
+
+/**
+ * Creates a menuitem element for the font faces menulist. Returns the menuitem
+ * but does not add it automatically to the menupopup.
+ *
+ * @param aFontLabel Label to be displayed for the item.
+ * @param aFontName The font face value to be used for the item.
+ * Will be used in <font face="value"> in the edited document.
+ * @param aMenuPopup The menupopup for which this menuitem is created.
+ */
+function createFontFaceMenuitem(aFontLabel, aFontName, aMenuPopup) {
+ let itemNode = document.createXULElement("menuitem");
+ itemNode.setAttribute("label", aFontLabel);
+ itemNode.setAttribute("value", aFontName);
+ itemNode.setAttribute(
+ "value_parsed",
+ aFontName.toLowerCase().replace(/, /g, ",")
+ );
+ itemNode.setAttribute("tooltiptext", aFontLabel);
+ if (aMenuPopup.getAttribute("useRadios") == "true") {
+ itemNode.setAttribute("type", "radio");
+ itemNode.setAttribute("observes", "cmd_renderedHTMLEnabler");
+ }
+ return itemNode;
+}
+
+/**
+ * Helper function
+ */
+function getFontSizeIndex() {
+ var firstHas = { value: false };
+ var anyHas = { value: false };
+ var allHas = { value: false };
+
+ var fontSize = EditorGetTextProperty(
+ "font",
+ "size",
+ null,
+ firstHas,
+ anyHas,
+ allHas
+ );
+
+ // If the element has no size attribute and no size was found at all,
+ // we assume "medium" size. This is highly problematic since
+ // CSS sizes are not recognised and will show as "medium" as well.
+ // Currently we can't distinguish between "no attribute" which
+ // can imply "medium" and "CSS attribute present" which should not
+ // imply "medium".
+ if (!anyHas.value) {
+ return 2;
+ }
+
+ // Mixed selection.
+ if (!allHas.value) {
+ return -1;
+ }
+
+ switch (fontSize) {
+ case "-3":
+ case "-2":
+ case "0":
+ case "1":
+ // x-small.
+ return 0;
+ case "-1":
+ case "2":
+ // small.
+ return 1;
+ case "3":
+ // medium.
+ return 2;
+ case "+1":
+ case "4":
+ // large.
+ return 3;
+ case "+2":
+ case "5":
+ // x-large.
+ return 4;
+ case "+3":
+ case "+4":
+ case "6":
+ case "7":
+ // xx-large.
+ return 5;
+ }
+
+ // We shouldn't get here. All the selection has a value we don't understand.
+ return -1;
+}
+
+function initFontSizeMenu(menuPopup, fullMenu) {
+ if (menuPopup) {
+ var children = menuPopup.childNodes;
+ if (!children) {
+ return;
+ }
+
+ // Fixed size items start after menu separator depending on whether it is
+ // a full menu.
+ var menuIndex = fullMenu ? 3 : 0;
+
+ var setIndex = getFontSizeIndex();
+ if (setIndex >= 0) {
+ children[menuIndex + setIndex].setAttribute("checked", true);
+ } else {
+ // In case of mixed, clear all items.
+ for (var i = menuIndex; i < children.length; i++) {
+ children[i].setAttribute("checked", false);
+ }
+ }
+
+ // Some configurations might not have the "small/big" indicator as
+ // last item. If there is no indicator, we are done.
+ if (!menuPopup.lastChild.id.includes("smallBigInfo")) {
+ return;
+ }
+
+ // While it would be better to show the number of levels,
+ // at least this tells user if either of them are set.
+ var firstHas = { value: false };
+ var anyHas = { value: false };
+ var allHas = { value: false };
+
+ // Show "small"/"big" indicator.
+ var htmlInfo = "";
+ EditorGetTextProperty("small", "", "", firstHas, anyHas, allHas);
+ if (anyHas.value) {
+ htmlInfo = "<small>";
+ }
+ EditorGetTextProperty("big", "", "", firstHas, anyHas, allHas);
+ if (anyHas.value) {
+ htmlInfo += "<big>";
+ }
+
+ if (htmlInfo) {
+ menuPopup.lastChild.hidden = false;
+ menuPopup.lastChild.setAttribute("label", "HTML: " + htmlInfo);
+ menuPopup.lastChild.setAttribute("checked", true);
+ } else {
+ menuPopup.lastChild.hidden = true;
+ }
+ }
+}
+
+function onHighlightColorChange() {
+ ChangeButtonColor("cmd_highlight", "HighlightColorButton", "transparent");
+}
+
+function onFontColorChange() {
+ ChangeButtonColor("cmd_fontColor", "TextColorButton", gDefaultTextColor);
+}
+
+function onBackgroundColorChange() {
+ ChangeButtonColor(
+ "cmd_backgroundColor",
+ "BackgroundColorButton",
+ gDefaultBackgroundColor
+ );
+}
+
+/* Helper function that changes the button color.
+ * commandID - The ID of the command element.
+ * id - The ID of the button needing to be changed.
+ * defaultColor - The default color the button gets set to.
+ */
+function ChangeButtonColor(commandID, id, defaultColor) {
+ var commandNode = document.getElementById(commandID);
+ if (commandNode) {
+ var color = commandNode.getAttribute("state");
+ var button = document.getElementById(id);
+ if (button) {
+ button.setAttribute("color", color);
+
+ // No color or a mixed color - get color set on page or other defaults.
+ if (!color || color == "mixed") {
+ color = defaultColor;
+ }
+
+ button.setAttribute("style", "background-color:" + color + " !important");
+ }
+ }
+}
+
+// Call this when user changes text and/or background colors of the page
+function UpdateDefaultColors() {
+ var BrowserColors = GetDefaultBrowserColors();
+ var bodyelement = GetBodyElement();
+ var defTextColor = gDefaultTextColor;
+ var defBackColor = gDefaultBackgroundColor;
+
+ if (bodyelement) {
+ var color = bodyelement.getAttribute("text");
+ if (color) {
+ gDefaultTextColor = color;
+ } else if (BrowserColors) {
+ gDefaultTextColor = BrowserColors.TextColor;
+ }
+
+ color = bodyelement.getAttribute("bgcolor");
+ if (color) {
+ gDefaultBackgroundColor = color;
+ } else if (BrowserColors) {
+ gDefaultBackgroundColor = BrowserColors.BackgroundColor;
+ }
+ }
+
+ // Trigger update on toolbar
+ if (defTextColor != gDefaultTextColor) {
+ goUpdateCommandState("cmd_fontColor");
+ onFontColorChange();
+ }
+ if (defBackColor != gDefaultBackgroundColor) {
+ goUpdateCommandState("cmd_backgroundColor");
+ onBackgroundColorChange();
+ }
+}
+
+function GetBackgroundElementWithColor() {
+ var editor = GetCurrentTableEditor();
+ if (!editor) {
+ return null;
+ }
+
+ gColorObj.Type = "";
+ gColorObj.PageColor = "";
+ gColorObj.TableColor = "";
+ gColorObj.CellColor = "";
+ gColorObj.BackgroundColor = "";
+ gColorObj.SelectedType = "";
+
+ var tagNameObj = { value: "" };
+ var element;
+ try {
+ element = editor.getSelectedOrParentTableElement(tagNameObj, { value: 0 });
+ } catch (e) {}
+
+ if (element && tagNameObj && tagNameObj.value) {
+ gColorObj.BackgroundColor = GetHTMLOrCSSStyleValue(
+ element,
+ "bgcolor",
+ "background-color"
+ );
+ gColorObj.BackgroundColor = ConvertRGBColorIntoHEXColor(
+ gColorObj.BackgroundColor
+ );
+ if (tagNameObj.value.toLowerCase() == "td") {
+ gColorObj.Type = "Cell";
+ gColorObj.CellColor = gColorObj.BackgroundColor;
+
+ // Get any color that might be on parent table
+ var table = GetParentTable(element);
+ gColorObj.TableColor = GetHTMLOrCSSStyleValue(
+ table,
+ "bgcolor",
+ "background-color"
+ );
+ gColorObj.TableColor = ConvertRGBColorIntoHEXColor(gColorObj.TableColor);
+ } else {
+ gColorObj.Type = "Table";
+ gColorObj.TableColor = gColorObj.BackgroundColor;
+ }
+ gColorObj.SelectedType = gColorObj.Type;
+ } else {
+ let IsCSSPrefChecked = Services.prefs.getBoolPref(kUseCssPref);
+ if (IsCSSPrefChecked && IsHTMLEditor()) {
+ let selection = editor.selection;
+ if (selection) {
+ element = selection.focusNode;
+ while (!editor.nodeIsBlock(element)) {
+ element = element.parentNode;
+ }
+ } else {
+ element = GetBodyElement();
+ }
+ } else {
+ element = GetBodyElement();
+ }
+ if (element) {
+ gColorObj.Type = "Page";
+ gColorObj.BackgroundColor = GetHTMLOrCSSStyleValue(
+ element,
+ "bgcolor",
+ "background-color"
+ );
+ if (gColorObj.BackgroundColor == "") {
+ gColorObj.BackgroundColor = "transparent";
+ } else {
+ gColorObj.BackgroundColor = ConvertRGBColorIntoHEXColor(
+ gColorObj.BackgroundColor
+ );
+ }
+ gColorObj.PageColor = gColorObj.BackgroundColor;
+ }
+ }
+ return element;
+}
+
+function SetSmiley(smileyText) {
+ try {
+ GetCurrentEditor().insertText(smileyText);
+ gContentWindow.focus();
+ } catch (e) {}
+}
+
+/* eslint-disable complexity */
+function EditorSelectColor(colorType, mouseEvent) {
+ var editor = GetCurrentEditor();
+ if (!editor || !gColorObj) {
+ return;
+ }
+
+ // Shift + mouse click automatically applies last color, if available
+ var useLastColor = mouseEvent
+ ? mouseEvent.button == 0 && mouseEvent.shiftKey
+ : false;
+ var element;
+ var table;
+ var currentColor = "";
+ var commandNode;
+
+ if (!colorType) {
+ colorType = "";
+ }
+
+ if (colorType == "Text") {
+ gColorObj.Type = colorType;
+
+ // Get color from command node state
+ commandNode = document.getElementById("cmd_fontColor");
+ currentColor = commandNode.getAttribute("state");
+ currentColor = ConvertRGBColorIntoHEXColor(currentColor);
+ gColorObj.TextColor = currentColor;
+
+ if (useLastColor && gColorObj.LastTextColor) {
+ gColorObj.TextColor = gColorObj.LastTextColor;
+ } else {
+ useLastColor = false;
+ }
+ } else if (colorType == "Highlight") {
+ gColorObj.Type = colorType;
+
+ // Get color from command node state
+ commandNode = document.getElementById("cmd_highlight");
+ currentColor = commandNode.getAttribute("state");
+ currentColor = ConvertRGBColorIntoHEXColor(currentColor);
+ gColorObj.HighlightColor = currentColor;
+
+ if (useLastColor && gColorObj.LastHighlightColor) {
+ gColorObj.HighlightColor = gColorObj.LastHighlightColor;
+ } else {
+ useLastColor = false;
+ }
+ } else {
+ element = GetBackgroundElementWithColor();
+ if (!element) {
+ return;
+ }
+
+ // Get the table if we found a cell
+ if (gColorObj.Type == "Table") {
+ table = element;
+ } else if (gColorObj.Type == "Cell") {
+ table = GetParentTable(element);
+ }
+
+ // Save to avoid resetting if not necessary
+ currentColor = gColorObj.BackgroundColor;
+
+ if (colorType == "TableOrCell" || colorType == "Cell") {
+ if (gColorObj.Type == "Cell") {
+ gColorObj.Type = colorType;
+ } else if (gColorObj.Type != "Table") {
+ return;
+ }
+ } else if (colorType == "Table" && gColorObj.Type == "Page") {
+ return;
+ }
+
+ if (colorType == "" && gColorObj.Type == "Cell") {
+ // Using empty string for requested type means
+ // we can let user select cell or table
+ gColorObj.Type = "TableOrCell";
+ }
+
+ if (useLastColor && gColorObj.LastBackgroundColor) {
+ gColorObj.BackgroundColor = gColorObj.LastBackgroundColor;
+ } else {
+ useLastColor = false;
+ }
+ }
+ // Save the type we are really requesting
+ colorType = gColorObj.Type;
+
+ if (!useLastColor) {
+ // Avoid the JS warning
+ gColorObj.NoDefault = false;
+
+ // Launch the ColorPicker dialog
+ // TODO: Figure out how to position this under the color buttons on the toolbar
+ window.openDialog(
+ "chrome://editor/content/EdColorPicker.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ "",
+ gColorObj
+ );
+
+ // User canceled the dialog
+ if (gColorObj.Cancel) {
+ return;
+ }
+ }
+
+ if (gColorObj.Type == "Text") {
+ if (currentColor != gColorObj.TextColor) {
+ if (gColorObj.TextColor) {
+ EditorSetTextProperty("font", "color", gColorObj.TextColor);
+ } else {
+ EditorRemoveTextProperty("font", "color");
+ }
+ }
+ // Update the command state (this will trigger color button update)
+ goUpdateCommandState("cmd_fontColor");
+ } else if (gColorObj.Type == "Highlight") {
+ if (currentColor != gColorObj.HighlightColor) {
+ if (gColorObj.HighlightColor) {
+ EditorSetTextProperty("font", "bgcolor", gColorObj.HighlightColor);
+ } else {
+ EditorRemoveTextProperty("font", "bgcolor");
+ }
+ }
+ // Update the command state (this will trigger color button update)
+ goUpdateCommandState("cmd_highlight");
+ } else if (element) {
+ if (gColorObj.Type == "Table") {
+ // Set background on a table
+ // Note that we shouldn't trust "currentColor" because of "TableOrCell" behavior
+ if (table) {
+ var bgcolor = table.getAttribute("bgcolor");
+ if (bgcolor != gColorObj.BackgroundColor) {
+ try {
+ if (gColorObj.BackgroundColor) {
+ editor.setAttributeOrEquivalent(
+ table,
+ "bgcolor",
+ gColorObj.BackgroundColor,
+ false
+ );
+ } else {
+ editor.removeAttributeOrEquivalent(table, "bgcolor", false);
+ }
+ } catch (e) {}
+ }
+ }
+ } else if (currentColor != gColorObj.BackgroundColor && IsHTMLEditor()) {
+ editor.beginTransaction();
+ try {
+ editor.setBackgroundColor(gColorObj.BackgroundColor);
+
+ if (gColorObj.Type == "Page" && gColorObj.BackgroundColor) {
+ // Set all page colors not explicitly set,
+ // else you can end up with unreadable pages
+ // because viewer's default colors may not be same as page author's
+ var bodyelement = GetBodyElement();
+ if (bodyelement) {
+ var defColors = GetDefaultBrowserColors();
+ if (defColors) {
+ if (!bodyelement.getAttribute("text")) {
+ editor.setAttributeOrEquivalent(
+ bodyelement,
+ "text",
+ defColors.TextColor,
+ false
+ );
+ }
+
+ // The following attributes have no individual CSS declaration counterparts
+ // Getting rid of them in favor of CSS implies CSS rules management
+ if (!bodyelement.getAttribute("link")) {
+ editor.setAttribute(bodyelement, "link", defColors.LinkColor);
+ }
+
+ if (!bodyelement.getAttribute("alink")) {
+ editor.setAttribute(
+ bodyelement,
+ "alink",
+ defColors.ActiveLinkColor
+ );
+ }
+
+ if (!bodyelement.getAttribute("vlink")) {
+ editor.setAttribute(
+ bodyelement,
+ "vlink",
+ defColors.VisitedLinkColor
+ );
+ }
+ }
+ }
+ }
+ } catch (e) {}
+
+ editor.endTransaction();
+ }
+
+ goUpdateCommandState("cmd_backgroundColor");
+ }
+ gContentWindow.focus();
+}
+/* eslint-enable complexity */
+
+function GetParentTable(element) {
+ var node = element;
+ while (node) {
+ if (node.nodeName.toLowerCase() == "table") {
+ return node;
+ }
+
+ node = node.parentNode;
+ }
+ return node;
+}
+
+function GetParentTableCell(element) {
+ var node = element;
+ while (node) {
+ if (
+ node.nodeName.toLowerCase() == "td" ||
+ node.nodeName.toLowerCase() == "th"
+ ) {
+ return node;
+ }
+
+ node = node.parentNode;
+ }
+ return node;
+}
+
+function EditorDblClick(event) {
+ // We check event.explicitOriginalTarget here because .target will never
+ // be a textnode (bug 193689)
+ if (event.explicitOriginalTarget) {
+ // Only bring up properties if clicked on an element or selected link
+ var element;
+ try {
+ element = event.explicitOriginalTarget;
+ } catch (e) {}
+
+ // We use "href" instead of "a" to not be fooled by named anchor
+ if (!element) {
+ try {
+ element = GetCurrentEditor().getSelectedElement("href");
+ } catch (e) {}
+ }
+
+ // Don't fire for body/p and other block elements.
+ // It's common that people try to double-click
+ // to select a word, but the click hits an empty area.
+ if (
+ element &&
+ ![
+ "body",
+ "p",
+ "h1",
+ "h2",
+ "h3",
+ "h4",
+ "h5",
+ "h6",
+ "blockquote",
+ "div",
+ "pre",
+ ].includes(element.nodeName.toLowerCase())
+ ) {
+ goDoCommand("cmd_objectProperties");
+ event.preventDefault();
+ }
+ }
+}
+
+function EditorClick(event) {
+ // For Web Composer: In Show All Tags Mode,
+ // single click selects entire element,
+ // except for body and table elements
+ if (gEditorDisplayMode == kDisplayModeAllTags) {
+ try {
+ // We check event.explicitOriginalTarget here because .target will never
+ // be a textnode (bug 193689)
+ var element = event.explicitOriginalTarget;
+ var name = element.localName;
+ if (!["body", "caption", "table", "td", "th", "tr"].includes(name)) {
+ GetCurrentEditor().selectElement(event.explicitOriginalTarget);
+ event.preventDefault();
+ }
+ } catch (e) {}
+ }
+}
+
+/* TODO: We need an oncreate hook to do enabling/disabling for the
+ Format menu. There should be code like this for the
+ object-specific "Properties" item
+*/
+// For property dialogs, we want the selected element,
+// but will accept a parent link, list, or table cell if inside one
+function GetObjectForProperties() {
+ var editor = GetCurrentEditor();
+ if (!editor || !IsHTMLEditor()) {
+ return null;
+ }
+
+ var element;
+ try {
+ element = editor.getSelectedElement("");
+ } catch (e) {}
+ if (element) {
+ if (element.namespaceURI == "http://www.w3.org/1998/Math/MathML") {
+ // If the object is a MathML element, we collapse the selection on it and
+ // we return its <math> ancestor. Hence the math dialog will be used.
+ GetCurrentEditor().selection.collapse(element, 0);
+ } else {
+ return element;
+ }
+ }
+
+ // Find nearest parent of selection anchor node
+ // that is a link, list, table cell, or table
+
+ var anchorNode;
+ var node;
+ try {
+ anchorNode = editor.selection.anchorNode;
+ if (anchorNode.firstChild) {
+ // Start at actual selected node
+ var offset = editor.selection.anchorOffset;
+ // Note: If collapsed, offset points to element AFTER caret,
+ // thus node may be null
+ node = anchorNode.childNodes.item(offset);
+ }
+ if (!node) {
+ node = anchorNode;
+ }
+ } catch (e) {}
+
+ while (node) {
+ if (node.nodeName) {
+ var nodeName = node.nodeName.toLowerCase();
+
+ // Done when we hit the body or #text.
+ if (nodeName == "body" || nodeName == "#text") {
+ break;
+ }
+
+ if (
+ (nodeName == "a" && node.href) ||
+ nodeName == "ol" ||
+ nodeName == "ul" ||
+ nodeName == "dl" ||
+ nodeName == "td" ||
+ nodeName == "th" ||
+ nodeName == "table" ||
+ nodeName == "math"
+ ) {
+ return node;
+ }
+ }
+ node = node.parentNode;
+ }
+ return null;
+}
+
+function SetEditMode(mode) {
+ if (!IsHTMLEditor()) {
+ return;
+ }
+
+ var bodyElement = GetBodyElement();
+ if (!bodyElement) {
+ dump("SetEditMode: We don't have a body node!\n");
+ return;
+ }
+
+ // must have editor if here!
+ var editor = GetCurrentEditor();
+ var inlineSpellCheckItem = document.getElementById("menu_inlineSpellCheck");
+
+ // Switch the UI mode before inserting contents
+ // so user can't type in source window while new window is being filled
+ var previousMode = gEditorDisplayMode;
+ if (!SetDisplayMode(mode)) {
+ return;
+ }
+
+ if (mode == kDisplayModeSource) {
+ // Display the DOCTYPE as a non-editable string above edit area
+ var domdoc;
+ try {
+ domdoc = editor.document;
+ } catch (e) {
+ dump(e + "\n");
+ }
+ if (domdoc) {
+ var doctypeNode = document.getElementById("doctype-text");
+ var dt = domdoc.doctype;
+ if (doctypeNode) {
+ if (dt) {
+ doctypeNode.collapsed = false;
+ var doctypeText = "<!DOCTYPE " + domdoc.doctype.name;
+ if (dt.publicId) {
+ doctypeText += ' PUBLIC "' + domdoc.doctype.publicId;
+ }
+ if (dt.systemId) {
+ doctypeText += ' "' + dt.systemId;
+ }
+ doctypeText += '">';
+ doctypeNode.setAttribute("value", doctypeText);
+ } else {
+ doctypeNode.collapsed = true;
+ }
+ }
+ }
+ // Get the entire document's source string
+
+ var flags =
+ editor.documentCharacterSet == "ISO-8859-1"
+ ? kOutputEncodeLatin1Entities
+ : kOutputEncodeBasicEntities;
+ try {
+ let encodeEntity = Services.prefs.getCharPref("editor.encode_entity");
+ switch (encodeEntity) {
+ case "basic":
+ flags = kOutputEncodeBasicEntities;
+ break;
+ case "latin1":
+ flags = kOutputEncodeLatin1Entities;
+ break;
+ case "html":
+ flags = kOutputEncodeHTMLEntities;
+ break;
+ case "none":
+ flags = 0;
+ break;
+ }
+ } catch (e) {}
+
+ if (Services.prefs.getBoolPref("editor.prettyprint")) {
+ flags |= kOutputFormatted;
+ }
+
+ flags |= kOutputLFLineBreak;
+ var source = editor.outputToString(editor.contentsMIMEType, flags);
+ var start = source.search(/<html/i);
+ if (start == -1) {
+ start = 0;
+ }
+ gSourceTextEditor.insertText(source.slice(start));
+ gSourceTextEditor.resetModificationCount();
+ gSourceTextEditor.addDocumentStateListener(gSourceTextListener);
+ gSourceTextEditor.enableUndo(true);
+ gSourceContentWindow.commandManager.addCommandObserver(
+ gSourceTextObserver,
+ "cmd_undo"
+ );
+ gSourceContentWindow.contentWindow.focus();
+ goDoCommand("cmd_moveTop");
+ } else if (previousMode == kDisplayModeSource) {
+ // Only rebuild document if a change was made in source window
+ if (IsHTMLSourceChanged()) {
+ // Disable spell checking when rebuilding source
+ InlineSpellCheckerUI.enabled = false;
+ inlineSpellCheckItem.removeAttribute("checked");
+
+ // Reduce the undo count so we don't use too much memory
+ // during multiple uses of source window
+ // (reinserting entire doc caches all nodes)
+ editor.clearUndoRedo();
+
+ editor.beginTransaction();
+ try {
+ // We are coming from edit source mode,
+ // so transfer that back into the document
+ source = gSourceTextEditor
+ .outputToString(kTextMimeType, kOutputLFLineBreak)
+ .trim();
+ if (editor.contentsMIMEType != kXHTMLMimeType) {
+ editor.rebuildDocumentFromSource(source); // This is undoable
+ } else {
+ /* eslint-disable-next-line no-unsanitized/method */
+ var fragment = editor.document
+ .createRange()
+ .createContextualFragment(source);
+ GetBodyElement().remove();
+ editor.document.replaceChild(
+ fragment.firstChild,
+ editor.document.documentElement
+ );
+ // We touched the DOM tree without a transaction here so that we
+ // broke undoable transactions. However, we cleared all undoable
+ // things above. Therefore nothing must be in the undo stack.
+ }
+
+ // Get the text for the <title> from the newly-parsed document
+ // (must do this for proper conversion of "escaped" characters)
+ let titleNode = editor.document.querySelector("title");
+ SetDocumentTitle(titleNode ? titleNode.textContent : "");
+ } catch (ex) {
+ dump(ex);
+ }
+ // If the MIME type is kXHTMLMimeType, we don't put any undoable
+ // transaction. Then, this endTransaction() call does not allow to
+ // live empty transaction. Therefore, the unnecessary empty transaction
+ // will be cleared here automatically.
+ editor.endTransaction();
+ }
+
+ // Clear out the string buffers
+ gSourceContentWindow.commandManager.removeCommandObserver(
+ gSourceTextObserver,
+ "cmd_undo"
+ );
+ gSourceTextEditor.removeDocumentStateListener(gSourceTextListener);
+ gSourceTextEditor.enableUndo(false);
+ gSourceTextEditor.selectAll();
+ gSourceTextEditor.deleteSelection(
+ gSourceTextEditor.eNone,
+ gSourceTextEditor.eStrip
+ );
+ gSourceTextEditor.resetModificationCount();
+
+ gContentWindow.focus();
+ // goDoCommand("cmd_moveTop");
+ }
+
+ switch (mode) {
+ case kDisplayModePreview:
+ // Disable spell checking when previewing
+ InlineSpellCheckerUI.enabled = false;
+ inlineSpellCheckItem.removeAttribute("checked");
+ inlineSpellCheckItem.setAttribute("disabled", true);
+ break;
+ case kDisplayModeSource:
+ inlineSpellCheckItem.setAttribute("disabled", true);
+ goSetCommandEnabled("cmd_pasteQuote", false);
+ break;
+ default:
+ inlineSpellCheckItem.setAttribute(
+ "disabled",
+ !InlineSpellCheckerUI.canSpellCheck
+ );
+ break;
+ }
+}
+
+function CancelHTMLSource() {
+ // Don't convert source text back into the DOM document
+ gSourceTextEditor.resetModificationCount();
+ SetDisplayMode(gPreviousNonSourceDisplayMode);
+}
+
+function SetDisplayMode(mode) {
+ if (!IsHTMLEditor()) {
+ return false;
+ }
+
+ // Already in requested mode:
+ // return false to indicate we didn't switch
+ if (mode == gEditorDisplayMode) {
+ return false;
+ }
+
+ var previousMode = gEditorDisplayMode;
+ gEditorDisplayMode = mode;
+
+ ResetStructToolbar();
+ if (mode == kDisplayModeSource) {
+ // Switch to the sourceWindow (second in the deck)
+ gContentWindowDeck.selectedIndex = 1;
+
+ // Hide the formatting toolbar if not already hidden
+ gFormatToolbarHidden = gFormatToolbar.hidden;
+ gFormatToolbar.hidden = true;
+ gFormatToolbar.setAttribute("hideinmenu", "true");
+
+ gSourceContentWindow.contentWindow.focus();
+ } else {
+ // Save the last non-source mode so we can cancel source editing easily
+ gPreviousNonSourceDisplayMode = mode;
+
+ // Load/unload appropriate override style sheet
+ try {
+ var editor = GetCurrentEditor();
+ editor.QueryInterface(nsIEditorStyleSheets);
+ editor instanceof Ci.nsIHTMLObjectResizer;
+
+ switch (mode) {
+ case kDisplayModePreview:
+ // Disable all extra "edit mode" style sheets
+ editor.enableStyleSheet(kNormalStyleSheet, false);
+ editor.enableStyleSheet(kAllTagsStyleSheet, false);
+ editor.objectResizingEnabled = true;
+ break;
+
+ case kDisplayModeNormal:
+ editor.addOverrideStyleSheet(kNormalStyleSheet);
+ // Disable ShowAllTags mode
+ editor.enableStyleSheet(kAllTagsStyleSheet, false);
+ editor.objectResizingEnabled = true;
+ break;
+
+ case kDisplayModeAllTags:
+ editor.addOverrideStyleSheet(kNormalStyleSheet);
+ editor.addOverrideStyleSheet(kAllTagsStyleSheet);
+ // don't allow resizing in AllTags mode because the visible tags
+ // change the computed size of images and tables...
+ if (editor.resizedObject) {
+ editor.hideResizers();
+ }
+ editor.objectResizingEnabled = false;
+ break;
+ }
+ } catch (e) {}
+
+ // Switch to the normal editor (first in the deck)
+ gContentWindowDeck.selectedIndex = 0;
+
+ // Restore menus and toolbars
+ if (previousMode == kDisplayModeSource) {
+ gFormatToolbar.hidden = gFormatToolbarHidden;
+ gFormatToolbar.removeAttribute("hideinmenu");
+ }
+
+ gContentWindow.focus();
+ }
+
+ // update commands to disable or re-enable stuff
+ window.updateCommands("mode_switch");
+
+ // Set the selected tab at bottom of window:
+ // (Note: Setting "selectedIndex = mode" won't redraw tabs when menu is used.)
+ document.getElementById(
+ "EditModeTabs"
+ ).selectedItem = document.getElementById(kDisplayModeTabIDS[mode]);
+
+ // Uncheck previous menuitem and set new check since toolbar may have been used
+ if (previousMode >= 0) {
+ document
+ .getElementById(kDisplayModeMenuIDs[previousMode])
+ .setAttribute("checked", "false");
+ }
+ document
+ .getElementById(kDisplayModeMenuIDs[mode])
+ .setAttribute("checked", "true");
+
+ return true;
+}
+
+function UpdateWindowTitle() {
+ try {
+ var filename = "";
+ var windowTitle = "";
+ var title = GetDocumentTitle();
+
+ // Append just the 'leaf' filename to the Doc. Title for the window caption
+ var docUrl = GetDocumentUrl();
+ if (docUrl && !IsUrlAboutBlank(docUrl)) {
+ var scheme = GetScheme(docUrl);
+ filename = GetFilename(docUrl);
+ if (filename) {
+ windowTitle = " [" + scheme + ":/.../" + filename + "]";
+ }
+
+ var fileType = IsHTMLEditor() ? "html" : "text";
+ // Save changed title in the recent pages data in prefs
+ SaveRecentFilesPrefs(title, fileType);
+ }
+
+ // Set window title with " - Composer" or " - Text Editor" appended.
+ var xulWin = document.documentElement;
+
+ document.title =
+ (title || filename || window.gUntitledString) +
+ windowTitle +
+ xulWin.getAttribute("titlemenuseparator") +
+ xulWin.getAttribute("titlemodifier");
+ } catch (e) {
+ dump(e);
+ }
+}
+
+function SaveRecentFilesPrefs(aTitle, aFileType) {
+ var curUrl = StripPassword(GetDocumentUrl());
+ var historyCount = Services.prefs.getIntPref("editor.history.url_maximum");
+
+ var titleArray = [];
+ var urlArray = [];
+ var typeArray = [];
+
+ if (historyCount && !IsUrlAboutBlank(curUrl) && GetScheme(curUrl) != "data") {
+ titleArray.push(aTitle);
+ urlArray.push(curUrl);
+ typeArray.push(aFileType);
+ }
+
+ for (let i = 0; i < historyCount && urlArray.length < historyCount; i++) {
+ let url = Services.prefs.getStringPref("editor.history_url_" + i, "");
+
+ // Continue if URL pref is missing because
+ // a URL not found during loading may have been removed
+
+ // Skip over current an "data" URLs
+ if (url && url != curUrl && GetScheme(url) != "data") {
+ let title = Services.prefs.getStringPref("editor.history_title_" + i, "");
+ let fileType = Services.prefs.getStringPref(
+ "editor.history_type_" + i,
+ ""
+ );
+ titleArray.push(title);
+ urlArray.push(url);
+ typeArray.push(fileType);
+ }
+ }
+
+ // Resave the list back to prefs in the new order
+ for (let i = 0; i < urlArray.length; i++) {
+ SetStringPref("editor.history_title_" + i, titleArray[i]);
+ SetStringPref("editor.history_url_" + i, urlArray[i]);
+ SetStringPref("editor.history_type_" + i, typeArray[i]);
+ }
+}
+
+function EditorInitFormatMenu() {
+ try {
+ InitObjectPropertiesMenuitem();
+ InitRemoveStylesMenuitems(
+ "removeStylesMenuitem",
+ "removeLinksMenuitem",
+ "removeNamedAnchorsMenuitem"
+ );
+ } catch (ex) {}
+}
+
+function InitObjectPropertiesMenuitem() {
+ // Set strings and enable for the [Object] Properties item
+ // Note that we directly do the enabling instead of
+ // using goSetCommandEnabled since we already have the command.
+ var cmd = document.getElementById("cmd_objectProperties");
+ if (!cmd) {
+ return null;
+ }
+
+ var element;
+ var menuStr = GetString("AdvancedProperties");
+ var name;
+
+ if (IsEditingRenderedHTML()) {
+ element = GetObjectForProperties();
+ }
+
+ if (element && element.nodeName) {
+ var objStr = "";
+ cmd.removeAttribute("disabled");
+ name = element.nodeName.toLowerCase();
+ switch (name) {
+ case "img":
+ // Check if img is enclosed in link
+ // (use "href" to not be fooled by named anchor)
+ try {
+ if (GetCurrentEditor().getElementOrParentByTagName("href", element)) {
+ objStr = GetString("ImageAndLink");
+ // Return "href" so it is detected as a link.
+ name = "href";
+ }
+ } catch (e) {}
+
+ if (objStr == "") {
+ objStr = GetString("Image");
+ }
+ break;
+ case "hr":
+ objStr = GetString("HLine");
+ break;
+ case "table":
+ objStr = GetString("Table");
+ break;
+ case "th":
+ name = "td";
+ // Falls through
+ case "td":
+ objStr = GetString("TableCell");
+ break;
+ case "ol":
+ case "ul":
+ case "dl":
+ objStr = GetString("List");
+ break;
+ case "li":
+ objStr = GetString("ListItem");
+ break;
+ case "form":
+ objStr = GetString("Form");
+ break;
+ case "input":
+ var type = element.getAttribute("type");
+ if (type && type.toLowerCase() == "image") {
+ objStr = GetString("InputImage");
+ } else {
+ objStr = GetString("InputTag");
+ }
+ break;
+ case "textarea":
+ objStr = GetString("TextArea");
+ break;
+ case "select":
+ objStr = GetString("Select");
+ break;
+ case "button":
+ objStr = GetString("Button");
+ break;
+ case "label":
+ objStr = GetString("Label");
+ break;
+ case "fieldset":
+ objStr = GetString("FieldSet");
+ break;
+ case "a":
+ if (element.name) {
+ objStr = GetString("NamedAnchor");
+ name = "anchor";
+ } else if (element.href) {
+ objStr = GetString("Link");
+ name = "href";
+ }
+ break;
+ }
+ if (objStr) {
+ menuStr = GetString("ObjectProperties").replace(/%obj%/, objStr);
+ }
+ } else {
+ // We show generic "Properties" string, but disable the command.
+ cmd.setAttribute("disabled", "true");
+ }
+ cmd.setAttribute("label", menuStr);
+ cmd.setAttribute("accesskey", GetString("ObjectPropertiesAccessKey"));
+ return name;
+}
+
+function InitParagraphMenu() {
+ var mixedObj = { value: null };
+ var state;
+ try {
+ state = GetCurrentEditor().getParagraphState(mixedObj);
+ } catch (e) {}
+ var IDSuffix;
+
+ // PROBLEM: When we get blockquote, it masks other styles contained by it
+ // We need a separate method to get blockquote state
+
+ // We use "x" as uninitialized paragraph state
+ if (!state || state == "x") {
+ // No paragraph container.
+ IDSuffix = "bodyText";
+ } else {
+ IDSuffix = state;
+ }
+
+ // Set "radio" check on one item, but...
+ var menuItem = document.getElementById("menu_" + IDSuffix);
+ menuItem.setAttribute("checked", "true");
+
+ // ..."bodyText" is returned if mixed selection, so remove checkmark
+ if (mixedObj.value) {
+ menuItem.setAttribute("checked", "false");
+ }
+}
+
+function GetListStateString() {
+ try {
+ var editor = GetCurrentEditor();
+
+ var mixedObj = { value: null };
+ var hasOL = { value: false };
+ var hasUL = { value: false };
+ var hasDL = { value: false };
+ editor.getListState(mixedObj, hasOL, hasUL, hasDL);
+
+ if (mixedObj.value) {
+ return "mixed";
+ }
+ if (hasOL.value) {
+ return "ol";
+ }
+ if (hasUL.value) {
+ return "ul";
+ }
+
+ if (hasDL.value) {
+ var hasLI = { value: false };
+ var hasDT = { value: false };
+ var hasDD = { value: false };
+ editor.getListItemState(mixedObj, hasLI, hasDT, hasDD);
+ if (mixedObj.value) {
+ return "mixed";
+ }
+ if (hasLI.value) {
+ return "li";
+ }
+ if (hasDT.value) {
+ return "dt";
+ }
+ if (hasDD.value) {
+ return "dd";
+ }
+ }
+ } catch (e) {}
+
+ // return "noList" if we aren't in a list at all
+ return "noList";
+}
+
+function InitListMenu() {
+ if (!IsHTMLEditor()) {
+ return;
+ }
+
+ var IDSuffix = GetListStateString();
+
+ // Set enable state for the "None" menuitem
+ goSetCommandEnabled("cmd_removeList", IDSuffix != "noList");
+
+ // Set "radio" check on one item, but...
+ // we won't find a match if it's "mixed"
+ var menuItem = document.getElementById("menu_" + IDSuffix);
+ if (menuItem) {
+ menuItem.setAttribute("checked", "true");
+ }
+}
+
+function GetAlignmentString() {
+ var mixedObj = { value: null };
+ var alignObj = { value: null };
+ try {
+ GetCurrentEditor().getAlignment(mixedObj, alignObj);
+ } catch (e) {}
+
+ if (mixedObj.value) {
+ return "mixed";
+ }
+ if (alignObj.value == nsIHTMLEditor.eLeft) {
+ return "left";
+ }
+ if (alignObj.value == nsIHTMLEditor.eCenter) {
+ return "center";
+ }
+ if (alignObj.value == nsIHTMLEditor.eRight) {
+ return "right";
+ }
+ if (alignObj.value == nsIHTMLEditor.eJustify) {
+ return "justify";
+ }
+
+ // return "left" if we got here
+ return "left";
+}
+
+function InitAlignMenu() {
+ if (!IsHTMLEditor()) {
+ return;
+ }
+
+ var IDSuffix = GetAlignmentString();
+
+ // we won't find a match if it's "mixed"
+ var menuItem = document.getElementById("menu_" + IDSuffix);
+ if (menuItem) {
+ menuItem.setAttribute("checked", "true");
+ }
+}
+
+function EditorSetDefaultPrefsAndDoctype() {
+ var editor = GetCurrentEditor();
+
+ var domdoc;
+ try {
+ domdoc = editor.document;
+ } catch (e) {
+ dump(e + "\n");
+ }
+ if (!domdoc) {
+ dump("EditorSetDefaultPrefsAndDoctype: EDITOR DOCUMENT NOT FOUND\n");
+ return;
+ }
+
+ // Insert a doctype element
+ // if it is missing from existing doc
+ if (!domdoc.doctype) {
+ var newdoctype = domdoc.implementation.createDocumentType(
+ "HTML",
+ "-//W3C//DTD HTML 4.01 Transitional//EN",
+ ""
+ );
+ if (newdoctype) {
+ domdoc.insertBefore(newdoctype, domdoc.firstChild);
+ }
+ }
+
+ // search for head; we'll need this for meta tag additions
+ let headelement = domdoc.querySelector("head");
+ if (!headelement) {
+ headelement = domdoc.createElement("head");
+ if (headelement) {
+ domdoc.insertAfter(headelement, domdoc.firstChild);
+ }
+ }
+
+ /* only set default prefs for new documents */
+ if (!IsUrlAboutBlank(GetDocumentUrl())) {
+ return;
+ }
+
+ // search for author meta tag.
+ // if one is found, don't do anything.
+ // if not, create one and make it a child of the head tag
+ // and set its content attribute to the value of the editor.author preference.
+
+ if (domdoc.querySelector("meta")) {
+ // we should do charset first since we need to have charset before
+ // hitting other 8-bit char in other meta tags
+ // grab charset pref and make it the default charset
+ var element;
+ var prefCharsetString = Services.prefs.getCharPref(
+ "intl.charset.fallback.override"
+ );
+ if (prefCharsetString) {
+ editor.documentCharacterSet = prefCharsetString;
+ }
+
+ // let's start by assuming we have an author in case we don't have the pref
+
+ var prefAuthorString = null;
+ let authorFound = domdoc.querySelector('meta[name="author"]');
+ try {
+ prefAuthorString = Services.prefs.getStringPref("editor.author");
+ } catch (ex) {}
+ if (
+ prefAuthorString &&
+ prefAuthorString != 0 &&
+ !authorFound &&
+ headelement
+ ) {
+ // create meta tag with 2 attributes
+ element = domdoc.createElement("meta");
+ if (element) {
+ element.setAttribute("name", "author");
+ element.setAttribute("content", prefAuthorString);
+ headelement.appendChild(element);
+ }
+ }
+ }
+
+ // add title tag if not present
+ if (headelement && !editor.document.querySelector("title")) {
+ var titleElement = domdoc.createElement("title");
+ if (titleElement) {
+ headelement.appendChild(titleElement);
+ }
+ }
+
+ // find body node
+ var bodyelement = GetBodyElement();
+ if (bodyelement) {
+ if (Services.prefs.getBoolPref("editor.use_custom_colors")) {
+ let text_color = Services.prefs.getCharPref("editor.text_color");
+ let background_color = Services.prefs.getCharPref(
+ "editor.background_color"
+ );
+
+ // add the color attributes to the body tag.
+ // and use them for the default text and background colors if not empty
+ editor.setAttributeOrEquivalent(bodyelement, "text", text_color, true);
+ gDefaultTextColor = text_color;
+ editor.setAttributeOrEquivalent(
+ bodyelement,
+ "bgcolor",
+ background_color,
+ true
+ );
+ gDefaultBackgroundColor = background_color;
+ bodyelement.setAttribute(
+ "link",
+ Services.prefs.getCharPref("editor.link_color")
+ );
+ bodyelement.setAttribute(
+ "alink",
+ Services.prefs.getCharPref("editor.active_link_color")
+ );
+ bodyelement.setAttribute(
+ "vlink",
+ Services.prefs.getCharPref("editor.followed_link_color")
+ );
+ }
+ // Default image is independent of Custom colors???
+ try {
+ let background_image = Services.prefs.getCharPref(
+ "editor.default_background_image"
+ );
+ if (background_image) {
+ editor.setAttributeOrEquivalent(
+ bodyelement,
+ "background",
+ background_image,
+ true
+ );
+ }
+ } catch (e) {
+ dump("BACKGROUND EXCEPTION: " + e + "\n");
+ }
+ }
+ // auto-save???
+}
+
+function GetBodyElement() {
+ try {
+ return GetCurrentEditor().rootElement;
+ } catch (ex) {
+ dump("no body tag found?!\n");
+ // better have one, how can we blow things up here?
+ }
+ return null;
+}
+
+// --------------------------- Logging stuff ---------------------------
+
+function EditorGetNodeFromOffsets(offsets) {
+ var node = null;
+ try {
+ node = GetCurrentEditor().document;
+
+ for (var i = 0; i < offsets.length; i++) {
+ node = node.childNodes[offsets[i]];
+ }
+ } catch (e) {}
+ return node;
+}
+
+function EditorSetSelectionFromOffsets(selRanges) {
+ try {
+ var editor = GetCurrentEditor();
+ var selection = editor.selection;
+ selection.removeAllRanges();
+
+ var rangeArr, start, end, node, offset;
+ for (var i = 0; i < selRanges.length; i++) {
+ rangeArr = selRanges[i];
+ start = rangeArr[0];
+ end = rangeArr[1];
+
+ var range = editor.document.createRange();
+
+ node = EditorGetNodeFromOffsets(start[0]);
+ offset = start[1];
+
+ range.setStart(node, offset);
+
+ node = EditorGetNodeFromOffsets(end[0]);
+ offset = end[1];
+
+ range.setEnd(node, offset);
+
+ selection.addRange(range);
+ }
+ } catch (e) {}
+}
+
+// --------------------------------------------------------------------
+function initFontStyleMenu(menuPopup) {
+ for (var i = 0; i < menuPopup.childNodes.length; i++) {
+ var menuItem = menuPopup.childNodes[i];
+ var theStyle = menuItem.getAttribute("state");
+ if (theStyle) {
+ menuItem.setAttribute("checked", theStyle);
+ }
+ }
+}
+
+// --------------------------------------------------------------------
+function onButtonUpdate(button, commmandID) {
+ var commandNode = document.getElementById(commmandID);
+ var state = commandNode.getAttribute("state");
+ button.checked = state == "true";
+}
+
+// --------------------------------------------------------------------
+function onStateButtonUpdate(button, commmandID, onState) {
+ var commandNode = document.getElementById(commmandID);
+ var state = commandNode.getAttribute("state");
+
+ button.checked = state == onState;
+}
+
+// --------------------------- Status calls ---------------------------
+function getColorAndSetColorWell(ColorPickerID, ColorWellID) {
+ var colorWell;
+ if (ColorWellID) {
+ colorWell = document.getElementById(ColorWellID);
+ }
+
+ var colorPicker = document.getElementById(ColorPickerID);
+ if (colorPicker) {
+ // Extract color from colorPicker and assign to colorWell.
+ var color = colorPicker.getAttribute("color");
+
+ if (colorWell && color) {
+ // Use setAttribute so colorwell can be a XUL element, such as button
+ colorWell.setAttribute("style", "background-color: " + color);
+ }
+ }
+ return color;
+}
+
+// -----------------------------------------------------------------------------------
+function IsSpellCheckerInstalled() {
+ return true; // Always installed.
+}
+
+// -----------------------------------------------------------------------------------
+function IsFindInstalled() {
+ return (
+ "@mozilla.org/embedcomp/rangefind;1" in Cc &&
+ "@mozilla.org/find/find_service;1" in Cc
+ );
+}
+
+// -----------------------------------------------------------------------------------
+function RemoveInapplicableUIElements() {
+ // For items that are in their own menu block, remove associated separator
+ // (we can't use "hidden" since class="hide-in-IM" CSS rule interferes)
+
+ // if no find, remove find ui
+ if (!IsFindInstalled()) {
+ HideItem("menu_find");
+ HideItem("menu_findnext");
+ HideItem("menu_replace");
+ HideItem("menu_find");
+ RemoveItem("sep_find");
+ }
+
+ // if no spell checker, remove spell checker ui
+ if (!IsSpellCheckerInstalled()) {
+ HideItem("spellingButton");
+ HideItem("menu_checkspelling");
+ RemoveItem("sep_checkspelling");
+ }
+
+ // Remove menu items (from overlay shared with HTML editor) in non-HTML.
+ if (!IsHTMLEditor()) {
+ HideItem("insertAnchor");
+ HideItem("insertImage");
+ HideItem("insertHline");
+ HideItem("insertTable");
+ HideItem("insertHTML");
+ HideItem("insertFormMenu");
+ HideItem("fileExportToText");
+ HideItem("viewEditModeToolbar");
+ }
+}
+
+function HideItem(id) {
+ var item = document.getElementById(id);
+ if (item) {
+ item.hidden = true;
+ }
+}
+
+function RemoveItem(id) {
+ var item = document.getElementById(id);
+ if (item) {
+ item.remove();
+ }
+}
+
+// Command Updating Strategy:
+// Don't update on on selection change, only when menu is displayed,
+// with this "oncreate" handler:
+function EditorInitTableMenu() {
+ try {
+ InitJoinCellMenuitem("menu_JoinTableCells");
+ } catch (ex) {}
+
+ // Set enable states for all table commands
+ goUpdateTableMenuItems(document.getElementById("composerTableMenuItems"));
+}
+
+function InitJoinCellMenuitem(id) {
+ // Change text on the "Join..." item depending if we
+ // are joining selected cells or just cell to right
+ // TODO: What to do about normal selection that crosses
+ // table border? Try to figure out all cells
+ // included in the selection?
+ var menuText;
+ var menuItem = document.getElementById(id);
+ if (!menuItem) {
+ return;
+ }
+
+ // Use "Join selected cells if there's more than 1 cell selected
+ var numSelected;
+ var foundElement;
+
+ try {
+ var tagNameObj = {};
+ var countObj = { value: 0 };
+ foundElement = GetCurrentTableEditor().getSelectedOrParentTableElement(
+ tagNameObj,
+ countObj
+ );
+ numSelected = countObj.value;
+ } catch (e) {}
+ if (foundElement && numSelected > 1) {
+ menuText = GetString("JoinSelectedCells");
+ } else {
+ menuText = GetString("JoinCellToRight");
+ }
+
+ menuItem.setAttribute("label", menuText);
+ menuItem.setAttribute("accesskey", GetString("JoinCellAccesskey"));
+}
+
+function InitRemoveStylesMenuitems(
+ removeStylesId,
+ removeLinksId,
+ removeNamedAnchorsId
+) {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ return;
+ }
+
+ // Change wording of menuitems depending on selection
+ var stylesItem = document.getElementById(removeStylesId);
+ var linkItem = document.getElementById(removeLinksId);
+
+ var isCollapsed = editor.selection.isCollapsed;
+ if (stylesItem) {
+ stylesItem.setAttribute(
+ "label",
+ isCollapsed ? GetString("StopTextStyles") : GetString("RemoveTextStyles")
+ );
+ stylesItem.setAttribute(
+ "accesskey",
+ GetString("RemoveTextStylesAccesskey")
+ );
+ }
+ if (linkItem) {
+ linkItem.setAttribute(
+ "label",
+ isCollapsed ? GetString("StopLinks") : GetString("RemoveLinks")
+ );
+ linkItem.setAttribute("accesskey", GetString("RemoveLinksAccesskey"));
+ // Note: disabling text style is a pain since there are so many - forget it!
+
+ // Disable if not in a link, but always allow "Remove"
+ // if selection isn't collapsed since we only look at anchor node
+ try {
+ SetElementEnabled(
+ linkItem,
+ !isCollapsed || editor.getElementOrParentByTagName("href", null)
+ );
+ } catch (e) {}
+ }
+ // Disable if selection is collapsed
+ SetElementEnabledById(removeNamedAnchorsId, !isCollapsed);
+}
+
+function goUpdateTableMenuItems(commandset) {
+ var editor = GetCurrentTableEditor();
+ if (!editor) {
+ dump("goUpdateTableMenuItems: too early, not initialized\n");
+ return;
+ }
+
+ var enabled = false;
+ var enabledIfTable = false;
+
+ var flags = editor.flags;
+ if (!(flags & Ci.nsIEditor.eEditorReadonlyMask) && IsEditingRenderedHTML()) {
+ var tagNameObj = { value: "" };
+ var element;
+ try {
+ element = editor.getSelectedOrParentTableElement(tagNameObj, {
+ value: 0,
+ });
+ } catch (e) {}
+
+ if (element) {
+ // Value when we need to have a selected table or inside a table
+ enabledIfTable = true;
+
+ // All others require being inside a cell or selected cell
+ enabled = tagNameObj.value == "td";
+ }
+ }
+
+ // Loop through command nodes
+ for (var i = 0; i < commandset.childNodes.length; i++) {
+ var commandID = commandset.childNodes[i].getAttribute("id");
+ if (commandID) {
+ if (
+ commandID == "cmd_InsertTable" ||
+ commandID == "cmd_JoinTableCells" ||
+ commandID == "cmd_SplitTableCell" ||
+ commandID == "cmd_ConvertToTable"
+ ) {
+ // Call the update method in the command class
+ goUpdateCommand(commandID);
+ } else if (
+ commandID == "cmd_DeleteTable" ||
+ commandID == "cmd_NormalizeTable" ||
+ commandID == "cmd_editTable" ||
+ commandID == "cmd_TableOrCellColor" ||
+ commandID == "cmd_SelectTable"
+ ) {
+ // Directly set with the values calculated here
+ goSetCommandEnabled(commandID, enabledIfTable);
+ } else {
+ goSetCommandEnabled(commandID, enabled);
+ }
+ }
+ }
+}
+
+// -----------------------------------------------------------------------------------
+// Helpers for inserting and editing tables:
+
+function IsInTable() {
+ var editor = GetCurrentEditor();
+ try {
+ var flags = editor.flags;
+ return (
+ IsHTMLEditor() &&
+ !(flags & Ci.nsIEditor.eEditorReadonlyMask) &&
+ IsEditingRenderedHTML() &&
+ null != editor.getElementOrParentByTagName("table", null)
+ );
+ } catch (e) {}
+ return false;
+}
+
+function IsInTableCell() {
+ try {
+ var editor = GetCurrentEditor();
+ var flags = editor.flags;
+ return (
+ IsHTMLEditor() &&
+ !(flags & Ci.nsIEditor.eEditorReadonlyMask) &&
+ IsEditingRenderedHTML() &&
+ null != editor.getElementOrParentByTagName("td", null)
+ );
+ } catch (e) {}
+ return false;
+}
+
+function IsSelectionInOneCell() {
+ try {
+ var editor = GetCurrentEditor();
+ var selection = editor.selection;
+
+ if (selection.rangeCount == 1) {
+ // We have a "normal" single-range selection
+ if (
+ !selection.isCollapsed &&
+ selection.anchorNode != selection.focusNode
+ ) {
+ // Check if both nodes are within the same cell
+ var anchorCell = editor.getElementOrParentByTagName(
+ "td",
+ selection.anchorNode
+ );
+ var focusCell = editor.getElementOrParentByTagName(
+ "td",
+ selection.focusNode
+ );
+ return (
+ focusCell != null && anchorCell != null && focusCell == anchorCell
+ );
+ }
+ // Collapsed selection or anchor == focus (thus must be in 1 cell)
+ return true;
+ }
+ } catch (e) {}
+ return false;
+}
+
+// Call this with insertAllowed = true to allow inserting if not in existing table,
+// else use false to do nothing if not in a table
+function EditorInsertOrEditTable(insertAllowed) {
+ if (IsInTable()) {
+ // Edit properties of existing table
+ window.openDialog(
+ "chrome://editor/content/EdTableProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ "",
+ "TablePanel"
+ );
+ gContentWindow.focus();
+ } else if (insertAllowed) {
+ try {
+ if (GetCurrentEditor().selection.isCollapsed) {
+ // If we have a caret, insert a blank table...
+ EditorInsertTable();
+ } else {
+ // Else convert the selection into a table.
+ goDoCommand("cmd_ConvertToTable");
+ }
+ } catch (e) {}
+ }
+}
+
+function EditorInsertTable() {
+ // Insert a new table
+ window.openDialog(
+ "chrome://editor/content/EdInsertTable.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ ""
+ );
+ gContentWindow.focus();
+}
+
+function EditorTableCellProperties() {
+ if (!IsHTMLEditor()) {
+ return;
+ }
+
+ try {
+ var cell = GetCurrentEditor().getElementOrParentByTagName("td", null);
+ if (cell) {
+ // Start Table Properties dialog on the "Cell" panel
+ window.openDialog(
+ "chrome://editor/content/EdTableProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ "",
+ "CellPanel"
+ );
+ gContentWindow.focus();
+ }
+ } catch (e) {}
+}
+
+function GetNumberOfContiguousSelectedRows() {
+ if (!IsHTMLEditor()) {
+ return 0;
+ }
+
+ var rows = 0;
+ try {
+ var editor = GetCurrentTableEditor();
+ var rowObj = { value: 0 };
+ var colObj = { value: 0 };
+ var cell = editor.getFirstSelectedCellInTable(rowObj, colObj);
+ if (!cell) {
+ return 0;
+ }
+
+ // We have at least one row
+ rows++;
+
+ var lastIndex = rowObj.value;
+ do {
+ cell = editor.getNextSelectedCell({ value: 0 });
+ if (cell) {
+ editor.getCellIndexes(cell, rowObj, colObj);
+ var index = rowObj.value;
+ if (index == lastIndex + 1) {
+ lastIndex = index;
+ rows++;
+ }
+ }
+ } while (cell);
+ } catch (e) {}
+
+ return rows;
+}
+
+function GetNumberOfContiguousSelectedColumns() {
+ if (!IsHTMLEditor()) {
+ return 0;
+ }
+
+ var columns = 0;
+ try {
+ var editor = GetCurrentTableEditor();
+ var colObj = { value: 0 };
+ var rowObj = { value: 0 };
+ var cell = editor.getFirstSelectedCellInTable(rowObj, colObj);
+ if (!cell) {
+ return 0;
+ }
+
+ // We have at least one column
+ columns++;
+
+ var lastIndex = colObj.value;
+ do {
+ cell = editor.getNextSelectedCell({ value: 0 });
+ if (cell) {
+ editor.getCellIndexes(cell, rowObj, colObj);
+ var index = colObj.value;
+ if (index == lastIndex + 1) {
+ lastIndex = index;
+ columns++;
+ }
+ }
+ } while (cell);
+ } catch (e) {}
+
+ return columns;
+}
+
+function EditorOnFocus() {
+ // Current window already has the InsertCharWindow
+ if ("InsertCharWindow" in window && window.InsertCharWindow) {
+ return;
+ }
+
+ // Find window with an InsertCharsWindow and switch association to this one
+ var windowWithDialog = FindEditorWithInsertCharDialog();
+ if (windowWithDialog) {
+ // Switch the dialog to current window
+ // this sets focus to dialog, so bring focus back to editor window
+ if (SwitchInsertCharToThisWindow(windowWithDialog)) {
+ top.document.commandDispatcher.focusedWindow.focus();
+ }
+ }
+}
+
+function SwitchInsertCharToThisWindow(windowWithDialog) {
+ if (
+ windowWithDialog &&
+ "InsertCharWindow" in windowWithDialog &&
+ windowWithDialog.InsertCharWindow
+ ) {
+ // Move dialog association to the current window
+ window.InsertCharWindow = windowWithDialog.InsertCharWindow;
+ windowWithDialog.InsertCharWindow = null;
+
+ // Switch the dialog's opener to current window's
+ window.InsertCharWindow.opener = window;
+
+ // Bring dialog to the foreground
+ window.InsertCharWindow.focus();
+ return true;
+ }
+ return false;
+}
+
+function FindEditorWithInsertCharDialog() {
+ try {
+ // Find window with an InsertCharsWindow and switch association to this one
+ let enumerator = Services.wm.getEnumerator(null);
+
+ while (enumerator.hasMoreElements()) {
+ var tempWindow = enumerator.getNext();
+
+ if (
+ !tempWindow.closed &&
+ tempWindow != window &&
+ "InsertCharWindow" in tempWindow &&
+ tempWindow.InsertCharWindow
+ ) {
+ return tempWindow;
+ }
+ }
+ } catch (e) {}
+ return null;
+}
+
+function EditorFindOrCreateInsertCharWindow() {
+ if ("InsertCharWindow" in window && window.InsertCharWindow) {
+ window.InsertCharWindow.focus();
+ } else {
+ // Since we switch the dialog during EditorOnFocus(),
+ // this should really never be found, but it's good to be sure
+ var windowWithDialog = FindEditorWithInsertCharDialog();
+ if (windowWithDialog) {
+ SwitchInsertCharToThisWindow(windowWithDialog);
+ } else {
+ // The dialog will set window.InsertCharWindow to itself
+ window.openDialog(
+ "chrome://editor/content/EdInsertChars.xhtml",
+ "_blank",
+ "chrome,close,titlebar",
+ ""
+ );
+ }
+ }
+}
+
+// Find another HTML editor window to associate with the InsertChar dialog
+// or close it if none found (May be a mail composer)
+function SwitchInsertCharToAnotherEditorOrClose() {
+ if ("InsertCharWindow" in window && window.InsertCharWindow) {
+ var enumerator;
+ try {
+ enumerator = Services.wm.getEnumerator(null);
+ } catch (e) {}
+ if (!enumerator) {
+ return;
+ }
+
+ // TODO: Fix this to search for command controllers and look for "cmd_InsertChars"
+ // For now, detect just Web Composer and HTML Mail Composer
+ while (enumerator.hasMoreElements()) {
+ var tempWindow = enumerator.getNext();
+ if (
+ !tempWindow.closed &&
+ tempWindow != window &&
+ tempWindow != window.InsertCharWindow &&
+ "GetCurrentEditor" in tempWindow &&
+ tempWindow.GetCurrentEditor()
+ ) {
+ tempWindow.InsertCharWindow = window.InsertCharWindow;
+ window.InsertCharWindow = null;
+ tempWindow.InsertCharWindow.opener = tempWindow;
+ return;
+ }
+ }
+ // Didn't find another editor - close the dialog
+ window.InsertCharWindow.close();
+ }
+}
+
+function ResetStructToolbar() {
+ gLastFocusNode = null;
+ UpdateStructToolbar();
+}
+
+function newCommandListener(element) {
+ return function() {
+ return SelectFocusNodeAncestor(element);
+ };
+}
+
+function newContextmenuListener(button, element) {
+ /* globals InitStructBarContextMenu */ // SeaMonkey only.
+ return function() {
+ return InitStructBarContextMenu(button, element);
+ };
+}
+
+function UpdateStructToolbar() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ return;
+ }
+
+ var mixed = GetSelectionContainer();
+ if (!mixed) {
+ return;
+ }
+ var element = mixed.node;
+ var oneElementSelected = mixed.oneElementSelected;
+
+ if (!element) {
+ return;
+ }
+
+ if (
+ element == gLastFocusNode &&
+ oneElementSelected == gLastFocusNodeWasSelected
+ ) {
+ return;
+ }
+
+ gLastFocusNode = element;
+ gLastFocusNodeWasSelected = mixed.oneElementSelected;
+
+ var toolbar = document.getElementById("structToolbar");
+ if (!toolbar) {
+ return;
+ }
+ // We need to leave the <label> to flex the buttons to the left.
+ for (let node of toolbar.querySelectorAll("toolbarbutton,textbox")) {
+ node.remove();
+ }
+
+ toolbar.removeAttribute("label");
+
+ if (IsInHTMLSourceMode()) {
+ // we have destroyed the contents of the status bar and are
+ // about to recreate it ; but we don't want to do that in
+ // Source mode
+ return;
+ }
+
+ var tag, button;
+ var bodyElement = GetBodyElement();
+ var isFocusNode = true;
+ var tmp;
+ do {
+ tag = element.nodeName.toLowerCase();
+
+ button = document.createXULElement("toolbarbutton");
+ button.setAttribute("label", "<" + tag + ">");
+ button.setAttribute("value", tag);
+ button.setAttribute("context", "structToolbarContext");
+ button.className = "struct-button";
+
+ toolbar.insertBefore(button, toolbar.firstChild);
+
+ button.addEventListener("command", newCommandListener(element));
+
+ button.addEventListener(
+ "contextmenu",
+ newContextmenuListener(button, element)
+ );
+
+ if (isFocusNode && oneElementSelected) {
+ button.setAttribute("checked", "true");
+ isFocusNode = false;
+ }
+
+ tmp = element;
+ element = element.parentNode;
+ } while (element && tmp != bodyElement);
+}
+
+function SelectFocusNodeAncestor(element) {
+ var editor = GetCurrentEditor();
+ if (editor) {
+ if (element == GetBodyElement()) {
+ editor.selectAll();
+ } else {
+ editor.selectElement(element);
+ }
+ }
+ ResetStructToolbar();
+}
+
+function GetSelectionContainer() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ return null;
+ }
+
+ var selection;
+ try {
+ selection = editor.selection;
+ if (!selection) {
+ return null;
+ }
+ } catch (e) {
+ return null;
+ }
+
+ var result = { oneElementSelected: false };
+
+ if (selection.isCollapsed) {
+ result.node = selection.focusNode;
+ } else {
+ var rangeCount = selection.rangeCount;
+ if (rangeCount == 1) {
+ result.node = editor.getSelectedElement("");
+ var range = selection.getRangeAt(0);
+
+ // check for a weird case : when we select a piece of text inside
+ // a text node and apply an inline style to it, the selection starts
+ // at the end of the text node preceding the style and ends after the
+ // last char of the style. Assume the style element is selected for
+ // user's pleasure
+ if (
+ !result.node &&
+ range.startContainer.nodeType == Node.TEXT_NODE &&
+ range.startOffset == range.startContainer.length &&
+ range.endContainer.nodeType == Node.TEXT_NODE &&
+ range.endOffset == range.endContainer.length &&
+ range.endContainer.nextSibling == null &&
+ range.startContainer.nextSibling == range.endContainer.parentNode
+ ) {
+ result.node = range.endContainer.parentNode;
+ }
+
+ if (!result.node) {
+ // let's rely on the common ancestor of the selection
+ result.node = range.commonAncestorContainer;
+ } else {
+ result.oneElementSelected = true;
+ }
+ } else {
+ // assume table cells !
+ var i,
+ container = null;
+ for (i = 0; i < rangeCount; i++) {
+ range = selection.getRangeAt(i);
+ if (!container) {
+ container = range.startContainer;
+ } else if (container != range.startContainer) {
+ // all table cells don't belong to same row so let's
+ // select the parent of all rows
+ result.node = container.parentNode;
+ break;
+ }
+ result.node = container;
+ }
+ }
+ }
+
+ // make sure we have an element here
+ while (result.node.nodeType != Node.ELEMENT_NODE) {
+ result.node = result.node.parentNode;
+ }
+
+ // and make sure the element is not a special editor node like
+ // the <br> we insert in blank lines
+ // and don't select anonymous content !!! (fix for bug 190279)
+ while (
+ result.node.hasAttribute("_moz_editor_bogus_node") ||
+ editor.isAnonymousElement(result.node)
+ ) {
+ result.node = result.node.parentNode;
+ }
+
+ return result;
+}
+
+function FillInHTMLTooltipEditor(tooltip) {
+ const XLinkNS = "http://www.w3.org/1999/xlink";
+ var tooltipText = null;
+ var node;
+ if (IsInPreviewMode()) {
+ for (node = document.tooltipNode; node; node = node.parentNode) {
+ if (node.nodeType == Node.ELEMENT_NODE) {
+ tooltipText = node.getAttributeNS(XLinkNS, "title");
+ if (tooltipText && /\S/.test(tooltipText)) {
+ tooltip.setAttribute("label", tooltipText);
+ return true;
+ }
+ tooltipText = node.getAttribute("title");
+ if (tooltipText && /\S/.test(tooltipText)) {
+ tooltip.setAttribute("label", tooltipText);
+ return true;
+ }
+ }
+ }
+ } else {
+ for (node = document.tooltipNode; node; node = node.parentNode) {
+ if (
+ ChromeUtils.getClassName(node) === "HTMLImageElement" ||
+ ChromeUtils.getClassName(node) === "HTMLInputElement"
+ ) {
+ tooltipText = node.getAttribute("src");
+ } else if (ChromeUtils.getClassName(node) === "HTMLAnchorElement") {
+ tooltipText = node.getAttribute("href") || node.getAttribute("name");
+ }
+ if (tooltipText) {
+ tooltip.setAttribute("label", tooltipText);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+function UpdateTOC() {
+ window.openDialog(
+ "chrome://editor/content/EdInsertTOC.xhtml",
+ "_blank",
+ "chrome,close,modal,titlebar"
+ );
+ window.content.focus();
+}
+
+function InitTOCMenu() {
+ var elt = GetCurrentEditor().document.getElementById("mozToc");
+ var createMenuitem = document.getElementById("insertTOCMenuitem");
+ var updateMenuitem = document.getElementById("updateTOCMenuitem");
+ var removeMenuitem = document.getElementById("removeTOCMenuitem");
+ if (removeMenuitem && createMenuitem && updateMenuitem) {
+ if (elt) {
+ createMenuitem.setAttribute("disabled", "true");
+ updateMenuitem.removeAttribute("disabled");
+ removeMenuitem.removeAttribute("disabled");
+ } else {
+ createMenuitem.removeAttribute("disabled");
+ removeMenuitem.setAttribute("disabled", "true");
+ updateMenuitem.setAttribute("disabled", "true");
+ }
+ }
+}
+
+function RemoveTOC() {
+ var theDocument = GetCurrentEditor().document;
+ var elt = theDocument.getElementById("mozToc");
+ if (elt) {
+ elt.remove();
+ }
+
+ let anchorNodes = theDocument.querySelectorAll('a[name^="mozTocId"]');
+ for (let node of anchorNodes) {
+ if (node.parentNode) {
+ node.remove();
+ }
+ }
+}
diff --git a/comm/suite/editor/base/content/editor.xhtml b/comm/suite/editor/base/content/editor.xhtml
new file mode 100644
index 0000000000..033185592f
--- /dev/null
+++ b/comm/suite/editor/base/content/editor.xhtml
@@ -0,0 +1,402 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/" type="text/css"?>
+
+<?xml-stylesheet href="chrome://editor/skin/editorPrimaryToolbar.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/editorFormatToolbar.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/editorModeToolbar.css" type="text/css"?>
+<?xul-overlay href="chrome://editor/content/editorOverlay.xhtml"?>
+<?xul-overlay href="chrome://editor/content/editingOverlay.xhtml"?>
+<?xul-overlay href="chrome://editor/content/composerOverlay.xhtml"?>
+<?xul-overlay href="chrome://communicator/content/contentAreaContextOverlay.xhtml"?>
+<?xul-overlay href="chrome://editor/content/EditorContextMenuOverlay.xhtml"?>
+<?xul-overlay href="chrome://communicator/content/charsetOverlay.xhtml"?>
+<?xul-overlay href="chrome://communicator/content/utilityOverlay.xhtml"?>
+<?xul-overlay href="chrome://communicator/content/tasksOverlay.xhtml"?>
+<?xul-overlay href="chrome://communicator/content/sidebar/sidebarOverlay.xhtml"?>
+
+<!DOCTYPE window [
+<!ENTITY % editorDTD SYSTEM "chrome://editor/locale/editor.dtd" >
+%editorDTD;
+<!ENTITY % editorOverlayDTD SYSTEM "chrome://editor/locale/editorOverlay.dtd" >
+%editorOverlayDTD;
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+]>
+
+<window id="editorWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="EditorOnLoad()"
+ onunload="EditorShutdown()"
+ onclose="return Async.promiseSpinningly(EditorCanClose())"
+ onfocus="EditorOnFocus()"
+ title="&editorWindow.titlemodifier;"
+ titlemodifier="&editorWindow.titlemodifier;"
+ titlemenuseparator="&editorWindow.titlemodifiermenuseparator;"
+ toggletoolbar="true"
+ lightweightthemes="true"
+ lightweightthemesfooter="status-bar"
+ windowtype="composer:html"
+ macanimationtype="document"
+ drawtitle="true"
+ width="640" height="480"
+ screenX="10" screenY="10"
+ persist="screenX screenY width height sizemode">
+
+ <script src="chrome://editor/content/editor.js"/>
+ <script src="chrome://editor/content/publishprefs.js"/>
+ <script src="chrome://communicator/content/contentAreaClick.js"/>
+ <script src="chrome://global/content/printUtils.js"/>
+ <script src="chrome://global/content/nsDragAndDrop.js"/>
+
+ <popupset id="contentAreaContextSet"/>
+ <popupset id="editorPopupSet">
+ <menupopup id="structToolbarContext"/>
+ <menupopup id="sidebarPopup"/>
+ </popupset>
+
+ <commandset id="editorCommands">
+ <commandset id="commonEditorMenuItems"/>
+ <commandset id="composerMenuItems"/>
+ <commandset id="composerOnlyMenuItems"
+ commandupdater="true"
+ events="create, mode_switch"
+ oncommandupdate="goUpdateComposerMenuItems(this);">
+ <!-- file menu -->
+ <command id="cmd_exportToText"
+ label="&exportToTextCmd.label;"
+ accesskey="&exportToTextCmd.accesskey;"
+ oncommand="goDoCommand('cmd_exportToText');"/>
+ <command id="cmd_preview"
+ oncommand="goDoCommand('cmd_preview');"/>
+ <command id="cmd_editSendPage"
+ label="&sendPageCmd.label;"
+ accesskey="&sendPageCmd.accesskey;"
+ oncommand="goDoCommand('cmd_editSendPage');"/>
+ <!-- format menu -->
+ <command id="cmd_pageProperties"
+ oncommand="goDoCommand('cmd_pageProperties');"/>
+ <!-- tools menu -->
+ <command id="cmd_validate"
+ oncommand="goDoCommand('cmd_validate');"/>
+ <!-- toolbars -->
+ <command id="cmd_NormalMode"
+ oncommand="goDoCommand('cmd_NormalMode');"/>
+ <command id="cmd_AllTagsMode"
+ oncommand="goDoCommand('cmd_AllTagsMode');"/>
+ <command id="cmd_HTMLSourceMode"
+ oncommand="goDoCommand('cmd_HTMLSourceMode');"/>
+ <command id="cmd_PreviewMode"
+ oncommand="goDoCommand('cmd_PreviewMode');"/>
+ </commandset>
+ <commandset id="composerEditMenuItems"/>
+ <commandset id="composerSaveMenuItems"/>
+ <commandset id="composerStyleMenuItems">
+ <command id="cmd_updateStructToolbar"
+ oncommand="goDoCommand('cmd_updateStructToolbar');"/>
+ </commandset>
+ <commandset id="composerTableMenuItems"/>
+ <commandset id="composerListMenuItems"/>
+ <commandset id="tasksCommands"/>
+ <!-- view menu -->
+ <command id="cmd_viewEditModeToolbar"
+ oncommand="goToggleToolbar('EditModeToolbar','cmd_viewEditModeToolbar');"
+ checked="true"/>
+ </commandset>
+
+ <tooltip id="aHTMLTooltip" onpopupshowing="return FillInHTMLTooltipEditor(this);"/>
+
+ <!-- keys are appended from the overlay -->
+ <keyset id="editorKeys">
+ <keyset id="tasksKeys"/>
+ <key id="showHideSidebar"/>
+ <!-- eat these tab events here to stop focus from moving -->
+ <key keycode="VK_TAB" oncommand="return true;"/>
+ <key keycode="VK_TAB" modifiers="shift" oncommand="return true;"/>
+ <key keycode="VK_TAB" modifiers="control" oncommand="return true;"/>
+ <key keycode="VK_TAB" modifiers="control,shift" oncommand="return true;"/>
+ </keyset>
+
+ <vbox id="titlebar"/>
+
+<toolbox id="EditorToolbox"
+ class="toolbox-top"
+ mode="full"
+ defaultmode="full">
+ <toolbar id="toolbar-menubar"
+ type="menubar"
+ class="chromeclass-menubar"
+ persist="collapsed"
+ grippytooltiptext="&menuBar.tooltip;"
+ customizable="true"
+ defaultset="menubar-items"
+ mode="icons"
+ iconsize="small"
+ defaultmode="icons"
+ defaulticonsize="small"
+ context="toolbar-context-menu">
+ <toolbaritem id="menubar-items"
+ class="menubar-items"
+ align="center">
+ <menubar id="main-menubar">
+ <menu id="menu_File"/>
+ <menu id="menu_Edit"/>
+
+ <menu id="menu_View">
+ <!-- id pulls in "Show Sidebar" item from sidebarOverlay -->
+ <menupopup id="menu_View_Popup">
+ <menu id="menu_Toolbars">
+ <menupopup id="view_toolbars_popup"
+ onpopupshowing="onViewToolbarsPopupShowing(event);"
+ oncommand="onViewToolbarCommand(event);">
+ <menuitem id="viewEditModeToolbar"
+ label="&editmodeToolbarCmd.label;"
+ accesskey="&editmodeToolbarCmd.accesskey;"
+ type="checkbox"
+ command="cmd_viewEditModeToolbar"/>
+ <menuitem id="menu_showTaskbar"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="viewSep1"/>
+ <menuitem id="viewNormalMode"
+ type="radio"
+ group="mode"
+ checked="true"
+ label="&NormalMode.label;"
+ accesskey="&NormalMode.accesskey;"
+ command="cmd_NormalMode"/>
+ <menuitem id="viewAllTagsMode"
+ type="radio"
+ group="mode"
+ label="&AllTagsMode.label;"
+ accesskey="&AllTagsMode.accesskey;"
+ command="cmd_AllTagsMode"/>
+ <menuitem id="viewSourceMode"
+ type="radio"
+ group="mode"
+ label="&HTMLSourceMode.label;"
+ accesskey="&HTMLSourceMode.accesskey;"
+ command="cmd_HTMLSourceMode"/>
+ <menuitem id="viewPreviewMode"
+ type="radio"
+ group="mode"
+ label="&PreviewMode.label;"
+ accesskey="&PreviewMode.accesskey;"
+ command="cmd_PreviewMode"/>
+ <menuseparator id="viewSep2"/>
+ <menu id="charsetMenu"
+ onpopupshowing="EditorUpdateCharsetMenu(event.target);"
+ oncommand="EditorSetCharacterSet(event);"/>
+ </menupopup>
+ </menu>
+
+ <menu id="insertMenu"/>
+
+ <menu id="formatMenu"
+ label="&formatMenu.label;"
+ accesskey="&formatMenu.accesskey;">
+ <menupopup id="formatMenuPopup">
+ <menuitem id="snapToGrid"
+ label="&grid.label;"
+ accesskey="&grid.accesskey;"
+ oncommand="goDoCommand('cmd_grid');"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuseparator/>
+ <menuitem id="objectProperties"/>
+ <menuitem id="colorsAndBackground"/>
+ <!-- Don't use 'observes', must call command correctly -->
+ <menuitem id="pageProperties"
+ label="&pageProperties.label;"
+ accesskey="&pageProperties.accesskey;"
+ oncommand="goDoCommand('cmd_pageProperties');"
+ observes="cmd_renderedHTMLEnabler"/>
+ </menupopup>
+ </menu>
+
+ <menu id="tableMenu"/>
+
+ <!-- tasks menu filled from tasksOverlay -->
+ <menu id="tasksMenu">
+ <menupopup id="taskPopup">
+ <menuitem id="menu_validate"
+ label="&validateCmd.label;"
+ accesskey="&validateCmd.accesskey;"
+ command="cmd_validate"/>
+ <menuseparator id="sep_validate"/>
+ </menupopup>
+ </menu>
+
+ <menu id="windowMenu"/>
+
+ <!-- help menu filled from globalOverlay -->
+ <menu id="menu_Help"/>
+ </menubar>
+ </toolbaritem>
+ </toolbar>
+
+ <!-- toolbar mostly filled out from editorOverlay -->
+ <!-- add class="standard" for dark blue background, icons need rework first -->
+ <toolbar id="EditToolbar"
+ class="chromeclass-toolbar toolbar-primary"
+ persist="collapsed"
+ grippytooltiptext="&compositionToolbar.tooltip;"
+ toolbarname="&compositionToolbarCmd.label;"
+ accesskey="&compositionToolbarCmd.accesskey;"
+ customizable="true"
+ defaultset="newButton,openButton,saveButton,publishButton,previewButton,print-button,separator,linkButton,imageButton,tableButton,spellingButton,spring,throbber-box"
+ context="toolbar-context-menu">
+ <toolbarbutton id="newButton"/>
+ <toolbarbutton id="openButton"/>
+ <toolbarbutton id="saveButton"/>
+ <toolbarbutton id="publishButton"/>
+ <toolbarbutton id="previewButton"
+ class="toolbarbutton-1"
+ label="&previewToolbarCmd.label;"
+ removable="true"
+ command="cmd_preview"
+ tooltiptext="&previewToolbarCmd.tooltip;"/>
+ <toolbarbutton id="cutButton"/>
+ <toolbarbutton id="copyButton"/>
+ <toolbarbutton id="pasteButton"/>
+ <toolbarbutton id="print-button"/>
+ <toolbarbutton id="findButton"/>
+ <toolbarbutton id="linkButton"/>
+ <toolbarbutton id="namedAnchorButton"/>
+ <toolbarbutton id="imageButton"/>
+ <toolbarbutton id="formButton"/>
+ <toolbarbutton id="hlineButton"/>
+ <toolbarbutton id="tableButton"/>
+ <toolbarbutton id="spellingButton"/>
+ <toolbaritem id="throbber-box"/>
+ </toolbar>
+
+ <toolbarset id="customToolbars" context="toolbar-context-menu"/>
+
+ <toolbarpalette id="EditToolbarPalette"/>
+
+ <toolbar id="FormatToolbar"
+ class="chromeclass-toolbar"
+ persist="collapsed"
+ grippytooltiptext="&formatToolbar.tooltip;"
+ toolbarname="&formattingToolbarCmd.label;"
+ accesskey="&formattingToolbarCmd.accesskey;"
+ customizable="true"
+ defaultset="paragraph-select-container,color-buttons-container,HighlightColorButton,separator,DecreaseFontSizeButton,IncreaseFontSizeButton,separator,boldButton,italicButton,underlineButton,separator,ulButton,olButton,outdentButton,indentButton,separator,align-left-button,align-center-button,align-right-button,align-justify-button,absolutePositionButton,decreaseZIndexButton,increaseZIndexButton"
+ mode="icons"
+ iconsize="small"
+ defaultmode="icons"
+ defaulticonsize="small"
+ context="toolbar-context-menu"
+ nowindowdrag="true">
+ <!-- from editorOverlay -->
+ <toolbaritem id="paragraph-select-container"/>
+ <toolbaritem id="color-buttons-container"
+ disableoncustomize="true"/>
+ <toolbarbutton id="HighlightColorButton"/>
+ <!-- Enable if required for SeaMonkey.
+ <toolbarbutton id="AbsoluteFontSizeButton"/>
+ -->
+ <toolbarbutton id="DecreaseFontSizeButton"/>
+ <toolbarbutton id="IncreaseFontSizeButton"/>
+ <toolbarbutton id="boldButton"/>
+ <toolbarbutton id="italicButton"/>
+ <toolbarbutton id="underlineButton"/>
+ <toolbarbutton id="ulButton"/>
+ <toolbarbutton id="olButton"/>
+ <toolbarbutton id="outdentButton"/>
+ <toolbarbutton id="indentButton"/>
+ <toolbarbutton id="align-left-button"/>
+ <toolbarbutton id="align-center-button"/>
+ <toolbarbutton id="align-right-button"/>
+ <toolbarbutton id="align-justify-button"/>
+ <toolbarbutton id="absolutePositionButton"/>
+ <toolbarbutton id="decreaseZIndexButton"/>
+ <toolbarbutton id="increaseZIndexButton"/>
+
+ <!-- TODO: Change to a menulist? -->
+ <!-- menu>
+ <button id="AlignPopupButton"/>
+ <menupopup id="AlignmentPopup"/>
+ </menu -->
+
+
+ <spacer flex="1"/>
+ </toolbar>
+</toolbox>
+
+<!-- sidebar/toolbar/content/status -->
+<hbox id="sidebar-parent" flex="1">
+ <!-- From sidebarOverlay.xhtml -->
+ <vbox id="sidebar-box" class="chromeclass-extrachrome" hidden="true"/>
+ <splitter id="sidebar-splitter" class="chromeclass-extrachrome" hidden="true"/>
+
+ <vbox id="appcontent" flex="1">
+ <deck id="ContentWindowDeck" selectedIndex="0" flex="1">
+ <vbox>
+ <findbar id="FindToolbar" browserid="content-frame"/>
+ <editor editortype="html"
+ type="content"
+ primary="true"
+ id="content-frame"
+ onclick="EditorClick(event);"
+ ondblclick="EditorDblClick(event);"
+ context="contentAreaContextMenu"
+ flex="1"
+ tooltip="aHTMLTooltip"/>
+ </vbox>
+ <vbox>
+ <label id="doctype-text" crop="right"/>
+ <editor type="content"
+ id="content-source"
+ context="contentAreaContextMenu"
+ flex="1"/>
+ </vbox>
+ </deck>
+
+ <!-- Edit Mode toolbar -->
+ <tabbox id="EditModeToolbar"
+ persist="collapsed">
+ <tabs id="EditModeTabs"
+ class="tabs-bottom"
+ flex="1"
+ onselect="this.selectedItem.doCommand();">
+ <tab id="NormalModeButton"
+ class="tab-bottom edit-mode"
+ label="&NormalModeTab.label;"
+ tooltiptext="&NormalMode.tooltip;"
+ command="cmd_NormalMode"/>
+ <tab id="TagModeButton"
+ class="tab-bottom edit-mode"
+ label="&AllTagsModeTab.label;"
+ tooltiptext="&AllTagsMode.tooltip;"
+ command="cmd_AllTagsMode"/>
+ <tab id="SourceModeButton"
+ class="tab-bottom edit-mode"
+ label="&HTMLSourceModeTab.label;"
+ tooltiptext="&HTMLSourceMode.tooltip;"
+ dir="&HTMLSourceModeTab.dir;"
+ command="cmd_HTMLSourceMode"/>
+ <tab id="PreviewModeButton"
+ class="tab-bottom edit-mode"
+ label="&PreviewModeTab.label;"
+ tooltiptext="&PreviewMode.tooltip;"
+ command="cmd_PreviewMode"/>
+ </tabs>
+ </tabbox>
+
+ </vbox> <!-- appcontent -->
+</hbox><!-- sidebar-parent -->
+
+ <!-- Some of this is from globalOverlay.xhtml -->
+ <hbox class="statusbar chromeclass-status" id="status-bar">
+ <statusbarpanel id="component-bar"/>
+ <hbox id="structToolbar" class="statusbarpanel" flex="1" pack="end">
+ <label id="structSpacer" value="" flex="1"/>
+ </hbox>
+ <statusbarpanel id="offline-status" class="statusbarpanel-iconic"/>
+ </hbox>
+</window>
diff --git a/comm/suite/editor/base/content/editorApplicationOverlay.js b/comm/suite/editor/base/content/editorApplicationOverlay.js
new file mode 100644
index 0000000000..17d02a510b
--- /dev/null
+++ b/comm/suite/editor/base/content/editorApplicationOverlay.js
@@ -0,0 +1,161 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Implementations of nsIControllerCommand for composer commands */
+
+function initEditorContextMenuItems(aEvent)
+{
+ var shouldShowEditPage = !gContextMenu.onImage && !gContextMenu.onLink && !gContextMenu.onTextInput && !gContextMenu.inDirList;
+ gContextMenu.showItem( "context-editpage", shouldShowEditPage );
+
+ var shouldShowEditLink = gContextMenu.onSaveableLink;
+ gContextMenu.showItem( "context-editlink", shouldShowEditLink );
+
+ // Hide the applications separator if there's no add-on apps present.
+ gContextMenu.showItem("context-sep-apps", gContextMenu.shouldShowSeparator("context-sep-apps"));
+}
+
+function initEditorContextMenuListener(aEvent)
+{
+ var popup = document.getElementById("contentAreaContextMenu");
+ if (popup)
+ popup.addEventListener("popupshowing", initEditorContextMenuItems);
+}
+
+addEventListener("load", initEditorContextMenuListener, false);
+
+function editDocument(aDocument)
+{
+ if (!aDocument)
+ aDocument = window.content.document;
+
+ editPage(aDocument.URL);
+}
+
+function editPageOrFrame()
+{
+ var focusedWindow = document.commandDispatcher.focusedWindow;
+
+ // if the uri is a specific frame, grab it, else use the frameset uri
+ // and let Composer handle error if necessary
+ editPage(getContentFrameURI(focusedWindow));
+}
+
+function getContentFrameURI(aFocusedWindow)
+{
+ let isContentFrame = aFocusedWindow ?
+ (aFocusedWindow.top == window.content) : false;
+
+ let contentFrame = isContentFrame ?
+ aFocusedWindow : window.content;
+
+ return contentFrame.location.href;
+}
+
+// Any non-editor window wanting to create an editor with a URL
+// should use this instead of "window.openDialog..."
+// We must always find an existing window with requested URL
+function editPage(url, aFileType)
+{
+ // aFileType is optional and needs to default to html.
+ aFileType = aFileType || "html";
+
+ // Always strip off "view-source:" and #anchors
+ url = url.replace(/^view-source:/, "").replace(/#.*/, "");
+
+ // if the current window is a browser window, then extract the current charset menu setting from the current
+ // document and use it to initialize the new composer window...
+
+ var wintype = document.documentElement.getAttribute('windowtype');
+ var charsetArg;
+
+ if (wintype == "navigator:browser" && content.document)
+ charsetArg = "charset=" + content.document.characterSet;
+
+ try {
+ let uri = createURI(url, null, null);
+
+ let enumerator = Services.wm.getEnumerator("composer:" + aFileType);
+ let emptyWindow;
+ while ( enumerator.hasMoreElements() )
+ {
+ var win = enumerator.getNext();
+ if (win && !win.closed && win.IsWebComposer())
+ {
+ if (CheckOpenWindowForURIMatch(uri, win))
+ {
+ // We found an editor with our url
+ win.focus();
+ return;
+ }
+ else if (!emptyWindow && win.PageIsEmptyAndUntouched())
+ {
+ emptyWindow = win;
+ }
+ }
+ }
+
+ if (emptyWindow)
+ {
+ // we have an empty window we can use
+ if (aFileType == "html" && emptyWindow.IsInHTMLSourceMode())
+ emptyWindow.SetEditMode(emptyWindow.PreviousNonSourceDisplayMode);
+ emptyWindow.EditorLoadUrl(url);
+ emptyWindow.focus();
+ emptyWindow.SetSaveAndPublishUI(url);
+ return;
+ }
+
+ // Create new Composer / Text Editor window.
+ if (aFileType == "text" && ("EditorNewPlaintext" in window))
+ EditorNewPlaintext(url, charsetArg);
+ else
+ NewEditorWindow(url, charsetArg);
+
+ } catch(e) {}
+}
+
+function createURI(urlstring)
+{
+ try {
+ return Services.io.newURI(urlstring);
+ } catch (e) {}
+
+ return null;
+}
+
+function CheckOpenWindowForURIMatch(uri, win)
+{
+ try {
+ return createURI(win.content.document.URL).equals(uri);
+ } catch (e) {}
+
+ return false;
+}
+
+function toEditor()
+{
+ if (!CycleWindow("composer:html"))
+ NewEditorWindow();
+}
+
+function NewEditorWindow(aUrl, aCharsetArg)
+{
+ window.openDialog("chrome://editor/content",
+ "_blank",
+ "chrome,all,dialog=no",
+ aUrl || "about:blank",
+ aCharsetArg);
+}
+
+function NewEditorFromTemplate()
+{
+ // XXX not implemented
+}
+
+function NewEditorFromDraft()
+{
+ // XXX not implemented
+}
diff --git a/comm/suite/editor/base/content/editorOverlay.xhtml b/comm/suite/editor/base/content/editorOverlay.xhtml
new file mode 100644
index 0000000000..7a5d3900f0
--- /dev/null
+++ b/comm/suite/editor/base/content/editorOverlay.xhtml
@@ -0,0 +1,1504 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay [
+<!ENTITY % editorOverlayDTD SYSTEM "chrome://editor/locale/editorOverlay.dtd">
+%editorOverlayDTD;
+<!ENTITY % editorSmileyOverlayDTD SYSTEM
+ "chrome://editor/locale/editorSmileyOverlay.dtd">
+%editorSmileyOverlayDTD;
+]>
+
+<overlay id="editorOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+<script src="chrome://editor/content/editorUtilities.js"/>
+<script src="chrome://editor/content/ComposerCommands.js"/>
+
+ <keyset id="editorKeys">
+ <!-- defined in globalOverlay -->
+ <key id="key_newNavigator"/>
+ <key id="key_newPrivateWindow"/>
+ <key id="key_newBlankPage"/>
+ <key id="key_save"
+ key="&saveCmd.key;"
+ command="cmd_save"
+ modifiers="accel"/>
+ <key id="key_print"/>
+ <key id="key_close"/>
+ <key id="key_undo"/>
+ <key id="key_redo"/>
+ <key id="key_cut"/>
+ <key id="key_copy"/>
+ <key id="key_paste"/>
+ <key id="key_delete"/>
+ <key id="key_delete2"/>
+ <key id="key_selectAll"/>
+ <key id="pastequotationkb"
+ key="&pasteAsQuotationCmd.key;"
+ command="cmd_pasteQuote"
+ modifiers="accel, shift"/>
+ <key id="pastenoformattingkb"
+ key="&pasteNoFormatting.key;"
+ command="cmd_pasteNoFormatting"
+ modifiers="accel, shift"/>
+ <key id="key_rewrap"
+ key="&editRewrapCmd.key;"
+ command="cmd_rewrap"
+ modifiers="accel"/>
+ <keyset id="findKeys"/>
+ <key id="key_checkspelling"
+ key="&checkSpellingCmd2.key;"
+ command="cmd_spelling"
+ modifiers="accel,shift"/>
+
+ <key id="boldkb"
+ key="&styleBoldCmd.key;"
+ command="cmd_bold"
+ modifiers="accel"/>
+ <key id="italickb"
+ key="&styleItalicCmd.key;"
+ command="cmd_italic"
+ modifiers="accel"/>
+ <key id="underlinekb"
+ key="&styleUnderlineCmd.key;"
+ command="cmd_underline"
+ modifiers="accel"/>
+ <key id="fixedwidthkb"
+ key="&fontFixedWidth.key;"
+ command="cmd_tt"
+ modifiers="accel"/>
+
+ <key id="increaseindentkb"
+ key="&increaseIndent.key;"
+ command="cmd_indent"
+ modifiers="accel"/>
+ <key id="decreaseindentkb"
+ key="&decreaseIndent.key;"
+ command="cmd_outdent"
+ modifiers="accel"/>
+
+ <key id="removestyleskb"
+ key="&formatRemoveStyles.key;"
+ command="cmd_removeStyles"
+ modifiers="accel, shift"/>
+ <key id="removestyleskb2"
+ key=" "
+ command="cmd_removeStyles"
+ modifiers="accel"/>
+ <key id="removelinkskb"
+ key="&formatRemoveLinks.key;"
+ command="cmd_removeLinks"
+ modifiers="accel, shift"/>
+ <key id="removenamedanchorskb"
+ key="&formatRemoveNamedAnchors2.key;"
+ command="cmd_removeNamedAnchors"
+ modifiers="accel, shift"/>
+ <key id="decreasefontsizekb"
+ key="&decrementFontSize.key;"
+ command="cmd_decreaseFontStep"
+ modifiers="accel"/>
+ <key id="increasefontsizekb"
+ key="&incrementFontSize.key;"
+ command="cmd_increaseFontStep"
+ modifiers="accel"/>
+ <key key="&incrementFontSize.key;"
+ command="cmd_increaseFontStep"
+ modifiers="accel,shift"/>
+ <key key="&incrementFontSize.key2;"
+ command="cmd_increaseFontStep"
+ modifiers="accel"/>
+
+ <key id="insertlinkkb"
+ key="&insertLinkCmd2.key;"
+ command="cmd_link"
+ modifiers="accel"/>
+ </keyset>
+
+ <!-- commands updated when the editor gets created -->
+ <commandset id="commonEditorMenuItems"
+ commandupdater="true"
+ events="create"
+ oncommandupdate="goUpdateComposerMenuItems(this)">
+ <command id="cmd_printSetup" oncommand="goDoCommand('cmd_printSetup');"/>
+ <command id="cmd_printpreview" oncommand="goDoCommand('cmd_printpreview');"/>
+ <command id="cmd_print" oncommand="goDoCommand('cmd_print');"/>
+ <command id="cmd_close" oncommand="goDoCommand('cmd_close');"/>
+ </commandset>
+
+ <commandset id="composerMenuItems"
+ commandupdater="true"
+ events="create, mode_switch"
+ oncommandupdate="goUpdateComposerMenuItems(this)">
+ <!-- format menu -->
+ <command id="cmd_listProperties" oncommand="goDoCommand('cmd_listProperties')"/>
+ <command id="cmd_colorProperties" oncommand="goDoCommand('cmd_colorProperties')"/>
+
+ <command id="cmd_link" oncommand="goDoCommand('cmd_link')"/>
+ <command id="cmd_anchor" oncommand="goDoCommand('cmd_anchor')"/>
+ <command id="cmd_image" oncommand="goDoCommand('cmd_image')"/>
+ <command id="cmd_hline" oncommand="goDoCommand('cmd_hline')"/>
+ <command id="cmd_table" oncommand="goDoCommand('cmd_table')"/>
+ <command id="cmd_objectProperties" oncommand="goDoCommand('cmd_objectProperties')"/>
+ <command id="cmd_insertChars" oncommand="goDoCommand('cmd_insertChars')" label="&insertCharsCmd.label;"/>
+ <command id="cmd_insertHTMLWithDialog" oncommand="goDoCommand('cmd_insertHTMLWithDialog')" label="&insertHTMLCmd.label;"/>
+ <command id="cmd_insertMathWithDialog" oncommand="goDoCommand('cmd_insertMathWithDialog')" label="&insertMathCmd.label;"/>
+ <command id="cmd_insertBreak" oncommand="goDoCommand('cmd_insertBreak')"/>
+ <command id="cmd_insertBreakAll" oncommand="goDoCommand('cmd_insertBreakAll')"/>
+
+ <!-- only used in context popup menu -->
+ <command id="cmd_editLink" oncommand="goDoCommand('cmd_editLink')"/>
+
+ <!-- dummy command used just to disable things in non-HTML modes -->
+ <command id="cmd_renderedHTMLEnabler"/>
+ </commandset>
+
+ <!-- edit menu commands. These get updated by code in globalOverlay.js -->
+ <commandset id="composerEditMenuItems"
+ commandupdater="true"
+ events="create, mode_switch"
+ oncommandupdate="goUpdateComposerMenuItems(this)">
+ <command id="cmd_undo"/>
+ <command id="cmd_redo"/>
+ <command id="cmd_cut"/>
+ <command id="cmd_copy"/>
+ <command id="cmd_paste"/>
+ <command id="cmd_pasteNoFormatting"
+ label="&pasteNoFormatting.label;"
+ accesskey="&pasteNoFormatting.accesskey;"
+ oncommand="goDoCommand('cmd_pasteNoFormatting');"/>
+ <command id="cmd_delete"/>
+ <command id="cmd_selectAll"/>
+ <command id="cmd_preferences" oncommand="goDoCommand('cmd_preferences')"/>
+ <command id="cmd_findReplace" oncommand="goDoCommand('cmd_findReplace')"/>
+ <command id="cmd_find" oncommand="goDoCommand('cmd_find')"/>
+ <command id="cmd_findNext" oncommand="goDoCommand('cmd_findNext');"/>
+ <command id="cmd_findPrev" oncommand="goDoCommand('cmd_findPrev');"/>
+ <command id="cmd_spelling" oncommand="goDoCommand('cmd_spelling')"/>
+ <command id="cmd_pasteQuote"
+ label="&pasteAsQuotationCmd.label;"
+ accesskey="&pasteAsQuotationCmd.accesskey;"
+ oncommand="goDoCommand('cmd_pasteQuote');"/>
+ <command id="cmd_rewrap" oncommand="goDoCommand('cmd_rewrap');"/>
+ </commandset>
+
+ <!-- style related commands that update on creation, and on selection change -->
+ <commandset id="composerStyleMenuItems"
+ commandupdater="true"
+ events="create, style, mode_switch"
+ oncommandupdate="goUpdateComposerMenuItems(this)">
+ <command id="cmd_bold" state="false" oncommand="doStyleUICommand('cmd_bold')"/>
+ <command id="cmd_italic" state="false" oncommand="doStyleUICommand('cmd_italic')"/>
+ <command id="cmd_underline" state="false" oncommand="doStyleUICommand('cmd_underline')"/>
+ <command id="cmd_tt" state="false" oncommand="doStyleUICommand('cmd_tt')"/>
+ <command id="cmd_smiley"/>
+ <command id="cmd_strikethrough" state="false" oncommand="doStyleUICommand('cmd_strikethrough');"/>
+ <command id="cmd_superscript" state="false" oncommand="doStyleUICommand('cmd_superscript');"/>
+ <command id="cmd_subscript" state="false" oncommand="doStyleUICommand('cmd_subscript');"/>
+ <command id="cmd_nobreak" state="false" oncommand="doStyleUICommand('cmd_nobreak');"/>
+ <command id="cmd_em" state="false" oncommand="doStyleUICommand('cmd_em')"/>
+ <command id="cmd_strong" state="false" oncommand="doStyleUICommand('cmd_strong')"/>
+ <command id="cmd_cite" state="false" oncommand="doStyleUICommand('cmd_cite')"/>
+ <command id="cmd_abbr" state="false" oncommand="doStyleUICommand('cmd_abbr')"/>
+ <command id="cmd_acronym" state="false" oncommand="doStyleUICommand('cmd_acronym')"/>
+ <command id="cmd_code" state="false" oncommand="doStyleUICommand('cmd_code')"/>
+ <command id="cmd_samp" state="false" oncommand="doStyleUICommand('cmd_samp')"/>
+ <command id="cmd_var" state="false" oncommand="doStyleUICommand('cmd_var')"/>
+ <command id="cmd_ul" state="false" oncommand="doStyleUICommand('cmd_ul')"/>
+ <command id="cmd_ol" state="false" oncommand="doStyleUICommand('cmd_ol')"/>
+ <command id="cmd_indent" oncommand="goDoCommand('cmd_indent')"/>
+ <command id="cmd_outdent" oncommand="goDoCommand('cmd_outdent')"/>
+
+ <!-- the state attribute gets filled with the paragraph format before the command is executed -->
+ <command id="cmd_paragraphState" state="" oncommand="doStatefulCommand('cmd_paragraphState', event.target.value)"/>
+ <command id="cmd_fontFace" state="" oncommand="doStatefulCommand('cmd_fontFace', event.target.value)"/>
+
+ <!-- No "oncommand", use EditorSelectColor() to bring up color dialog -->
+ <command id="cmd_fontColor" state=""/>
+ <command id="cmd_backgroundColor" state=""/>
+ <command id="cmd_highlight" state="transparent" oncommand="EditorSelectColor('Highlight', event);"/>
+ <command id="cmd_fontSize" oncommand="goDoCommand('cmd_fontSize')"/>
+ <command id="cmd_align" state=""/>
+ <command id="cmd_absPos" state="" oncommand="goDoCommand('cmd_absPos')"/>
+ <command id="cmd_increaseZIndex" state="" oncommand="goDoCommand('cmd_increaseZIndex')"/>
+ <command id="cmd_decreaseZIndex" state="" oncommand="goDoCommand('cmd_decreaseZIndex')"/>
+ <command id="cmd_advancedProperties" oncommand="goDoCommand('cmd_advancedProperties')"/>
+ <command id="cmd_increaseFontStep" oncommand="goDoCommand('cmd_increaseFontStep')"/>
+ <command id="cmd_decreaseFontStep" oncommand="goDoCommand('cmd_decreaseFontStep')"/>
+ <command id="cmd_removeStyles" oncommand="goDoCommand('cmd_removeStyles')"/>
+ <command id="cmd_removeLinks" oncommand="goDoCommand('cmd_removeLinks')"/>
+ <command id="cmd_removeNamedAnchors" oncommand="goDoCommand('cmd_removeNamedAnchors')"/>
+ </commandset>
+
+ <!-- commands updated only when the menu gets created -->
+ <commandset id="composerListMenuItems"
+ commandupdater="true"
+ events="create, mode_switch"
+ oncommandupdate="goUpdateComposerMenuItems(this)">
+ <!-- List menu -->
+ <command id="cmd_dt" oncommand="doStyleUICommand('cmd_dt')"/>
+ <command id="cmd_dd" oncommand="doStyleUICommand('cmd_dd')"/>
+ <command id="cmd_removeList" oncommand="goDoCommand('cmd_removeList')"/>
+ <!-- cmd_ul and cmd_ol are shared with toolbar and are in composerStyleMenuItems commandset -->
+ </commandset>
+
+ <commandset id="composerTableMenuItems"
+ commandupdater="true"
+ events="create, mode_switch"
+ oncommandupdate="goUpdateTableMenuItems(this)">
+ <!-- Table menu -->
+ <command id="cmd_SelectTable" oncommand="goDoCommand('cmd_SelectTable')"/>
+ <command id="cmd_SelectRow" oncommand="goDoCommand('cmd_SelectRow')"/>
+ <command id="cmd_SelectColumn" oncommand="goDoCommand('cmd_SelectColumn')"/>
+ <command id="cmd_SelectCell" oncommand="goDoCommand('cmd_SelectCell')"/>
+ <command id="cmd_SelectAllCells" oncommand="goDoCommand('cmd_SelectAllCells')"/>
+ <command id="cmd_InsertTable" oncommand="goDoCommand('cmd_InsertTable')"/>
+ <command id="cmd_InsertRowAbove" oncommand="goDoCommand('cmd_InsertRowAbove')"/>
+ <command id="cmd_InsertRowBelow" oncommand="goDoCommand('cmd_InsertRowBelow')"/>
+ <command id="cmd_InsertColumnBefore" oncommand="goDoCommand('cmd_InsertColumnBefore')"/>
+ <command id="cmd_InsertColumnAfter" oncommand="goDoCommand('cmd_InsertColumnAfter')"/>
+ <command id="cmd_InsertCellBefore" oncommand="goDoCommand('cmd_InsertCellBefore')"/>
+ <command id="cmd_InsertCellAfter" oncommand="goDoCommand('cmd_InsertCellAfter')"/>
+ <command id="cmd_DeleteTable" oncommand="goDoCommand('cmd_DeleteTable')"/>
+ <command id="cmd_DeleteRow" oncommand="goDoCommand('cmd_DeleteRow')"/>
+ <command id="cmd_DeleteColumn" oncommand="goDoCommand('cmd_DeleteColumn')"/>
+ <command id="cmd_DeleteCell" oncommand="goDoCommand('cmd_DeleteCell')"/>
+ <command id="cmd_DeleteCellContents" oncommand="goDoCommand('cmd_DeleteCellContents')"/>
+ <command id="cmd_NormalizeTable" oncommand="goDoCommand('cmd_NormalizeTable')"/>
+ <command id="cmd_JoinTableCells" oncommand="goDoCommand('cmd_JoinTableCells')"/>
+ <command id="cmd_SplitTableCell" oncommand="goDoCommand('cmd_SplitTableCell')"/>
+ <command id="cmd_ConvertToTable" oncommand="goDoCommand('cmd_ConvertToTable')"/>
+ <command id="cmd_TableOrCellColor" oncommand="goDoCommand('cmd_TableOrCellColor')"/>
+ <command id="cmd_editTable" oncommand="goDoCommand('cmd_editTable')"/>
+ </commandset>
+
+ <commandset id="editorCommands">
+ <commandset id="globalEditMenuItems"/>
+ <commandset id="selectEditMenuItems"/>
+ <commandset id="undoEditMenuItems"/>
+ <commandset id="clipboardEditMenuItems"/>
+ <command id="toggleSidebar"/>
+ <!-- file menu -->
+ <command id="cmd_newNavigator"/>
+ <command id="cmd_newPrivateWindow"/>
+ <command id="cmd_newEditor"/>
+ <command id="cmd_newEditorTemplate"/>
+ <command id="cmd_newEditorDraft"/>
+ </commandset>
+
+ <popupset id="editorPopupSet">
+ <menupopup id="popupNotificationMenu"/>
+ <menupopup id="toolbar-context-menu"/>
+ <panel id="customizeToolbarSheetPopup"/>
+ </popupset>
+
+ <menu id="menu_Edit">
+ <menupopup id="menu_EditPopup">
+ <!-- from utilityOverlay.xhtml -->
+ <menuitem id="menu_undo"/>
+ <menuitem id="menu_redo"/>
+ <menuseparator id="sep_cut"/>
+ <menuitem id="menu_cut"/>
+ <menuitem id="menu_copy"/>
+ <menuitem id="menu_paste"/>
+ <menuitem id="menu_pasteNoFormatting"
+ key="pastenoformattingkb"
+ command="cmd_pasteNoFormatting"/>
+ <menuitem id="menu_pasteQuote"
+ key="pastequotationkb"
+ command="cmd_pasteQuote"/>
+ <menuitem id="menu_rewrap"
+ label="&editRewrapCmd.label;"
+ accesskey="&editRewrapCmd.accesskey;"
+ key="key_rewrap"
+ command="cmd_rewrap"/>
+ <menuitem id="menu_delete"/>
+ <menuseparator id="sep_selectAll"/>
+ <menuitem id="menu_selectAll"/>
+ <menuseparator id="sep_find"/>
+ <menuitem id="menu_find"
+ label="&findBarCmd.label;"/>
+ <menuitem id="menu_findReplace"
+ label="&findReplaceCmd.label;"/>
+ <menuitem id="menu_findNext"/>
+ <menuitem id="menu_findPrev"/>
+ <menuseparator id="sep_checkspelling"/>
+ <menuitem id="menu_checkspelling"
+ label="&checkSpellingCmd2.label;"
+ accesskey="&checkSpellingCmd2.accesskey;"
+ key="key_checkspelling"
+ command="cmd_spelling"/>
+ <menuitem id="menu_inlineSpellCheck"
+ type="checkbox"
+ label="&enableInlineSpellChecker.label;"
+ accesskey="&enableInlineSpellChecker.accesskey;"/>
+ <menuseparator id="sep_preferences"/>
+ <menuitem id="menu_preferences"
+ command="cmd_preferences"/>
+ </menupopup>
+ </menu>
+
+ <!-- Insert menu -->
+ <menu id="insertMenu"
+ label="&insertMenu.label;"
+ accesskey="&insertMenu.accesskey;">
+ <menupopup id="insertMenuPopup">
+ <menuitem id="insertImage"
+ label="&insertImageCmd.label;"
+ accesskey="&insertImageCmd.accesskey;"
+ command="cmd_image"/>
+ <menuitem id="insertTable"
+ label="&insertTableCmd.label;"
+ accesskey="&insertTableCmd.accesskey;"
+ command="cmd_InsertTable"/>
+ <menuitem id="insertLink"
+ label="&insertLinkCmd2.label;"
+ accesskey="&insertLinkCmd2.accesskey;"
+ command="cmd_link"
+ key="insertlinkkb"/>
+ <menuitem id="insertAnchor"
+ label="&insertAnchorCmd.label;"
+ accesskey="&insertAnchorCmd.accesskey;"
+ command="cmd_anchor"/>
+ <menuitem id="insertHline"
+ label="&insertHLineCmd.label;"
+ accesskey="&insertHLineCmd.accesskey;"
+ command="cmd_hline"/>
+ <menuitem id="insertHTMLSource"
+ accesskey="&insertHTMLCmd.accesskey;"
+ command="cmd_insertHTMLWithDialog"/>
+ <menuitem id="insertMath"
+ accesskey="&insertMathCmd.accesskey;"
+ command="cmd_insertMathWithDialog"/>
+ <menuitem id="insertChars"
+ accesskey="&insertCharsCmd.accesskey;"
+ command="cmd_insertChars"/>
+ <menu id="insertTOC" label="&tocMenu.label;" accesskey="&tocMenu.accesskey;">
+ <menupopup id="insertTOCPopup" onpopupshowing="InitTOCMenu()">
+ <menuitem id="insertTOCMenuitem"
+ label="&insertTOC.label;"
+ accesskey="&insertTOC.accesskey;"
+ oncommand="UpdateTOC()"/>
+ <menuitem id="updateTOCMenuitem"
+ label="&updateTOC.label;"
+ accesskey="&updateTOC.accesskey;"
+ oncommand="UpdateTOC()"/>
+ <menuitem id="removeTOCMenuitem"
+ label="&removeTOC.label;"
+ accesskey="&removeTOC.accesskey;"
+ oncommand="RemoveTOC()"/>
+ </menupopup>
+ </menu>
+ <menu id="insertSmiley"
+ label="&insertSmiley.label;"
+ accesskey="&insertSmiley.accesskey;">
+ <menupopup id="smilyMenuPopup">
+ <menuitem class="smiley insert-smile menuitem-iconic"
+ label="&smiley1Cmd.label;"
+ accesskey="&smiley1Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-)');"/>
+ <menuitem class="smiley insert-frown menuitem-iconic"
+ label="&smiley2Cmd.label;"
+ accesskey="&smiley2Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-(');"/>
+ <menuitem class="smiley insert-wink menuitem-iconic"
+ label="&smiley3Cmd.label;"
+ accesskey="&smiley3Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ';-)');"/>
+ <menuitem class="smiley insert-tongue menuitem-iconic"
+ label="&smiley4Cmd.label;"
+ accesskey="&smiley4Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-P');"/>
+ <menuitem class="smiley insert-laughing menuitem-iconic"
+ label="&smiley5Cmd.label;"
+ accesskey="&smiley5Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-D');"/>
+ <menuitem class="smiley insert-embarrassed menuitem-iconic"
+ label="&smiley6Cmd.label;"
+ accesskey="&smiley6Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-[');"/>
+ <menuitem class="smiley insert-undecided menuitem-iconic"
+ label="&smiley7Cmd.label;"
+ accesskey="&smiley7Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-\\');"/>
+ <menuitem class="smiley insert-surprise menuitem-iconic"
+ label="&smiley8Cmd.label;"
+ accesskey="&smiley8Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', '=-O');"/>
+ <menuitem class="smiley insert-kiss menuitem-iconic"
+ label="&smiley9Cmd.label;"
+ accesskey="&smiley9Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-*');"/>
+ <menuitem class="smiley insert-yell menuitem-iconic"
+ label="&smiley10Cmd.label;"
+ accesskey="&smiley10Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', '>:o');"/>
+ <menuitem class="smiley insert-cool menuitem-iconic"
+ label="&smiley11Cmd.label;"
+ accesskey="&smiley11Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', '8-)');"/>
+ <menuitem class="smiley insert-money menuitem-iconic"
+ label="&smiley12Cmd.label;"
+ accesskey="&smiley12Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-$');"/>
+ <menuitem class="smiley insert-foot menuitem-iconic"
+ label="&smiley13Cmd.label;"
+ accesskey="&smiley13Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-!');"/>
+ <menuitem class="smiley insert-innocent menuitem-iconic"
+ label="&smiley14Cmd.label;"
+ accesskey="&smiley14Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', 'O:-)');"/>
+ <menuitem class="smiley insert-cry menuitem-iconic"
+ label="&smiley15Cmd.label;"
+ accesskey="&smiley15Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':\'(');"/>
+ <menuitem class="smiley insert-sealed menuitem-iconic"
+ label="&smiley16Cmd.label;"
+ accesskey="&smiley16Cmd.accesskey;"
+ oncommand="doStatefulCommand('cmd_smiley', ':-X');"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="insertMenuSeparator"/>
+ <menuitem id="insertBreakAll"
+ accesskey="&insertBreakAllCmd.accesskey;"
+ command="cmd_insertBreakAll"
+ label="&insertBreakAllCmd.label;"/>
+ </menupopup>
+ </menu>
+
+ <!-- Format Menu -->
+ <menupopup id="formatMenuPopup" onpopupshowing="EditorInitFormatMenu()">
+ <!-- Font face submenu -->
+ <menu id="fontFaceMenu"
+ label="&fontfaceMenu.label;"
+ accesskey="&fontfaceMenu.accesskey;"
+ position="1">
+ <menupopup id="fontFaceMenuPopup"
+ oncommand="if (event.target.localName == 'menuitem')
+ doStatefulCommand('cmd_fontFace', event.target.getAttribute('value'));"
+ onpopupshowing="initFontFaceMenu(this);">
+ <menuitem id="menu_fontFaceVarWidth"
+ label="&fontVarWidth.label;"
+ accesskey="&fontVarWidth.accesskey;"
+ value=""
+ type="radio"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_fontFaceFixedWidth"
+ label="&fontFixedWidth.label;"
+ accesskey="&fontFixedWidth.accesskey;"
+ value="tt"
+ type="radio"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuseparator id="fontFaceMenuAfterGenericFontsSeparator"/>
+ <menuitem id="menu_fontFaceHelvetica"
+ label="&fontHelvetica.label;"
+ accesskey="&fontHelvetica.accesskey;"
+ value="Helvetica, Arial, sans-serif"
+ value_parsed="helvetica,arial,sans-serif"
+ type="radio"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_fontFaceTimes"
+ label="&fontTimes.label;"
+ accesskey="&fontTimes.accesskey;"
+ value="Times New Roman, Times, serif"
+ value_parsed="times new roman,times,serif"
+ type="radio"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_fontFaceCourier"
+ label="&fontCourier.label;"
+ accesskey="&fontCourier.accesskey;"
+ value="Courier New, Courier, monospace"
+ value_parsed="courier new,courier,monospace"
+ type="radio"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuseparator id="fontFaceMenuAfterDefaultFontsSeparator"
+ class="fontFaceMenuAfterDefaultFonts"/>
+ <menuseparator id="fontFaceMenuAfterUsedFontsSeparator"
+ class="fontFaceMenuAfterUsedFonts"
+ collapsed="true"/>
+ <!-- Local font face items added here by initLocalFontFaceMenu() -->
+ </menupopup>
+ </menu>
+
+ <!-- Font size submenu -->
+ <menu id="fontSizeMenu" label="&fontSizeMenu.label;"
+ accesskey="&fontSizeMenu.accesskey;"
+ position="2">
+ <menupopup id="fontSizeMenuPopup" onpopupshowing="initFontSizeMenu(this, true)">
+ <menuitem id="menu_decreaseFontSize"
+ label="&decreaseFontSize.label;"
+ accesskey="&decreaseFontSize.accesskey;"
+ command="cmd_decreaseFontStep"
+ type="radio" name="1" autocheck="false"
+ key="decreasefontsizekb"/>
+ <menuitem id="menu_increaseFontSize"
+ label="&increaseFontSize.label;"
+ accesskey="&increaseFontSize.accesskey;"
+ command="cmd_increaseFontStep"
+ type="radio" name="1" autocheck="false"
+ key="increasefontsizekb"/>
+ <menuseparator id="fontSizeMenuAfterIncreaseFontSizeSeparator"/>
+ <menuitem id="menu_x-small"
+ label="&size-tinyCmd.label;"
+ accesskey="&size-tinyCmd.accesskey;"
+ oncommand="EditorSetFontSize('x-small')"
+ type="radio" name="1"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_size-small"
+ label="&size-smallCmd.label;"
+ accesskey="&size-smallCmd.accesskey;"
+ oncommand="EditorSetFontSize('small')"
+ type="radio" name="1"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_size-medium"
+ label="&size-mediumCmd.label;"
+ accesskey="&size-mediumCmd.accesskey;"
+ oncommand="EditorSetFontSize('medium')"
+ type="radio" name="1"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_size-large"
+ label="&size-largeCmd.label;"
+ accesskey="&size-largeCmd.accesskey;"
+ oncommand="EditorSetFontSize('large')"
+ type="radio" name="1"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_size-x-large"
+ label="&size-extraLargeCmd.label;"
+ accesskey="&size-extraLargeCmd.accesskey;"
+ oncommand="EditorSetFontSize('x-large')"
+ type="radio" name="1"
+ observes="cmd_renderedHTMLEnabler"/>
+ <menuitem id="menu_size-xx-large"
+ label="&size-hugeCmd.label;"
+ accesskey="&size-hugeCmd.accesskey;"
+ oncommand="EditorSetFontSize('xx-large')"
+ type="radio" name="1"
+ observes="cmd_renderedHTMLEnabler"/>
+ <!-- Enable if required for SeaMonkey.
+ <menuitem id="fontSizeMenu_smallBigInfo"
+ type="checkbox" name="2" disabled="true" hidden="true"/>
+ -->
+ </menupopup>
+ </menu>
+
+ <!-- Font style submenu -->
+ <menu id="fontStyleMenu" label="&fontStyleMenu.label;"
+ accesskey="&fontStyleMenu.accesskey;"
+ position="3">
+ <menupopup id="fontStyleMenuPopup" onpopupshowing="initFontStyleMenu(this)">
+ <menuitem id="menu_styleBold"
+ label="&styleBoldCmd.label;"
+ accesskey="&styleBoldCmd.accesskey;"
+ observes="cmd_bold"
+ type="checkbox"
+ key="boldkb"/>
+ <menuitem id="menu_styleItalic"
+ label="&styleItalicCmd.label;"
+ accesskey="&styleItalicCmd.accesskey;"
+ observes="cmd_italic"
+ type="checkbox"
+ key="italickb"/>
+ <menuitem id="menu_styleUnderline"
+ label="&styleUnderlineCmd.label;"
+ accesskey="&styleUnderlineCmd.accesskey;"
+ observes="cmd_underline"
+ type="checkbox"
+ key="underlinekb"/>
+ <menuitem id="menu_styleStrikeThru"
+ label="&styleStrikeThruCmd.label;"
+ accesskey="&styleStrikeThruCmd.accesskey;"
+ observes="cmd_strikethrough"
+ type="checkbox"/>
+ <menuitem id="menu_styleSuperscript"
+ label="&styleSuperscriptCmd.label;"
+ accesskey="&styleSuperscriptCmd.accesskey;"
+ observes="cmd_superscript"
+ type="checkbox"/>
+ <menuitem id="menu_styleSubscript"
+ label="&styleSubscriptCmd.label;"
+ accesskey="&styleSubscriptCmd.accesskey;"
+ observes="cmd_subscript"
+ type="checkbox"/>
+ <menuitem id="menu_fontFixedWidth"
+ label="&fontFixedWidth.label;"
+ accesskey="&fontFixedWidth.accesskey;"
+ observes="cmd_tt"
+ type="checkbox"
+ key="fixedwidthkb"/>
+ <menuitem id="menu_styleNonbreaking"
+ label="&styleNonbreakingCmd.label;"
+ accesskey="&styleNonbreakingCmd.accesskey;"
+ observes="cmd_nobreak"
+ type="checkbox"/>
+ <menuseparator id="fontStyleMenuAfterNonbreakingSeparator"/>
+ <menuitem id="menu_styleEm"
+ label="&styleEm.label;"
+ accesskey="&styleEm.accesskey;"
+ observes="cmd_em"
+ type="checkbox"/>
+ <menuitem id="menu_styleStrong"
+ label="&styleStrong.label;"
+ accesskey="&styleStrong.accesskey;"
+ observes="cmd_strong"
+ type="checkbox"/>
+ <menuitem id="menu_styleCite"
+ label="&styleCite.label;"
+ accesskey="&styleCite.accesskey;"
+ observes="cmd_cite"
+ type="checkbox"/>
+ <menuitem id="menu_styleAbbr"
+ label="&styleAbbr.label;"
+ accesskey="&styleAbbr.accesskey;"
+ observes="cmd_abbr"
+ type="checkbox"/>
+ <menuitem id="menu_styleAcronym"
+ label="&styleAcronym.label;"
+ accesskey="&styleAcronym.accesskey;"
+ observes="cmd_acronym"
+ type="checkbox"/>
+ <menuitem id="menu_styleCode"
+ label="&styleCode.label;"
+ accesskey="&styleCode.accesskey;"
+ observes="cmd_code"
+ type="checkbox"/>
+ <menuitem id="menu_styleSamp"
+ label="&styleSamp.label;"
+ accesskey="&styleSamp.accesskey;"
+ observes="cmd_samp"
+ type="checkbox"/>
+ <menuitem id="menu_styleVar"
+ label="&styleVar.label;"
+ accesskey="&styleVar.accesskey;"
+ observes="cmd_var"
+ type="checkbox"/>
+ </menupopup>
+ </menu>
+
+ <!-- Note: "cmd_fontColor" only monitors color state, it doesn't execute the command
+ (We should use "cmd_fontColorState" and "cmd_backgroundColorState" ?) -->
+ <menuitem id="fontColor"
+ label="&formatFontColor.label;"
+ accesskey="&formatFontColor.accesskey;"
+ observes="cmd_fontColor"
+ oncommand="EditorSelectColor('Text', null);"
+ position="4"/>
+ <menuseparator id="removeSep" position="5"/>
+
+ <!-- label and accesskey set at runtime from strings -->
+ <menuitem id="removeStylesMenuitem"
+ key="removestyleskb"
+ observes="cmd_removeStyles"
+ position="6"/>
+ <menuitem id="removeLinksMenuitem"
+ key="removelinkskb"
+ observes="cmd_removeLinks"
+ position="7"/>
+ <menuitem id="removeNamedAnchorsMenuitem"
+ label="&formatRemoveNamedAnchors.label;"
+ key="removenamedanchorskb"
+ accesskey="&formatRemoveNamedAnchors.accesskey;"
+ observes="cmd_removeNamedAnchors"
+ position="8"/>
+ <menuseparator id="tabSep" position="9"/>
+
+ <!-- Note: the 'Init' menu methods for Paragraph, List, and Align
+ assume that the id = 'menu_'+tagName (the 'value' label),
+ except for the first ('none') item
+ -->
+ <!-- Paragraph Style submenu -->
+ <menu id="paragraphMenu" label="&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"]
diff --git a/comm/suite/editor/components/dialogs/content/EdAEAttributes.js b/comm/suite/editor/components/dialogs/content/EdAEAttributes.js
new file mode 100644
index 0000000000..52b7e30fac
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdAEAttributes.js
@@ -0,0 +1,973 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HTML Attributes object for "Name" menulist
+var gHTMLAttr = {};
+
+// JS Events Attributes object for "Name" menulist
+var gJSAttr = {};
+
+// Core HTML attribute values //
+// This is appended to Name menulist when "_core" is attribute name
+var gCoreHTMLAttr = ["^id", "class", "title"];
+
+// Core event attribute values //
+// This is appended to all JS menulists
+// except those elements having "noJSEvents"
+// as a value in their gJSAttr array.
+var gCoreJSEvents = [
+ "onclick",
+ "ondblclick",
+ "onmousedown",
+ "onmouseup",
+ "onmouseover",
+ "onmousemove",
+ "onmouseout",
+ "-",
+ "onkeypress",
+ "onkeydown",
+ "onkeyup",
+];
+
+// Following are commonly-used strings
+
+// Also accept: sRGB: #RRGGBB //
+var gHTMLColors = [
+ "Aqua",
+ "Black",
+ "Blue",
+ "Fuchsia",
+ "Gray",
+ "Green",
+ "Lime",
+ "Maroon",
+ "Navy",
+ "Olive",
+ "Purple",
+ "Red",
+ "Silver",
+ "Teal",
+ "White",
+ "Yellow",
+];
+
+var gHAlign = ["left", "center", "right"];
+
+var gHAlignJustify = ["left", "center", "right", "justify"];
+
+var gHAlignTableContent = ["left", "center", "right", "justify", "char"];
+
+var gVAlignTable = ["top", "middle", "bottom", "baseline"];
+
+var gTarget = ["_blank", "_self", "_parent", "_top"];
+
+// ================ HTML Attributes ================ //
+/* For each element, there is an array of attributes,
+ whose name is the element name,
+ used to fill the "Attribute Name" menulist.
+ For each of those attributes, if they have a specific
+ set of values, those are listed in an array named:
+ "elementName_attName".
+
+ In each values string, the following characters
+ are signal to do input filtering:
+ "#" Allow only integer values
+ "%" Allow integer values or a number ending in "%"
+ "+" Allow integer values and allow "+" or "-" as first character
+ "!" Allow only one character
+ "^" The first character can be only be A-Z, a-z, hyphen, underscore, colon or period
+ "$" is an attribute required by HTML DTD
+*/
+
+/*
+ Most elements have the "dir" attribute,
+ so we use this value array
+ for all elements instead of specifying
+ separately for each element
+*/
+gHTMLAttr.all_dir = ["ltr", "rtl"];
+
+gHTMLAttr.a = [
+ "charset",
+ "type",
+ "name",
+ "href",
+ "^hreflang",
+ "target",
+ "rel",
+ "rev",
+ "!accesskey",
+ "shape", // with imagemap //
+ "coords", // with imagemap //
+ "#tabindex",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.a_target = gTarget;
+
+gHTMLAttr.a_rel = [
+ "alternate",
+ "stylesheet",
+ "start",
+ "next",
+ "prev",
+ "contents",
+ "index",
+ "glossary",
+ "copyright",
+ "chapter",
+ "section",
+ "subsection",
+ "appendix",
+ "help",
+ "bookmark",
+];
+
+gHTMLAttr.a_rev = [
+ "alternate",
+ "stylesheet",
+ "start",
+ "next",
+ "prev",
+ "contents",
+ "index",
+ "glossary",
+ "copyright",
+ "chapter",
+ "section",
+ "subsection",
+ "appendix",
+ "help",
+ "bookmark",
+];
+
+gHTMLAttr.a_shape = ["rect", "circle", "poly", "default"];
+
+gHTMLAttr.abbr = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.acronym = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.address = ["_core", "-", "^lang", "dir"];
+
+// this is deprecated //
+gHTMLAttr.applet = [
+ "codebase",
+ "archive",
+ "code",
+ "object",
+ "alt",
+ "name",
+ "%$width",
+ "%$height",
+ "align",
+ "#hspace",
+ "#vspace",
+ "-",
+ "_core",
+];
+
+gHTMLAttr.applet_align = ["top", "middle", "bottom", "left", "right"];
+
+gHTMLAttr.area = [
+ "shape",
+ "coords",
+ "href",
+ "nohref",
+ "target",
+ "$alt",
+ "#tabindex",
+ "!accesskey",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.area_target = gTarget;
+
+gHTMLAttr.area_shape = ["rect", "circle", "poly", "default"];
+
+gHTMLAttr.area_nohref = ["nohref"];
+
+gHTMLAttr.b = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.base = ["href", "target"];
+
+gHTMLAttr.base_target = gTarget;
+
+// this is deprecated //
+gHTMLAttr.basefont = ["^id", "$size", "color", "face"];
+
+gHTMLAttr.basefont_color = gHTMLColors;
+
+gHTMLAttr.bdo = ["_core", "-", "^lang", "$dir"];
+
+gHTMLAttr.bdo_dir = ["ltr", "rtl"];
+
+gHTMLAttr.big = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.blockquote = ["cite", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.body = [
+ "background",
+ "bgcolor",
+ "text",
+ "link",
+ "vlink",
+ "alink",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.body_bgcolor = gHTMLColors;
+
+gHTMLAttr.body_text = gHTMLColors;
+
+gHTMLAttr.body_link = gHTMLColors;
+
+gHTMLAttr.body_vlink = gHTMLColors;
+
+gHTMLAttr.body_alink = gHTMLColors;
+
+gHTMLAttr.br = ["clear", "-", "_core"];
+
+gHTMLAttr.br_clear = ["none", "left", "all", "right"];
+
+gHTMLAttr.button = [
+ "name",
+ "value",
+ "$type",
+ "disabled",
+ "#tabindex",
+ "!accesskey",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.button_type = ["submit", "button", "reset"];
+
+gHTMLAttr.button_disabled = ["disabled"];
+
+gHTMLAttr.caption = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.caption_align = ["top", "bottom", "left", "right"];
+
+// this is deprecated //
+gHTMLAttr.center = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.cite = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.code = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.col = [
+ "#$span",
+ "%width",
+ "align",
+ "!char",
+ "#charoff",
+ "valign",
+ "char",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.col_span = [
+ "1", // default
+];
+
+gHTMLAttr.col_align = gHAlignTableContent;
+
+gHTMLAttr.col_valign = ["top", "middle", "bottom", "baseline"];
+
+gHTMLAttr.colgroup = [
+ "#$span",
+ "%width",
+ "align",
+ "!char",
+ "#charoff",
+ "valign",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.colgroup_span = [
+ "1", // default
+];
+
+gHTMLAttr.colgroup_align = gHAlignTableContent;
+
+gHTMLAttr.colgroup_valign = ["top", "middle", "bottom", "baseline"];
+
+gHTMLAttr.dd = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.del = ["cite", "datetime", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.dfn = ["_core", "-", "^lang", "dir"];
+
+// this is deprecated //
+gHTMLAttr.dir = ["compact", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.dir_compact = ["compact"];
+
+gHTMLAttr.div = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.div_align = gHAlignJustify;
+
+gHTMLAttr.dl = ["compact", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.dl_compact = ["compact"];
+
+gHTMLAttr.dt = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.em = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.fieldset = ["_core", "-", "^lang", "dir"];
+
+// this is deprecated //
+gHTMLAttr.font = ["+size", "color", "face", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.font_color = gHTMLColors;
+
+gHTMLAttr.form = [
+ "$action",
+ "$method",
+ "enctype",
+ "accept",
+ "name",
+ "accept-charset",
+ "target",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.form_method = ["get", "post"];
+
+gHTMLAttr.form_enctype = ["application/x-www-form-urlencoded"];
+
+gHTMLAttr.form_target = gTarget;
+
+gHTMLAttr.frame = [
+ "longdesc",
+ "name",
+ "src",
+ "#frameborder",
+ "#marginwidth",
+ "#marginheight",
+ "noresize",
+ "$scrolling",
+];
+
+gHTMLAttr.frame_frameborder = ["1", "0"];
+
+gHTMLAttr.frame_noresize = ["noresize"];
+
+gHTMLAttr.frame_scrolling = ["auto", "yes", "no"];
+
+gHTMLAttr.frameset = ["rows", "cols", "-", "_core"];
+
+gHTMLAttr.h1 = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.h1_align = gHAlignJustify;
+
+gHTMLAttr.h2 = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.h2_align = gHAlignJustify;
+
+gHTMLAttr.h3 = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.h3_align = gHAlignJustify;
+
+gHTMLAttr.h4 = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.h4_align = gHAlignJustify;
+
+gHTMLAttr.h5 = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.h5_align = gHAlignJustify;
+
+gHTMLAttr.h6 = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.h6_align = gHAlignJustify;
+
+gHTMLAttr.head = ["profile", "-", "^lang", "dir"];
+
+gHTMLAttr.hr = [
+ "align",
+ "noshade",
+ "#size",
+ "%width",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.hr_align = gHAlign;
+
+gHTMLAttr.hr_noshade = ["noshade"];
+
+gHTMLAttr.html = ["version", "-", "^lang", "dir"];
+
+gHTMLAttr.i = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.iframe = [
+ "longdesc",
+ "name",
+ "src",
+ "$frameborder",
+ "marginwidth",
+ "marginheight",
+ "$scrolling",
+ "align",
+ "%height",
+ "%width",
+ "-",
+ "_core",
+];
+
+gHTMLAttr.iframe_frameborder = ["1", "0"];
+
+gHTMLAttr.iframe_scrolling = ["auto", "yes", "no"];
+
+gHTMLAttr.iframe_align = ["top", "middle", "bottom", "left", "right"];
+
+gHTMLAttr.img = [
+ "$src",
+ "$alt",
+ "longdesc",
+ "name",
+ "%height",
+ "%width",
+ "usemap",
+ "ismap",
+ "align",
+ "#border",
+ "#hspace",
+ "#vspace",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.img_ismap = ["ismap"];
+
+gHTMLAttr.img_align = ["top", "middle", "bottom", "left", "right"];
+
+gHTMLAttr.input = [
+ "$type",
+ "name",
+ "value",
+ "checked",
+ "disabled",
+ "readonly",
+ "#size",
+ "#maxlength",
+ "src",
+ "alt",
+ "usemap",
+ "ismap",
+ "#tabindex",
+ "!accesskey",
+ "accept",
+ "align",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.input_type = [
+ "text",
+ "password",
+ "checkbox",
+ "radio",
+ "submit",
+ "reset",
+ "file",
+ "hidden",
+ "image",
+ "button",
+];
+
+gHTMLAttr.input_checked = ["checked"];
+
+gHTMLAttr.input_disabled = ["disabled"];
+
+gHTMLAttr.input_readonly = ["readonly"];
+
+gHTMLAttr.input_ismap = ["ismap"];
+
+gHTMLAttr.input_align = ["top", "middle", "bottom", "left", "right"];
+
+gHTMLAttr.ins = ["cite", "datetime", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.isindex = ["prompt", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.kbd = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.label = ["for", "!accesskey", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.legend = ["!accesskey", "align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.legend_align = ["top", "bottom", "left", "right"];
+
+gHTMLAttr.li = ["type", "#value", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.li_type = ["disc", "square", "circle", "-", "1", "a", "A", "i", "I"];
+
+gHTMLAttr.link = [
+ "charset",
+ "href",
+ "^hreflang",
+ "type",
+ "rel",
+ "rev",
+ "media",
+ "target",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.link_target = gTarget;
+
+gHTMLAttr.link_rel = [
+ "alternate",
+ "stylesheet",
+ "start",
+ "next",
+ "prev",
+ "contents",
+ "index",
+ "glossary",
+ "copyright",
+ "chapter",
+ "section",
+ "subsection",
+ "appendix",
+ "help",
+ "bookmark",
+];
+
+gHTMLAttr.link_rev = [
+ "alternate",
+ "stylesheet",
+ "start",
+ "next",
+ "prev",
+ "contents",
+ "index",
+ "glossary",
+ "copyright",
+ "chapter",
+ "section",
+ "subsection",
+ "appendix",
+ "help",
+ "bookmark",
+];
+
+gHTMLAttr.map = ["$name", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.menu = ["compact", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.menu_compact = ["compact"];
+
+gHTMLAttr.meta = [
+ "http-equiv",
+ "name",
+ "$content",
+ "scheme",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.noframes = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.noscript = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.object = [
+ "declare",
+ "classid",
+ "codebase",
+ "data",
+ "type",
+ "codetype",
+ "archive",
+ "standby",
+ "%height",
+ "%width",
+ "usemap",
+ "name",
+ "#tabindex",
+ "align",
+ "#border",
+ "#hspace",
+ "#vspace",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.object_declare = ["declare"];
+
+gHTMLAttr.object_align = ["top", "middle", "bottom", "left", "right"];
+
+gHTMLAttr.ol = ["type", "compact", "#start", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.ol_type = ["1", "a", "A", "i", "I"];
+
+gHTMLAttr.ol_compact = ["compact"];
+
+gHTMLAttr.optgroup = ["disabled", "$label", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.optgroup_disabled = ["disabled"];
+
+gHTMLAttr.option = [
+ "selected",
+ "disabled",
+ "label",
+ "value",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.option_selected = ["selected"];
+
+gHTMLAttr.option_disabled = ["disabled"];
+
+gHTMLAttr.p = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.p_align = gHAlignJustify;
+
+gHTMLAttr.param = ["^id", "$name", "value", "$valuetype", "type"];
+
+gHTMLAttr.param_valuetype = ["data", "ref", "object"];
+
+gHTMLAttr.pre = ["%width", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.q = ["cite", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.s = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.samp = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.script = ["charset", "$type", "language", "src", "defer"];
+
+gHTMLAttr.script_defer = ["defer"];
+
+gHTMLAttr.select = [
+ "name",
+ "#size",
+ "multiple",
+ "disabled",
+ "#tabindex",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.select_multiple = ["multiple"];
+
+gHTMLAttr.select_disabled = ["disabled"];
+
+gHTMLAttr.small = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.span = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.strike = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.strong = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.style = ["$type", "media", "title", "-", "^lang", "dir"];
+
+gHTMLAttr.sub = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.sup = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.table = [
+ "summary",
+ "%width",
+ "#border",
+ "frame",
+ "rules",
+ "#cellspacing",
+ "#cellpadding",
+ "align",
+ "bgcolor",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.table_frame = [
+ "void",
+ "above",
+ "below",
+ "hsides",
+ "lhs",
+ "rhs",
+ "vsides",
+ "box",
+ "border",
+];
+
+gHTMLAttr.table_rules = ["none", "groups", "rows", "cols", "all"];
+
+// Note; This is alignment of the table,
+// not table contents, like all other table child elements
+gHTMLAttr.table_align = gHAlign;
+
+gHTMLAttr.table_bgcolor = gHTMLColors;
+
+gHTMLAttr.tbody = [
+ "align",
+ "!char",
+ "#charoff",
+ "valign",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.tbody_align = gHAlignTableContent;
+
+gHTMLAttr.tbody_valign = gVAlignTable;
+
+gHTMLAttr.td = [
+ "abbr",
+ "axis",
+ "headers",
+ "scope",
+ "$#rowspan",
+ "$#colspan",
+ "align",
+ "!char",
+ "#charoff",
+ "valign",
+ "nowrap",
+ "bgcolor",
+ "%width",
+ "%height",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.td_scope = ["row", "col", "rowgroup", "colgroup"];
+
+gHTMLAttr.td_rowspan = [
+ "1", // default
+];
+
+gHTMLAttr.td_colspan = [
+ "1", // default
+];
+
+gHTMLAttr.td_align = gHAlignTableContent;
+
+gHTMLAttr.td_valign = gVAlignTable;
+
+gHTMLAttr.td_nowrap = ["nowrap"];
+
+gHTMLAttr.td_bgcolor = gHTMLColors;
+
+gHTMLAttr.textarea = [
+ "name",
+ "$#rows",
+ "$#cols",
+ "disabled",
+ "readonly",
+ "#tabindex",
+ "!accesskey",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.textarea_disabled = ["disabled"];
+
+gHTMLAttr.textarea_readonly = ["readonly"];
+
+gHTMLAttr.tfoot = [
+ "align",
+ "!char",
+ "#charoff",
+ "valign",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.tfoot_align = gHAlignTableContent;
+
+gHTMLAttr.tfoot_valign = gVAlignTable;
+
+gHTMLAttr.th = [
+ "abbr",
+ "axis",
+ "headers",
+ "scope",
+ "$#rowspan",
+ "$#colspan",
+ "align",
+ "!char",
+ "#charoff",
+ "valign",
+ "nowrap",
+ "bgcolor",
+ "%width",
+ "%height",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.th_scope = ["row", "col", "rowgroup", "colgroup"];
+
+gHTMLAttr.th_rowspan = [
+ "1", // default
+];
+
+gHTMLAttr.th_colspan = [
+ "1", // default
+];
+
+gHTMLAttr.th_align = gHAlignTableContent;
+
+gHTMLAttr.th_valign = gVAlignTable;
+
+gHTMLAttr.th_nowrap = ["nowrap"];
+
+gHTMLAttr.th_bgcolor = gHTMLColors;
+
+gHTMLAttr.thead = [
+ "align",
+ "!char",
+ "#charoff",
+ "valign",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.thead_align = gHAlignTableContent;
+
+gHTMLAttr.thead_valign = gVAlignTable;
+
+gHTMLAttr.title = ["^lang", "dir"];
+
+gHTMLAttr.tr = [
+ "align",
+ "!char",
+ "#charoff",
+ "valign",
+ "bgcolor",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.tr_align = gHAlignTableContent;
+
+gHTMLAttr.tr_valign = gVAlignTable;
+
+gHTMLAttr.tr_bgcolor = gHTMLColors;
+
+gHTMLAttr.tt = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.u = ["_core", "-", "^lang", "dir"];
+gHTMLAttr.ul = ["type", "compact", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.ul_type = ["disc", "square", "circle"];
+
+gHTMLAttr.ul_compact = ["compact"];
+
+// Prefix with "_" since this is reserved (it's stripped out)
+gHTMLAttr._var = ["_core", "-", "^lang", "dir"];
+
+// ================ JS Attributes ================ //
+// These are element specific even handlers.
+/* Most all elements use gCoreJSEvents, so those
+ are assumed except for those listed here with "noEvents"
+*/
+
+gJSAttr.a = ["onfocus", "onblur"];
+
+gJSAttr.area = ["onfocus", "onblur"];
+
+gJSAttr.body = ["onload", "onupload"];
+
+gJSAttr.button = ["onfocus", "onblur"];
+
+gJSAttr.form = ["onsubmit", "onreset"];
+
+gJSAttr.frameset = ["onload", "onunload"];
+
+gJSAttr.input = ["onfocus", "onblur", "onselect", "onchange"];
+
+gJSAttr.label = ["onfocus", "onblur"];
+
+gJSAttr.select = ["onfocus", "onblur", "onchange"];
+
+gJSAttr.textarea = ["onfocus", "onblur", "onselect", "onchange"];
+
+// Elements that don't have JSEvents:
+gJSAttr.font = ["noJSEvents"];
+
+gJSAttr.applet = ["noJSEvents"];
+
+gJSAttr.isindex = ["noJSEvents"];
+
+gJSAttr.iframe = ["noJSEvents"];
diff --git a/comm/suite/editor/components/dialogs/content/EdAECSSAttributes.js b/comm/suite/editor/components/dialogs/content/EdAECSSAttributes.js
new file mode 100644
index 0000000000..977068bd70
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdAECSSAttributes.js
@@ -0,0 +1,146 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdAdvancedEdit.js */
+/* import-globals-from EdDialogCommon.js */
+
+// build attribute list in tree form from element attributes
+function BuildCSSAttributeTable() {
+ var style = gElement.style;
+ if (style == undefined) {
+ dump("Inline styles undefined\n");
+ return;
+ }
+
+ var declLength = style.length;
+
+ if (declLength == undefined || declLength == 0) {
+ if (declLength == undefined) {
+ dump("Failed to query the number of inline style declarations\n");
+ }
+
+ return;
+ }
+
+ if (declLength > 0) {
+ for (var i = 0; i < declLength; ++i) {
+ var name = style.item(i);
+ var value = style.getPropertyValue(name);
+ AddTreeItem(name, value, "CSSAList", CSSAttrs);
+ }
+ }
+
+ ClearCSSInputWidgets();
+}
+
+function onChangeCSSAttribute() {
+ var name = TrimString(gDialog.AddCSSAttributeNameInput.value);
+ if (!name) {
+ return;
+ }
+
+ var value = TrimString(gDialog.AddCSSAttributeValueInput.value);
+
+ // First try to update existing attribute
+ // If not found, add new attribute
+ if (!UpdateExistingAttribute(name, value, "CSSAList") && value) {
+ AddTreeItem(name, value, "CSSAList", CSSAttrs);
+ }
+}
+
+function ClearCSSInputWidgets() {
+ gDialog.AddCSSAttributeTree.view.selection.clearSelection();
+ gDialog.AddCSSAttributeNameInput.value = "";
+ gDialog.AddCSSAttributeValueInput.value = "";
+ SetTextboxFocus(gDialog.AddCSSAttributeNameInput);
+}
+
+function onSelectCSSTreeItem() {
+ if (!gDoOnSelectTree) {
+ return;
+ }
+
+ var tree = gDialog.AddCSSAttributeTree;
+ if (tree && tree.view.selection.count) {
+ gDialog.AddCSSAttributeNameInput.value = GetTreeItemAttributeStr(
+ getSelectedItem(tree)
+ );
+ gDialog.AddCSSAttributeValueInput.value = GetTreeItemValueStr(
+ getSelectedItem(tree)
+ );
+ }
+}
+
+function onInputCSSAttributeName() {
+ var attName = TrimString(
+ gDialog.AddCSSAttributeNameInput.value
+ ).toLowerCase();
+ var newValue = "";
+
+ var existingValue = GetAndSelectExistingAttributeValue(attName, "CSSAList");
+ if (existingValue) {
+ newValue = existingValue;
+ }
+
+ gDialog.AddCSSAttributeValueInput.value = newValue;
+}
+
+function editCSSAttributeValue(targetCell) {
+ if (IsNotTreeHeader(targetCell)) {
+ gDialog.AddCSSAttributeValueInput.inputField.select();
+ }
+}
+
+function UpdateCSSAttributes() {
+ var CSSAList = document.getElementById("CSSAList");
+ var styleString = "";
+ for (var i = 0; i < CSSAList.childNodes.length; i++) {
+ var item = CSSAList.childNodes[i];
+ var name = GetTreeItemAttributeStr(item);
+ var value = GetTreeItemValueStr(item);
+ // this code allows users to be sloppy in typing in values, and enter
+ // things like "foo: " and "bar;". This will trim off everything after the
+ // respective character.
+ if (name.includes(":")) {
+ name = name.substring(0, name.lastIndexOf(":"));
+ }
+ if (value.includes(";")) {
+ value = value.substring(0, value.lastIndexOf(";"));
+ }
+ if (i == CSSAList.childNodes.length - 1) {
+ // Last property.
+ styleString += name + ": " + value + ";";
+ } else {
+ styleString += name + ": " + value + "; ";
+ }
+ }
+ if (styleString) {
+ // Use editor transactions if modifying the element directly in the document
+ doRemoveAttribute("style");
+ doSetAttribute("style", styleString); // NOTE BUG 18894!!!
+ } else if (gElement.getAttribute("style")) {
+ doRemoveAttribute("style");
+ }
+}
+
+function RemoveCSSAttribute() {
+ // We only allow 1 selected item
+ if (gDialog.AddCSSAttributeTree.view.selection.count) {
+ // Remove the item from the tree
+ // We always rebuild complete "style" string,
+ // so no list of "removed" items
+ getSelectedItem(gDialog.AddCSSAttributeTree).remove();
+
+ ClearCSSInputWidgets();
+ }
+}
+
+function SelectCSSTree(index) {
+ gDoOnSelectTree = false;
+ try {
+ gDialog.AddCSSAttributeTree.selectedIndex = index;
+ } catch (e) {}
+ gDoOnSelectTree = true;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdAEHTMLAttributes.js b/comm/suite/editor/components/dialogs/content/EdAEHTMLAttributes.js
new file mode 100644
index 0000000000..1f96762754
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdAEHTMLAttributes.js
@@ -0,0 +1,367 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdAdvancedEdit.js */
+/* import-globals-from EdDialogCommon.js */
+
+function BuildHTMLAttributeNameList() {
+ gDialog.AddHTMLAttributeNameInput.removeAllItems();
+
+ var elementName = gElement.localName;
+ var attNames = gHTMLAttr[elementName];
+
+ if (attNames && attNames.length) {
+ var menuitem;
+
+ for (var i = 0; i < attNames.length; i++) {
+ var name = attNames[i];
+
+ if (name == "_core") {
+ // Signal to append the common 'core' attributes.
+ for (var j = 0; j < gCoreHTMLAttr.length; j++) {
+ name = gCoreHTMLAttr[j];
+
+ // only filtering rule used for core attributes as of 8-20-01
+ // Add more rules if necessary.
+ if (name.includes("^")) {
+ name = name.replace(/\^/g, "");
+ menuitem = gDialog.AddHTMLAttributeNameInput.appendItem(name, name);
+ menuitem.setAttribute("limitFirstChar", "true");
+ } else {
+ gDialog.AddHTMLAttributeNameInput.appendItem(name, name);
+ }
+ }
+ } else if (name == "-") {
+ // Signal for separator
+ var popup = gDialog.AddHTMLAttributeNameInput.menupopup;
+ if (popup) {
+ var sep = document.createXULElement("menuseparator");
+ if (sep) {
+ popup.appendChild(sep);
+ }
+ }
+ } else {
+ // Get information about value filtering
+ let forceOneChar = name.includes("!");
+ let forceInteger = name.includes("#");
+ let forceSignedInteger = name.includes("+");
+ let forceIntOrPercent = name.includes("%");
+ let limitFirstChar = name.includes("^");
+ // let required = name.includes("$");
+
+ // Strip flag characters
+ name = name.replace(/[!^#%$+]/g, "");
+
+ menuitem = gDialog.AddHTMLAttributeNameInput.appendItem(name, name);
+ if (menuitem) {
+ // Signify "required" attributes by special style
+ // TODO: Don't do this until next version, when we add
+ // explanatory text and an 'Autofill Required Attributes' button
+ // if (required)
+ // menuitem.setAttribute("class", "menuitem-highlight-1");
+
+ // Set flags to filter value input
+ if (forceOneChar) {
+ menuitem.setAttribute("forceOneChar", "true");
+ }
+ if (limitFirstChar) {
+ menuitem.setAttribute("limitFirstChar", "true");
+ }
+ if (forceInteger) {
+ menuitem.setAttribute("forceInteger", "true");
+ }
+ if (forceSignedInteger) {
+ menuitem.setAttribute("forceSignedInteger", "true");
+ }
+ if (forceIntOrPercent) {
+ menuitem.setAttribute("forceIntOrPercent", "true");
+ }
+ }
+ }
+ }
+ }
+}
+
+// build attribute list in tree form from element attributes
+function BuildHTMLAttributeTable() {
+ var nodeMap = gElement.attributes;
+ var i;
+ if (nodeMap.length > 0) {
+ var added = false;
+ for (i = 0; i < nodeMap.length; i++) {
+ let name = nodeMap[i].name.trim().toLowerCase();
+ if (
+ CheckAttributeNameSimilarity(nodeMap[i].nodeName, HTMLAttrs) ||
+ name.startsWith("on") ||
+ name == "style"
+ ) {
+ continue; // repeated or non-HTML attribute, ignore this one and go to next
+ }
+ if (
+ !name.startsWith("_moz") &&
+ AddTreeItem(name, nodeMap[i].value, "HTMLAList", HTMLAttrs)
+ ) {
+ added = true;
+ }
+ }
+
+ if (added) {
+ SelectHTMLTree(0);
+ }
+ }
+}
+
+function ClearHTMLInputWidgets() {
+ gDialog.AddHTMLAttributeTree.view.selection.clearSelection();
+ gDialog.AddHTMLAttributeNameInput.value = "";
+ gDialog.AddHTMLAttributeValueInput.value = "";
+ SetTextboxFocus(gDialog.AddHTMLAttributeNameInput);
+}
+
+function onSelectHTMLTreeItem() {
+ if (!gDoOnSelectTree) {
+ return;
+ }
+
+ var tree = gDialog.AddHTMLAttributeTree;
+ if (tree && tree.view.selection.count) {
+ var inputName = TrimString(
+ gDialog.AddHTMLAttributeNameInput.value
+ ).toLowerCase();
+ var selectedItem = getSelectedItem(tree);
+ var selectedName = selectedItem.firstChild.firstChild.getAttribute("label");
+
+ if (inputName == selectedName) {
+ // Already editing selected name - just update the value input
+ gDialog.AddHTMLAttributeValueInput.value = GetTreeItemValueStr(
+ selectedItem
+ );
+ } else {
+ gDialog.AddHTMLAttributeNameInput.value = selectedName;
+
+ // Change value input based on new selected name
+ onInputHTMLAttributeName();
+ }
+ }
+}
+
+function onInputHTMLAttributeName() {
+ let attName = gDialog.AddHTMLAttributeNameInput.value.toLowerCase().trim();
+
+ // Clear value widget, but prevent triggering update in tree
+ gUpdateTreeValue = false;
+ gDialog.AddHTMLAttributeValueInput.value = "";
+ gUpdateTreeValue = true;
+
+ if (attName) {
+ // Get value list for current attribute name
+ var valueListName;
+
+ // Most elements have the "dir" attribute,
+ // so we have just one array for the allowed values instead
+ // requiring duplicate entries for each element in EdAEAttributes.js
+ if (attName == "dir") {
+ valueListName = "all_dir";
+ } else {
+ valueListName = gElement.localName + "_" + attName;
+ }
+
+ // Strip off leading "_" we sometimes use (when element name is reserved word)
+ if (valueListName.startsWith("_")) {
+ valueListName = valueListName.slice(1);
+ }
+
+ var newValue = "";
+ var listLen = 0;
+
+ // Index to which widget we were using to edit the value
+ var deckIndex = gDialog.AddHTMLAttributeValueDeck.getAttribute(
+ "selectedIndex"
+ );
+
+ if (valueListName in gHTMLAttr) {
+ var valueList = gHTMLAttr[valueListName];
+
+ listLen = valueList.length;
+ if (listLen == 1) {
+ newValue = valueList[0];
+ }
+
+ // Note: For case where "value list" is actually just
+ // one (default) item, don't use menulist for that
+ if (listLen > 1) {
+ gDialog.AddHTMLAttributeValueMenulist.removeAllItems();
+
+ if (deckIndex != "1") {
+ // Switch to using editable menulist
+ gDialog.AddHTMLAttributeValueInput =
+ gDialog.AddHTMLAttributeValueMenulist;
+ gDialog.AddHTMLAttributeValueDeck.setAttribute("selectedIndex", "1");
+ }
+ // Rebuild the list
+ for (var i = 0; i < listLen; i++) {
+ if (valueList[i] == "-") {
+ // Signal for separator
+ var popup = gDialog.AddHTMLAttributeValueInput.menupopup;
+ if (popup) {
+ var sep = document.createXULElement("menuseparator");
+ if (sep) {
+ popup.appendChild(sep);
+ }
+ }
+ } else {
+ gDialog.AddHTMLAttributeValueMenulist.appendItem(
+ valueList[i],
+ valueList[i]
+ );
+ }
+ }
+ }
+ }
+
+ if (listLen <= 1 && deckIndex != "0") {
+ // No list: Use textbox for input instead
+ gDialog.AddHTMLAttributeValueInput = gDialog.AddHTMLAttributeValueTextbox;
+ gDialog.AddHTMLAttributeValueDeck.setAttribute("selectedIndex", "0");
+ }
+
+ // If attribute already exists in tree, use associated value,
+ // else use default found above
+ var existingValue = GetAndSelectExistingAttributeValue(
+ attName,
+ "HTMLAList"
+ );
+ if (existingValue) {
+ newValue = existingValue;
+ }
+
+ gDialog.AddHTMLAttributeValueInput.value = newValue;
+
+ if (!existingValue) {
+ onInputHTMLAttributeValue();
+ }
+ }
+}
+
+function onInputHTMLAttributeValue() {
+ if (!gUpdateTreeValue) {
+ return;
+ }
+
+ var name = TrimString(gDialog.AddHTMLAttributeNameInput.value);
+ if (!name) {
+ return;
+ }
+
+ // Trim spaces only from left since we must allow spaces within the string
+ // (we always reset the input field's value below)
+ var value = TrimStringLeft(gDialog.AddHTMLAttributeValueInput.value);
+ if (value) {
+ // Do value filtering based on type of attribute
+ // (Do not use "forceInteger()" to avoid multiple
+ // resetting of input's value and flickering)
+ var selectedItem = gDialog.AddHTMLAttributeNameInput.selectedItem;
+
+ if (selectedItem) {
+ if (
+ selectedItem.getAttribute("forceOneChar") == "true" &&
+ value.length > 1
+ ) {
+ value = value.slice(0, 1);
+ }
+
+ if (selectedItem.getAttribute("forceIntOrPercent") == "true") {
+ // Allow integer with optional "%" as last character
+ var percent = TrimStringRight(value).slice(-1);
+ value = value.replace(/\D+/g, "");
+ if (percent == "%") {
+ value += percent;
+ }
+ } else if (selectedItem.getAttribute("forceInteger") == "true") {
+ value = value.replace(/\D+/g, "");
+ } else if (selectedItem.getAttribute("forceSignedInteger") == "true") {
+ // Allow integer with optional "+" or "-" as first character
+ var sign = value[0];
+ value = value.replace(/\D+/g, "");
+ if (sign == "+" || sign == "-") {
+ value = sign + value;
+ }
+ }
+
+ // Special case attributes
+ if (selectedItem.getAttribute("limitFirstChar") == "true") {
+ // Limit first character to letter, and all others to
+ // letters, numbers, and a few others
+ value = value
+ .replace(/^[^a-zA-Z\u0080-\uFFFF]/, "")
+ .replace(/[^a-zA-Z0-9_\.\-\:\u0080-\uFFFF]+/g, "");
+ }
+
+ // Update once only if it changed
+ if (value != gDialog.AddHTMLAttributeValueInput.value) {
+ gDialog.AddHTMLAttributeValueInput.value = value;
+ }
+ }
+ }
+
+ // Update value in the tree list
+ // If not found, add new attribute
+ if (!UpdateExistingAttribute(name, value, "HTMLAList") && value) {
+ AddTreeItem(name, value, "HTMLAList", HTMLAttrs);
+ }
+}
+
+function editHTMLAttributeValue(targetCell) {
+ if (IsNotTreeHeader(targetCell)) {
+ gDialog.AddHTMLAttributeValueInput.select();
+ }
+}
+
+// update the object with added and removed attributes
+function UpdateHTMLAttributes() {
+ var HTMLAList = document.getElementById("HTMLAList");
+ var i;
+
+ // remove removed attributes
+ for (i = 0; i < HTMLRAttrs.length; i++) {
+ var name = HTMLRAttrs[i];
+
+ if (gElement.hasAttribute(name)) {
+ doRemoveAttribute(name);
+ }
+ }
+
+ // Set added or changed attributes
+ for (i = 0; i < HTMLAList.childNodes.length; i++) {
+ var item = HTMLAList.childNodes[i];
+ doSetAttribute(GetTreeItemAttributeStr(item), GetTreeItemValueStr(item));
+ }
+}
+
+function RemoveHTMLAttribute() {
+ // We only allow 1 selected item
+ if (gDialog.AddHTMLAttributeTree.view.selection.count) {
+ var item = getSelectedItem(gDialog.AddHTMLAttributeTree);
+ var attr = GetTreeItemAttributeStr(item);
+
+ // remove the item from the attribute array
+ HTMLRAttrs[HTMLRAttrs.length] = attr;
+ RemoveNameFromAttArray(attr, HTMLAttrs);
+
+ // Remove the item from the tree
+ item.remove();
+
+ // Clear inputs and selected item in tree
+ ClearHTMLInputWidgets();
+ }
+}
+
+function SelectHTMLTree(index) {
+ gDoOnSelectTree = false;
+ try {
+ gDialog.AddHTMLAttributeTree.selectedIndex = index;
+ } catch (e) {}
+ gDoOnSelectTree = true;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdAEJSEAttributes.js b/comm/suite/editor/components/dialogs/content/EdAEJSEAttributes.js
new file mode 100644
index 0000000000..c15c938b3e
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdAEJSEAttributes.js
@@ -0,0 +1,200 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdAdvancedEdit.js */
+/* import-globals-from EdDialogCommon.js */
+
+function BuildJSEAttributeNameList() {
+ gDialog.AddJSEAttributeNameList.removeAllItems();
+
+ // Get events specific to current element
+ var elementName = gElement.localName;
+ if (elementName in gJSAttr) {
+ var attNames = gJSAttr[elementName];
+ var i;
+ var popup;
+ var sep;
+
+ if (attNames && attNames.length) {
+ // Since we don't allow user-editable JS events yet (but we will soon)
+ // simply remove the JS tab to not allow adding JS events
+ if (attNames[0] == "noJSEvents") {
+ var tab = document.getElementById("tabJSE");
+ if (tab) {
+ tab.remove();
+ }
+
+ return;
+ }
+
+ for (i = 0; i < attNames.length; i++) {
+ gDialog.AddJSEAttributeNameList.appendItem(attNames[i], attNames[i]);
+ }
+
+ popup = gDialog.AddJSEAttributeNameList.firstChild;
+ if (popup) {
+ sep = document.createXULElement("menuseparator");
+ if (sep) {
+ popup.appendChild(sep);
+ }
+ }
+ }
+ }
+
+ // Always add core JS events unless we aborted above
+ for (i = 0; i < gCoreJSEvents.length; i++) {
+ if (gCoreJSEvents[i] == "-") {
+ if (!popup) {
+ popup = gDialog.AddJSEAttributeNameList.firstChild;
+ }
+
+ sep = document.createXULElement("menuseparator");
+
+ if (popup && sep) {
+ popup.appendChild(sep);
+ }
+ } else {
+ gDialog.AddJSEAttributeNameList.appendItem(
+ gCoreJSEvents[i],
+ gCoreJSEvents[i]
+ );
+ }
+ }
+
+ gDialog.AddJSEAttributeNameList.selectedIndex = 0;
+
+ // Use current name and value of first tree item if it exists
+ onSelectJSETreeItem();
+}
+
+// build attribute list in tree form from element attributes
+function BuildJSEAttributeTable() {
+ var nodeMap = gElement.attributes;
+ if (nodeMap.length > 0) {
+ var added = false;
+ for (var i = 0; i < nodeMap.length; i++) {
+ let name = nodeMap[i].nodeName.toLowerCase();
+ if (CheckAttributeNameSimilarity(nodeMap[i].nodeName, JSEAttrs)) {
+ // Repeated or non-JS handler, ignore this one and go to next.
+ continue;
+ }
+ if (!name.startsWith("on")) {
+ // Attribute isn't an event handler.
+ continue;
+ }
+ var value = gElement.getAttribute(nodeMap[i].nodeName);
+ if (AddTreeItem(name, value, "JSEAList", JSEAttrs)) {
+ // add item to tree
+ added = true;
+ }
+ }
+
+ // Select first item
+ if (added) {
+ gDialog.AddJSEAttributeTree.selectedIndex = 0;
+ }
+ }
+}
+
+function onSelectJSEAttribute() {
+ if (!gDoOnSelectTree) {
+ return;
+ }
+
+ gDialog.AddJSEAttributeValueInput.value = GetAndSelectExistingAttributeValue(
+ gDialog.AddJSEAttributeNameList.label,
+ "JSEAList"
+ );
+}
+
+function onSelectJSETreeItem() {
+ var tree = gDialog.AddJSEAttributeTree;
+ if (tree && tree.view.selection.count) {
+ // Select attribute name in list
+ gDialog.AddJSEAttributeNameList.value = GetTreeItemAttributeStr(
+ getSelectedItem(tree)
+ );
+
+ // Set value input to that in tree (no need to update this in the tree)
+ gUpdateTreeValue = false;
+ gDialog.AddJSEAttributeValueInput.value = GetTreeItemValueStr(
+ getSelectedItem(tree)
+ );
+ gUpdateTreeValue = true;
+ }
+}
+
+function onInputJSEAttributeValue() {
+ if (gUpdateTreeValue) {
+ var name = TrimString(gDialog.AddJSEAttributeNameList.label);
+ var value = TrimString(gDialog.AddJSEAttributeValueInput.value);
+
+ // Update value in the tree list
+ // Since we have a non-editable menulist,
+ // we MUST automatically add the event attribute if it doesn't exist
+ if (!UpdateExistingAttribute(name, value, "JSEAList") && value) {
+ AddTreeItem(name, value, "JSEAList", JSEAttrs);
+ }
+ }
+}
+
+function editJSEAttributeValue(targetCell) {
+ if (IsNotTreeHeader(targetCell)) {
+ gDialog.AddJSEAttributeValueInput.inputField.select();
+ }
+}
+
+function UpdateJSEAttributes() {
+ var JSEAList = document.getElementById("JSEAList");
+ var i;
+
+ // remove removed attributes
+ for (i = 0; i < JSERAttrs.length; i++) {
+ var name = JSERAttrs[i];
+
+ if (gElement.hasAttribute(name)) {
+ doRemoveAttribute(name);
+ }
+ }
+
+ // Add events
+ for (i = 0; i < JSEAList.childNodes.length; i++) {
+ var item = JSEAList.childNodes[i];
+
+ // set the event handler
+ doSetAttribute(GetTreeItemAttributeStr(item), GetTreeItemValueStr(item));
+ }
+}
+
+function RemoveJSEAttribute() {
+ // This differs from HTML and CSS panels:
+ // We reselect after removing, because there is not
+ // editable attribute name input, so we can't clear that
+ // like we do in other panels
+ var newIndex = gDialog.AddJSEAttributeTree.selectedIndex;
+
+ // We only allow 1 selected item
+ if (gDialog.AddJSEAttributeTree.view.selection.count) {
+ var item = getSelectedItem(gDialog.AddJSEAttributeTree);
+
+ // Name is the text of the treecell
+ var attr = GetTreeItemAttributeStr(item);
+
+ // remove the item from the attribute array
+ if (newIndex >= JSEAttrs.length - 1) {
+ newIndex--;
+ }
+
+ // remove the item from the attribute array
+ JSERAttrs[JSERAttrs.length] = attr;
+ RemoveNameFromAttArray(attr, JSEAttrs);
+
+ // Remove the item from the tree
+ item.remove();
+
+ // Reselect an item
+ gDialog.AddJSEAttributeTree.selectedIndex = newIndex;
+ }
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.js b/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.js
new file mode 100644
index 0000000000..60e9009905
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.js
@@ -0,0 +1,342 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdAEAttributes.js */
+/* import-globals-from EdAECSSAttributes.js */
+/* import-globals-from EdAEHTMLAttributes.js */
+/* import-globals-from EdAEJSEAttributes.js */
+/* import-globals-from EdDialogCommon.js */
+
+/** ************ GLOBALS **************/
+var gElement = null; // handle to actual element edited
+
+var HTMLAttrs = []; // html attributes
+var CSSAttrs = []; // css attributes
+var JSEAttrs = []; // js events
+
+var HTMLRAttrs = []; // removed html attributes
+var JSERAttrs = []; // removed js events
+
+/* Set false to allow changing selection in tree
+ without doing "onselect" handler actions
+*/
+var gDoOnSelectTree = true;
+var gUpdateTreeValue = true;
+
+/** ************ INITIALISATION && SETUP **************/
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+/**
+ * function : void Startup();
+ * parameters : none
+ * returns : none
+ * desc. : startup and initialisation, prepares dialog.
+ **/
+function Startup() {
+ var editor = GetCurrentEditor();
+
+ // Element to edit is passed in
+ if (!editor || !window.arguments[1]) {
+ dump("Advanced Edit: No editor or element to edit not supplied\n");
+ window.close();
+ return;
+ }
+ // This is the return value for the parent,
+ // who only needs to know if OK was clicked
+ window.opener.AdvancedEditOK = false;
+
+ // The actual element edited (not a copy!)
+ gElement = window.arguments[1];
+
+ // place the tag name in the header
+ var tagLabel = document.getElementById("tagLabel");
+ tagLabel.setAttribute("value", "<" + gElement.localName + ">");
+
+ // Create dialog object to store controls for easy access
+ gDialog.AddHTMLAttributeNameInput = document.getElementById(
+ "AddHTMLAttributeNameInput"
+ );
+
+ // We use a <deck> to switch between editable menulist and textbox
+ gDialog.AddHTMLAttributeValueDeck = document.getElementById(
+ "AddHTMLAttributeValueDeck"
+ );
+ gDialog.AddHTMLAttributeValueMenulist = document.getElementById(
+ "AddHTMLAttributeValueMenulist"
+ );
+ gDialog.AddHTMLAttributeValueTextbox = document.getElementById(
+ "AddHTMLAttributeValueTextbox"
+ );
+ gDialog.AddHTMLAttributeValueInput = gDialog.AddHTMLAttributeValueTextbox;
+
+ gDialog.AddHTMLAttributeTree = document.getElementById("HTMLATree");
+ gDialog.AddCSSAttributeNameInput = document.getElementById(
+ "AddCSSAttributeNameInput"
+ );
+ gDialog.AddCSSAttributeValueInput = document.getElementById(
+ "AddCSSAttributeValueInput"
+ );
+ gDialog.AddCSSAttributeTree = document.getElementById("CSSATree");
+ gDialog.AddJSEAttributeNameList = document.getElementById(
+ "AddJSEAttributeNameList"
+ );
+ gDialog.AddJSEAttributeValueInput = document.getElementById(
+ "AddJSEAttributeValueInput"
+ );
+ gDialog.AddJSEAttributeTree = document.getElementById("JSEATree");
+ gDialog.okButton = document.documentElement.getButton("accept");
+
+ // build the attribute trees
+ BuildHTMLAttributeTable();
+ BuildCSSAttributeTable();
+ BuildJSEAttributeTable();
+
+ // Build attribute name arrays for menulists
+ BuildJSEAttributeNameList();
+ BuildHTMLAttributeNameList();
+ // No menulists for CSS panel (yet)
+
+ // Set focus to Name editable menulist in HTML panel
+ SetTextboxFocus(gDialog.AddHTMLAttributeNameInput);
+
+ // size the dialog properly
+ window.sizeToContent();
+
+ SetWindowLocation();
+}
+
+/**
+ * function : bool onAccept ( void );
+ * parameters : none
+ * returns : boolean true to close the window
+ * desc. : event handler for ok button
+ **/
+function onAccept() {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+ try {
+ // Update our gElement attributes
+ UpdateHTMLAttributes();
+ UpdateCSSAttributes();
+ UpdateJSEAttributes();
+ } catch (ex) {
+ dump(ex);
+ }
+ editor.endTransaction();
+
+ window.opener.AdvancedEditOK = true;
+ SaveWindowLocation();
+}
+
+// Helpers for removing and setting attributes
+// Use editor transactions if modifying the element already in the document
+// (Temporary element from a property dialog won't have a parent node)
+function doRemoveAttribute(attrib) {
+ try {
+ var editor = GetCurrentEditor();
+ if (gElement.parentNode) {
+ editor.removeAttribute(gElement, attrib);
+ } else {
+ gElement.removeAttribute(attrib);
+ }
+ } catch (ex) {}
+}
+
+function doSetAttribute(attrib, value) {
+ try {
+ var editor = GetCurrentEditor();
+ if (gElement.parentNode) {
+ editor.setAttribute(gElement, attrib, value);
+ } else {
+ gElement.setAttribute(attrib, value);
+ }
+ } catch (ex) {}
+}
+
+/**
+ * function : bool CheckAttributeNameSimilarity ( string attName, array attArray );
+ * parameters : attribute to look for, array of current attributes
+ * returns : true if attribute already exists, false if it does not
+ * desc. : checks to see if any other attributes by the same name as the arg supplied
+ * already exist.
+ **/
+function CheckAttributeNameSimilarity(attName, attArray) {
+ for (var i = 0; i < attArray.length; i++) {
+ if (attName.toLowerCase() == attArray[i].toLowerCase()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * function : bool UpdateExistingAttribute ( string attName, string attValue, string treeChildrenId );
+ * parameters : attribute to look for, new value, ID of <treeChildren> node in XUL tree
+ * returns : true if attribute already exists in tree, false if it does not
+ * desc. : checks to see if any other attributes by the same name as the arg supplied
+ * already exist while setting the associated value if different from current value
+ **/
+function UpdateExistingAttribute(attName, attValue, treeChildrenId) {
+ var treeChildren = document.getElementById(treeChildrenId);
+ if (!treeChildren) {
+ return false;
+ }
+
+ var name;
+ var i;
+ attName = TrimString(attName).toLowerCase();
+ attValue = TrimString(attValue);
+
+ for (i = 0; i < treeChildren.childNodes.length; i++) {
+ var item = treeChildren.childNodes[i];
+ name = GetTreeItemAttributeStr(item);
+ if (name.toLowerCase() == attName) {
+ // Set the text in the "value' column treecell
+ SetTreeItemValueStr(item, attValue);
+
+ // Select item just changed,
+ // but don't trigger the tree's onSelect handler
+ gDoOnSelectTree = false;
+ try {
+ selectTreeItem(treeChildren, item);
+ } catch (e) {}
+ gDoOnSelectTree = true;
+
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * function : string GetAndSelectExistingAttributeValue ( string attName, string treeChildrenId );
+ * parameters : attribute to look for, ID of <treeChildren> node in XUL tree
+ * returns : value in from the tree or empty string if name not found
+ **/
+function GetAndSelectExistingAttributeValue(attName, treeChildrenId) {
+ if (!attName) {
+ return "";
+ }
+
+ var treeChildren = document.getElementById(treeChildrenId);
+ var name;
+ var i;
+
+ for (i = 0; i < treeChildren.childNodes.length; i++) {
+ var item = treeChildren.childNodes[i];
+ name = GetTreeItemAttributeStr(item);
+ if (name.toLowerCase() == attName.toLowerCase()) {
+ // Select item in the tree
+ // but don't trigger the tree's onSelect handler
+ gDoOnSelectTree = false;
+ try {
+ selectTreeItem(treeChildren, item);
+ } catch (e) {}
+ gDoOnSelectTree = true;
+
+ // Get the text in the "value' column treecell
+ return GetTreeItemValueStr(item);
+ }
+ }
+
+ // Attribute doesn't exist in tree, so remove selection
+ gDoOnSelectTree = false;
+ try {
+ treeChildren.parentNode.view.selection.clearSelection();
+ } catch (e) {}
+ gDoOnSelectTree = true;
+
+ return "";
+}
+
+/* Tree structure:
+ <treeItem>
+ <treeRow>
+ <treeCell> // Name Cell
+ <treeCell // Value Cell
+*/
+function GetTreeItemAttributeStr(treeItem) {
+ if (treeItem) {
+ return TrimString(treeItem.firstChild.firstChild.getAttribute("label"));
+ }
+
+ return "";
+}
+
+function GetTreeItemValueStr(treeItem) {
+ if (treeItem) {
+ return TrimString(treeItem.firstChild.lastChild.getAttribute("label"));
+ }
+
+ return "";
+}
+
+function SetTreeItemValueStr(treeItem, value) {
+ if (treeItem && GetTreeItemValueStr(treeItem) != value) {
+ treeItem.firstChild.lastChild.setAttribute("label", value);
+ }
+}
+
+function IsNotTreeHeader(treeCell) {
+ if (treeCell) {
+ return treeCell.parentNode.parentNode.nodeName != "treehead";
+ }
+
+ return false;
+}
+
+function RemoveNameFromAttArray(attName, attArray) {
+ for (var i = 0; i < attArray.length; i++) {
+ if (attName.toLowerCase() == attArray[i].toLowerCase()) {
+ // Remove 1 array item
+ attArray.splice(i, 1);
+ break;
+ }
+ }
+}
+
+// adds a generalised treeitem.
+function AddTreeItem(name, value, treeChildrenId, attArray) {
+ attArray[attArray.length] = name;
+ var treeChildren = document.getElementById(treeChildrenId);
+ var treeitem = document.createXULElement("treeitem");
+ var treerow = document.createXULElement("treerow");
+
+ var attrCell = document.createXULElement("treecell");
+ attrCell.setAttribute("class", "propertylist");
+ attrCell.setAttribute("label", name);
+
+ var valueCell = document.createXULElement("treecell");
+ valueCell.setAttribute("class", "propertylist");
+ valueCell.setAttribute("label", value);
+
+ treerow.appendChild(attrCell);
+ treerow.appendChild(valueCell);
+ treeitem.appendChild(treerow);
+ treeChildren.appendChild(treeitem);
+
+ // Select item just added, but suppress calling the onSelect handler.
+ gDoOnSelectTree = false;
+ try {
+ selectTreeItem(treeChildren, treeitem);
+ } catch (e) {}
+ gDoOnSelectTree = true;
+
+ return treeitem;
+}
+
+function selectTreeItem(treeChildren, item) {
+ var index = treeChildren.parentNode.view.getIndexOfItem(item);
+ treeChildren.parentNode.view.selection.select(index);
+}
+
+function getSelectedItem(tree) {
+ if (tree.view.selection.count == 1) {
+ return tree.view.getItemAtIndex(tree.currentIndex);
+ }
+ return null;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.xhtml b/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.xhtml
new file mode 100644
index 0000000000..94942709a1
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.xhtml
@@ -0,0 +1,182 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!-- first checkin of the year 2000! -->
+<!-- Ben Goodger, 12:50AM, 01/00/00 NZST -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/menulist.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EdAdvancedEdit.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ id="advancedEditDlg"
+ style="width: 40em;"
+ title="&WindowTitle.label;"
+ onload="Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <!-- element page functions -->
+ <script src="chrome://editor/content/EdAEHTMLAttributes.js"/>
+ <script src="chrome://editor/content/EdAECSSAttributes.js"/>
+ <script src="chrome://editor/content/EdAEJSEAttributes.js"/>
+ <script src="chrome://editor/content/EdAEAttributes.js"/>
+
+ <!-- global dialog functions -->
+ <script src="chrome://editor/content/EdAdvancedEdit.js"/>
+
+ <script src="chrome://messenger/content/customElements.js"/>
+
+ <hbox>
+ <label value="&currentattributesfor.label;"/>
+ <label class="header" id="tagLabel"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <tabbox flex="1">
+ <tabs>
+ <tab label="&tabHTML.label;"/>
+ <tab label="&tabCSS.label;"/>
+ <tab label="&tabJSE.label;" id="tabJSE"/>
+ </tabs>
+ <tabpanels flex="1">
+ <!-- ============================================================== -->
+ <!-- HTML Attributes -->
+ <!-- ============================================================== -->
+ <vbox>
+ <tree id="HTMLATree" class="AttributesTree" flex="1"
+ hidecolumnpicker="true" seltype="single"
+ onselect="onSelectHTMLTreeItem();"
+ onclick="onSelectHTMLTreeItem();"
+ ondblclick="editHTMLAttributeValue(event.target);">
+ <treecols>
+ <treecol id="HTMLAttrCol" flex="35" label="&tree.attributeHeader.label;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="HTMLValCol" flex="65" label="&tree.valueHeader.label;"/>
+ </treecols>
+ <treechildren id="HTMLAList" flex="1"/>
+ </tree>
+ <hbox align="center">
+ <label value="&editAttribute.label;"/>
+ <spacer flex="1"/>
+ <button label="&removeAttribute.label;" oncommand="RemoveHTMLAttribute();"/>
+ </hbox>
+ <grid>
+ <columns>
+ <column flex="1"/><column flex="1"/>
+ </columns>
+ <rows>
+ <row equalsize="always">
+ <label control="AddHTMLAttributeNameInput" value="&AttName.label;"/>
+ <label control="AddHTMLAttributeValueInput" value="&AttValue.label;"/>
+ </row>
+ <row align="top" equalsize="always">
+ <!-- Lists are built at runtime -->
+ <menulist is="menulist-editable" id="AddHTMLAttributeNameInput"
+ editable="true" flex="1"
+ oninput="onInputHTMLAttributeName();"
+ oncommand="onInputHTMLAttributeName();"/>
+ <deck id="AddHTMLAttributeValueDeck" selectedIndex="0">
+ <hbox align="top">
+ <textbox id="AddHTMLAttributeValueTextbox" flex="1"
+ oninput="onInputHTMLAttributeValue();"/>
+ </hbox>
+ <hbox align="top">
+ <menulist is="menulist-editable" id="AddHTMLAttributeValueMenulist"
+ editable="true" flex="1"
+ oninput="onInputHTMLAttributeValue();"
+ oncommand="onInputHTMLAttributeValue();"/>
+ </hbox>
+ </deck>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+ <!-- ============================================================== -->
+ <!-- CSS Attributes -->
+ <!-- ============================================================== -->
+ <vbox>
+ <tree id="CSSATree" class="AttributesTree" flex="1"
+ hidecolumnpicker="true" seltype="single"
+ onselect="onSelectCSSTreeItem();"
+ onclick="onSelectCSSTreeItem();"
+ ondblclick="editCSSAttributeValue(event.target);">
+ <treecols>
+ <treecol id="CSSPropCol" flex="35" label="&tree.propertyHeader.label;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="CSSValCol" flex="65" label="&tree.valueHeader.label;"/>
+ </treecols>
+ <treechildren id="CSSAList" flex="1"/>
+ </tree>
+ <hbox align="center">
+ <label value="&editAttribute.label;"/>
+ <spacer flex="1"/>
+ <button label="&removeAttribute.label;" oncommand="RemoveCSSAttribute();"/>
+ </hbox>
+ <grid>
+ <columns>
+ <column flex="1"/><column flex="1"/>
+ </columns>
+ <rows>
+ <row equalsize="always">
+ <label value="&PropertyName.label;"/>
+ <label value="&AttValue.label;"/>
+ </row>
+ <row align="top" equalsize="always">
+ <textbox id="AddCSSAttributeNameInput" flex="1"
+ oninput="onInputCSSAttributeName();"/>
+ <textbox id="AddCSSAttributeValueInput" flex="1"
+ oninput="onChangeCSSAttribute();"/>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+ <!-- ============================================================== -->
+ <!-- JavaScript Event Handlers -->
+ <!-- ============================================================== -->
+ <vbox>
+ <tree id="JSEATree" class="AttributesTree" flex="1"
+ hidecolumnpicker="true" seltype="single"
+ onselect="onSelectJSETreeItem();"
+ onclick="onSelectJSETreeItem();"
+ ondblclick="editJSEAttributeValue(event.target);">
+ <treecols>
+ <treecol id="AttrCol" flex="35" label="&tree.attributeHeader.label;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="HeaderCol" flex="65" label="&tree.valueHeader.label;"/>
+ </treecols>
+ <treechildren id="JSEAList" flex="1"/>
+ </tree>
+ <hbox align="center">
+ <label value="&editAttribute.label;"/>
+ <spacer flex="1"/>
+ <button label="&removeAttribute.label;" oncommand="RemoveJSEAttribute()"/>
+ </hbox>
+ <grid>
+ <columns>
+ <column flex="1"/><column flex="1"/>
+ </columns>
+ <rows>
+ <row equalsize="always">
+ <label value="&AttName.label;"/>
+ <label value="&AttValue.label;"/>
+ </row>
+ <row align="top" equalsize="always">
+ <!-- List is built at runtime -->
+ <menulist id="AddJSEAttributeNameList" flex="1"
+ oncommand="onSelectJSEAttribute();"/>
+ <textbox id="AddJSEAttributeValueInput" flex="1"
+ oninput="onInputJSEAttributeValue();"/>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+ </tabpanels>
+ </tabbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdButtonProps.js b/comm/suite/editor/components/dialogs/content/EdButtonProps.js
new file mode 100644
index 0000000000..1cd0ee7365
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdButtonProps.js
@@ -0,0 +1,146 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var insertNew;
+var buttonElement;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ gDialog = {
+ buttonType: document.getElementById("ButtonType"),
+ buttonName: document.getElementById("ButtonName"),
+ buttonValue: document.getElementById("ButtonValue"),
+ buttonDisabled: document.getElementById("ButtonDisabled"),
+ buttonTabIndex: document.getElementById("ButtonTabIndex"),
+ buttonAccessKey: document.getElementById("ButtonAccessKey"),
+ MoreSection: document.getElementById("MoreSection"),
+ MoreFewerButton: document.getElementById("MoreFewerButton"),
+ RemoveButton: document.getElementById("RemoveButton"),
+ };
+
+ // Get a single selected button element
+ const kTagName = "button";
+ try {
+ buttonElement = editor.getSelectedElement(kTagName);
+ } catch (e) {}
+
+ if (buttonElement) {
+ // We found an element and don't need to insert one
+ insertNew = false;
+ } else {
+ insertNew = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+ try {
+ buttonElement = editor.createElementWithDefaults(kTagName);
+ } catch (e) {}
+
+ if (!buttonElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+ // Hide button removing existing button
+ gDialog.RemoveButton.hidden = true;
+ }
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = buttonElement.cloneNode(false);
+
+ InitDialog();
+
+ InitMoreFewer();
+
+ gDialog.buttonType.focus();
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ var type = globalElement.getAttribute("type");
+ var index = 0;
+ switch (type) {
+ case "button":
+ index = 2;
+ break;
+ case "reset":
+ index = 1;
+ break;
+ }
+ gDialog.buttonType.selectedIndex = index;
+ gDialog.buttonName.value = globalElement.getAttribute("name");
+ gDialog.buttonValue.value = globalElement.getAttribute("value");
+ gDialog.buttonDisabled.setAttribute(
+ "checked",
+ globalElement.hasAttribute("disabled")
+ );
+ gDialog.buttonTabIndex.value = globalElement.getAttribute("tabindex");
+ gDialog.buttonAccessKey.value = globalElement.getAttribute("accesskey");
+}
+
+function RemoveButton() {
+ RemoveContainer(buttonElement);
+ SaveWindowLocation();
+ window.close();
+}
+
+function ValidateData() {
+ var attributes = {
+ type: ["", "reset", "button"][gDialog.buttonType.selectedIndex],
+ name: gDialog.buttonName.value,
+ value: gDialog.buttonValue.value,
+ tabindex: gDialog.buttonTabIndex.value,
+ accesskey: gDialog.buttonAccessKey.value,
+ };
+ for (var a in attributes) {
+ if (attributes[a]) {
+ globalElement.setAttribute(a, attributes[a]);
+ } else {
+ globalElement.removeAttribute(a);
+ }
+ }
+ if (gDialog.buttonDisabled.checked) {
+ globalElement.setAttribute("disabled", "");
+ } else {
+ globalElement.removeAttribute("disabled");
+ }
+ return true;
+}
+
+function onAccept() {
+ // All values are valid - copy to actual element in doc or
+ // element created to insert
+ ValidateData();
+
+ var editor = GetCurrentEditor();
+
+ editor.cloneAttributes(buttonElement, globalElement);
+
+ if (insertNew) {
+ if (!InsertElementAroundSelection(buttonElement)) {
+ /* eslint-disable-next-line no-unsanitized/property */
+ buttonElement.innerHTML = editor.outputToString(
+ "text/html",
+ kOutputSelectionOnly
+ );
+ editor.insertElementAtSelection(buttonElement, true);
+ }
+ }
+
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdButtonProps.xhtml b/comm/suite/editor/components/dialogs/content/EdButtonProps.xhtml
new file mode 100644
index 0000000000..70e4774f13
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdButtonProps.xhtml
@@ -0,0 +1,92 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edButtonProperties SYSTEM "chrome://editor/locale/EditorButtonProperties.dtd">
+%edButtonProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ buttons="accept,cancel">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdButtonProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&Settings.label;</label>
+ </hbox>
+ <grid><columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label control="ButtonType" value="&ButtonType.label;" accesskey="&ButtonType.accesskey;"/>
+ <menulist id="ButtonType">
+ <menupopup>
+ <menuitem label="&submit.value;"/>
+ <menuitem label="&reset.value;"/>
+ <menuitem label="&button.value;"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row align="center">
+ <label control="ButtonName" value="&ButtonName.label;" accesskey="&ButtonName.accesskey;"/>
+ <textbox id="ButtonName"/>
+ </row>
+ <row align="center">
+ <label control="ButtonValue" value="&ButtonValue.label;" accesskey="&ButtonValue.accesskey;"/>
+ <textbox id="ButtonValue"/>
+ </row>
+ </rows>
+ </grid>
+ <hbox>
+ <button id="MoreFewerButton" oncommand="onMoreFewer();" persist="more"/>
+ </hbox>
+ <grid id="MoreSection"><columns><column/><column/></columns>
+ <rows>
+ <row>
+ <spacer/>
+ <checkbox id="ButtonDisabled" label="&ButtonDisabled.label;" accesskey="&ButtonDisabled.accesskey;"/>
+ </row>
+ <row align="center">
+ <label control="ButtonTabIndex" value="&tabIndex.label;" accesskey="&tabIndex.accesskey;"/>
+ <hbox>
+ <textbox id="ButtonTabIndex" class="narrow" oninput="forceInteger(this.id);"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="ButtonAccessKey" value="&AccessKey.label;" accesskey="&AccessKey.accesskey;"/>
+ <hbox>
+ <textbox id="ButtonAccessKey" class="narrow"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <!-- from EdDialogOverlay -->
+ <hbox flex="1" style="margin-top: 0.2em">
+ <button id="RemoveButton" label="&RemoveButton.label;" accesskey="&RemoveButton.accesskey;" oncommand="RemoveButton();"/>
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator class="groove"/>
+
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdColorPicker.js b/comm/suite/editor/components/dialogs/content/EdColorPicker.js
new file mode 100644
index 0000000000..95ce279368
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdColorPicker.js
@@ -0,0 +1,297 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// Cancel() is in EdDialogCommon.js
+
+var insertNew = true;
+var tagname = "TAG NAME";
+var gColor = "";
+var LastPickedColor = "";
+var ColorType = "Text";
+var TextType = false;
+var HighlightType = false;
+var TableOrCell = false;
+var LastPickedIsDefault = true;
+var NoDefault = false;
+var gColorObj;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancelColor);
+
+function Startup() {
+ if (!window.arguments[1]) {
+ dump("EdColorPicker: Missing color object param\n");
+ return;
+ }
+
+ // window.arguments[1] is object to get initial values and return color data
+ gColorObj = window.arguments[1];
+ gColorObj.Cancel = false;
+
+ gDialog.ColorPicker = document.getElementById("ColorPicker");
+ gDialog.ColorInput = document.getElementById("ColorInput");
+ gDialog.LastPickedButton = document.getElementById("LastPickedButton");
+ gDialog.LastPickedColor = document.getElementById("LastPickedColor");
+ gDialog.CellOrTableGroup = document.getElementById("CellOrTableGroup");
+ gDialog.TableRadio = document.getElementById("TableRadio");
+ gDialog.CellRadio = document.getElementById("CellRadio");
+ gDialog.ColorSwatch = document.getElementById("ColorPickerSwatch");
+ gDialog.Ok = document.documentElement.getButton("accept");
+
+ // The type of color we are setting:
+ // text: Text, Link, ActiveLink, VisitedLink,
+ // or background: Page, Table, or Cell
+ if (gColorObj.Type) {
+ ColorType = gColorObj.Type;
+ // Get string for dialog title from passed-in type
+ // (note constraint on editor.properties string name)
+ let IsCSSPrefChecked = Services.prefs.getBoolPref("editor.use_css");
+
+ if (GetCurrentEditor()) {
+ if (ColorType == "Page" && IsCSSPrefChecked && IsHTMLEditor()) {
+ document.title = GetString("BlockColor");
+ } else {
+ document.title = GetString(ColorType + "Color");
+ }
+ }
+ }
+
+ gDialog.ColorInput.value = "";
+ var tmpColor;
+ var haveTableRadio = false;
+
+ switch (ColorType) {
+ case "Page":
+ tmpColor = gColorObj.PageColor;
+ if (tmpColor && tmpColor.toLowerCase() != "window") {
+ gColor = tmpColor;
+ }
+ break;
+ case "Table":
+ if (gColorObj.TableColor) {
+ gColor = gColorObj.TableColor;
+ }
+ break;
+ case "Cell":
+ if (gColorObj.CellColor) {
+ gColor = gColorObj.CellColor;
+ }
+ break;
+ case "TableOrCell":
+ TableOrCell = true;
+ document.getElementById("TableOrCellGroup").collapsed = false;
+ haveTableRadio = true;
+ if (gColorObj.SelectedType == "Cell") {
+ gColor = gColorObj.CellColor;
+ gDialog.CellOrTableGroup.selectedItem = gDialog.CellRadio;
+ gDialog.CellRadio.focus();
+ } else {
+ gColor = gColorObj.TableColor;
+ gDialog.CellOrTableGroup.selectedItem = gDialog.TableRadio;
+ gDialog.TableRadio.focus();
+ }
+ break;
+ case "Highlight":
+ HighlightType = true;
+ if (gColorObj.HighlightColor) {
+ gColor = gColorObj.HighlightColor;
+ }
+ break;
+ default:
+ // Any other type will change some kind of text,
+ TextType = true;
+ tmpColor = gColorObj.TextColor;
+ if (tmpColor && tmpColor.toLowerCase() != "windowtext") {
+ gColor = gColorObj.TextColor;
+ }
+ break;
+ }
+
+ // Set initial color in input field and in the colorpicker
+ SetCurrentColor(gColor);
+ gDialog.ColorPicker.value = gColor;
+
+ // Use last-picked colors passed in, or those persistent on dialog
+ if (TextType) {
+ if (!("LastTextColor" in gColorObj) || !gColorObj.LastTextColor) {
+ gColorObj.LastTextColor = gDialog.LastPickedColor.getAttribute(
+ "LastTextColor"
+ );
+ }
+ LastPickedColor = gColorObj.LastTextColor;
+ } else if (HighlightType) {
+ if (!("LastHighlightColor" in gColorObj) || !gColorObj.LastHighlightColor) {
+ gColorObj.LastHighlightColor = gDialog.LastPickedColor.getAttribute(
+ "LastHighlightColor"
+ );
+ }
+ LastPickedColor = gColorObj.LastHighlightColor;
+ } else {
+ if (
+ !("LastBackgroundColor" in gColorObj) ||
+ !gColorObj.LastBackgroundColor
+ ) {
+ gColorObj.LastBackgroundColor = gDialog.LastPickedColor.getAttribute(
+ "LastBackgroundColor"
+ );
+ }
+ LastPickedColor = gColorObj.LastBackgroundColor;
+ }
+
+ // Set method to detect clicking on OK button
+ // so we don't get fooled by changing "default" behavior
+ gDialog.Ok.setAttribute("onclick", "SetDefaultToOk()");
+
+ if (!LastPickedColor) {
+ // Hide the button, as there is no last color available.
+ gDialog.LastPickedButton.hidden = true;
+ } else {
+ gDialog.LastPickedColor.setAttribute(
+ "style",
+ "background-color: " + LastPickedColor
+ );
+
+ // Make "Last-picked" the default button, until the user selects a color.
+ gDialog.Ok.removeAttribute("default");
+ gDialog.LastPickedButton.setAttribute("default", "true");
+ }
+
+ // Caller can prevent user from submitting an empty, i.e., default color
+ NoDefault = gColorObj.NoDefault;
+ if (NoDefault) {
+ // Hide the "Default button -- user must pick a color
+ document.getElementById("DefaultColorButton").collapsed = true;
+ }
+
+ // Set focus to colorpicker if not set to table radio buttons above
+ if (!haveTableRadio) {
+ gDialog.ColorPicker.focus();
+ }
+
+ SetWindowLocation();
+}
+
+function SelectColor() {
+ var color = gDialog.ColorPicker.value;
+ if (color) {
+ SetCurrentColor(color);
+ }
+}
+
+function RemoveColor() {
+ SetCurrentColor("");
+ gDialog.ColorInput.focus();
+ SetDefaultToOk();
+}
+
+function SelectColorByKeypress(aEvent) {
+ if (aEvent.charCode == aEvent.DOM_VK_SPACE) {
+ SelectColor();
+ SetDefaultToOk();
+ }
+}
+
+function SelectLastPickedColor() {
+ SetCurrentColor(LastPickedColor);
+ if (onAccept()) {
+ // window.close();
+ return true;
+ }
+
+ return false;
+}
+
+function SetCurrentColor(color) {
+ // TODO: Validate color?
+ if (!color) {
+ color = "";
+ }
+ gColor = TrimString(color).toLowerCase();
+ if (gColor == "mixed") {
+ gColor = "";
+ }
+ gDialog.ColorInput.value = gColor;
+ SetColorSwatch();
+}
+
+function SetColorSwatch() {
+ // TODO: DON'T ALLOW SPACES?
+ var color = TrimString(gDialog.ColorInput.value);
+ if (color) {
+ gDialog.ColorSwatch.setAttribute("style", "background-color:" + color);
+ gDialog.ColorSwatch.removeAttribute("default");
+ } else {
+ gDialog.ColorSwatch.setAttribute("style", "background-color:inherit");
+ gDialog.ColorSwatch.setAttribute("default", "true");
+ }
+}
+
+function SetDefaultToOk() {
+ gDialog.LastPickedButton.removeAttribute("default");
+ gDialog.Ok.setAttribute("default", "true");
+ LastPickedIsDefault = false;
+}
+
+function ValidateData() {
+ if (LastPickedIsDefault) {
+ gColor = LastPickedColor;
+ } else {
+ gColor = gDialog.ColorInput.value;
+ }
+
+ gColor = TrimString(gColor).toLowerCase();
+
+ // TODO: Validate the color string!
+
+ if (NoDefault && !gColor) {
+ ShowInputErrorMessage(GetString("NoColorError"));
+ SetTextboxFocus(gDialog.ColorInput);
+ return false;
+ }
+ return true;
+}
+
+function onAccept(event) {
+ if (!ValidateData()) {
+ event.preventDefault();
+ return;
+ }
+
+ // Set return values and save in persistent color attributes
+ if (TextType) {
+ gColorObj.TextColor = gColor;
+ if (gColor.length > 0) {
+ gDialog.LastPickedColor.setAttribute("LastTextColor", gColor);
+ gColorObj.LastTextColor = gColor;
+ }
+ } else if (HighlightType) {
+ gColorObj.HighlightColor = gColor;
+ if (gColor.length > 0) {
+ gDialog.LastPickedColor.setAttribute("LastHighlightColor", gColor);
+ gColorObj.LastHighlightColor = gColor;
+ }
+ } else {
+ gColorObj.BackgroundColor = gColor;
+ if (gColor.length > 0) {
+ gDialog.LastPickedColor.setAttribute("LastBackgroundColor", gColor);
+ gColorObj.LastBackgroundColor = gColor;
+ }
+ // If table or cell requested, tell caller which element to set on
+ if (TableOrCell && gDialog.TableRadio.selected) {
+ gColorObj.Type = "Table";
+ }
+ }
+ SaveWindowLocation();
+}
+
+function onCancelColor() {
+ // Tells caller that user canceled
+ gColorObj.Cancel = true;
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdColorPicker.xhtml b/comm/suite/editor/components/dialogs/content/EdColorPicker.xhtml
new file mode 100644
index 0000000000..c18bc90e62
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdColorPicker.xhtml
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EdColorPicker.dtd">
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdColorPicker.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <hbox id="TableOrCellGroup" align="center" collapsed="true">
+ <label control="CellOrTableGroup" value="&background.label;" accesskey="&background.accessKey;"/>
+ <radiogroup id="CellOrTableGroup" orient="horizontal">
+ <radio id="TableRadio" label="&table.label;" accesskey="&table.accessKey;"/>
+ <radio id="CellRadio" label="&cell.label;" accesskey="&cell.accessKey;"/>
+ </radiogroup>
+ </hbox>
+ <label value="&chooseColor1.label;"/>
+ <html:input type="color" id="ColorPicker"
+ onclick="SetDefaultToOk();"
+ ondblclick="if (onAccept()) { window.close(); }"
+ onkeypress="SelectColorByKeypress(event);"
+ onchange="SelectColor();"/>
+
+ <spacer class="spacer"/>
+ <vbox flex="1">
+ <button id="LastPickedButton" crop="right" oncommand="SelectLastPickedColor();">
+ <spacer id="LastPickedColor"
+ LastTextColor="" LastBackgroundColor=""
+ persist="LastTextColor LastBackgroundColor"/>
+ <label value="&lastPickedColor.label;" accesskey="&lastPickedColor.accessKey;" flex="1" style="text-align: center;"/>
+ </button>
+ <label value="&chooseColor2.label;" accesskey="&chooseColor2.accessKey;" control="ColorInput"/>
+ <label value="&setColorExample.label;"/>
+ <hbox align="center" flex="1=">
+ <textbox id="ColorInput" style="width: 8em" oninput="SetColorSwatch(); SetDefaultToOk();"/>
+ <spacer flex="1"/>
+ <spacer id="ColorPickerSwatch"/>
+ <spacer flex="1"/>
+ <button id="DefaultColorButton" label="&default.label;" accesskey="&default.accessKey;"
+ style="margin-right:0px;" oncommand="RemoveColor()"/>
+ </hbox>
+ </vbox>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdColorProps.js b/comm/suite/editor/components/dialogs/content/EdColorProps.js
new file mode 100644
index 0000000000..62d3f29c9a
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdColorProps.js
@@ -0,0 +1,476 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ Behavior notes:
+ Radio buttons select "UseDefaultColors" vs. "UseCustomColors" modes.
+ If any color attribute is set in the body, mode is "Custom Colors",
+ even if 1 or more (but not all) are actually null (= "use default")
+ When in "Custom Colors" mode, all colors will be set on body tag,
+ even if they are just default colors, to assure compatible colors in page.
+ User cannot select "use default" for individual colors
+*/
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// Cancel() is in EdDialogCommon.js
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+var gBodyElement;
+var prefs;
+var gBackgroundImage;
+
+// Initialize in case we can't get them from prefs???
+var defaultTextColor = "#000000";
+var defaultLinkColor = "#000099";
+var defaultActiveColor = "#000099";
+var defaultVisitedColor = "#990099";
+var defaultBackgroundColor = "#FFFFFF";
+const styleStr = "style";
+const textStr = "text";
+const linkStr = "link";
+const vlinkStr = "vlink";
+const alinkStr = "alink";
+const bgcolorStr = "bgcolor";
+const backgroundStr = "background";
+const cssColorStr = "color";
+const cssBackgroundColorStr = "background-color";
+const cssBackgroundImageStr = "background-image";
+const colorStyle = cssColorStr + ": ";
+const backColorStyle = cssBackgroundColorStr + ": ";
+const backImageStyle = "; " + cssBackgroundImageStr + ": url(";
+
+var customTextColor;
+var customLinkColor;
+var customActiveColor;
+var customVisitedColor;
+var customBackgroundColor;
+var previewBGColor;
+
+// dialog initialization code
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ gDialog.ColorPreview = document.getElementById("ColorPreview");
+ gDialog.NormalText = document.getElementById("NormalText");
+ gDialog.LinkText = document.getElementById("LinkText");
+ gDialog.ActiveLinkText = document.getElementById("ActiveLinkText");
+ gDialog.VisitedLinkText = document.getElementById("VisitedLinkText");
+ gDialog.PageColorGroup = document.getElementById("PageColorGroup");
+ gDialog.DefaultColorsRadio = document.getElementById("DefaultColorsRadio");
+ gDialog.CustomColorsRadio = document.getElementById("CustomColorsRadio");
+ gDialog.BackgroundImageInput = document.getElementById(
+ "BackgroundImageInput"
+ );
+
+ try {
+ gBodyElement = editor.rootElement;
+ } catch (e) {}
+
+ if (!gBodyElement) {
+ dump("Failed to get BODY element!\n");
+ window.close();
+ }
+
+ // Set element we will edit
+ globalElement = gBodyElement.cloneNode(false);
+
+ // Initialize default colors from browser prefs
+ var browserColors = GetDefaultBrowserColors();
+ if (browserColors) {
+ // Use author's browser pref colors passed into dialog
+ defaultTextColor = browserColors.TextColor;
+ defaultLinkColor = browserColors.LinkColor;
+ defaultActiveColor = browserColors.ActiveLinkColor;
+ defaultVisitedColor = browserColors.VisitedLinkColor;
+ defaultBackgroundColor = browserColors.BackgroundColor;
+ }
+
+ // We only need to test for this once per dialog load
+ gHaveDocumentUrl = GetDocumentBaseUrl();
+
+ InitDialog();
+
+ gDialog.PageColorGroup.focus();
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ // Get image from document
+ gBackgroundImage = GetHTMLOrCSSStyleValue(
+ globalElement,
+ backgroundStr,
+ cssBackgroundImageStr
+ );
+ if (/url\((.*)\)/.test(gBackgroundImage)) {
+ gBackgroundImage = RegExp.$1;
+ }
+
+ if (gBackgroundImage) {
+ // Shorten data URIs for display.
+ shortenImageData(gBackgroundImage, gDialog.BackgroundImageInput);
+ gDialog.ColorPreview.setAttribute(
+ styleStr,
+ backImageStyle + gBackgroundImage + ");"
+ );
+ }
+
+ SetRelativeCheckbox();
+
+ customTextColor = GetHTMLOrCSSStyleValue(globalElement, textStr, cssColorStr);
+ customTextColor = ConvertRGBColorIntoHEXColor(customTextColor);
+ customLinkColor = globalElement.getAttribute(linkStr);
+ customActiveColor = globalElement.getAttribute(alinkStr);
+ customVisitedColor = globalElement.getAttribute(vlinkStr);
+ customBackgroundColor = GetHTMLOrCSSStyleValue(
+ globalElement,
+ bgcolorStr,
+ cssBackgroundColorStr
+ );
+ customBackgroundColor = ConvertRGBColorIntoHEXColor(customBackgroundColor);
+
+ var haveCustomColor =
+ customTextColor ||
+ customLinkColor ||
+ customVisitedColor ||
+ customActiveColor ||
+ customBackgroundColor;
+
+ // Set default color explicitly for any that are missing
+ // PROBLEM: We are using "windowtext" and "window" for the Windows OS
+ // default color values. This works with CSS in preview window,
+ // but we should NOT use these as values for HTML attributes!
+
+ if (!customTextColor) {
+ customTextColor = defaultTextColor;
+ }
+ if (!customLinkColor) {
+ customLinkColor = defaultLinkColor;
+ }
+ if (!customActiveColor) {
+ customActiveColor = defaultActiveColor;
+ }
+ if (!customVisitedColor) {
+ customVisitedColor = defaultVisitedColor;
+ }
+ if (!customBackgroundColor) {
+ customBackgroundColor = defaultBackgroundColor;
+ }
+
+ if (haveCustomColor) {
+ // If any colors are set, then check the "Custom" radio button
+ gDialog.PageColorGroup.selectedItem = gDialog.CustomColorsRadio;
+ UseCustomColors();
+ } else {
+ gDialog.PageColorGroup.selectedItem = gDialog.DefaultColorsRadio;
+ UseDefaultColors();
+ }
+}
+
+function GetColorAndUpdate(ColorWellID) {
+ // Only allow selecting when in custom mode
+ if (!gDialog.CustomColorsRadio.selected) {
+ return;
+ }
+
+ var colorWell = document.getElementById(ColorWellID);
+ if (!colorWell) {
+ return;
+ }
+
+ // Don't allow a blank color, i.e., using the "default"
+ var colorObj = {
+ NoDefault: true,
+ Type: "",
+ TextColor: 0,
+ PageColor: 0,
+ Cancel: false,
+ };
+
+ switch (ColorWellID) {
+ case "textCW":
+ colorObj.Type = "Text";
+ colorObj.TextColor = customTextColor;
+ break;
+ case "linkCW":
+ colorObj.Type = "Link";
+ colorObj.TextColor = customLinkColor;
+ break;
+ case "activeCW":
+ colorObj.Type = "ActiveLink";
+ colorObj.TextColor = customActiveColor;
+ break;
+ case "visitedCW":
+ colorObj.Type = "VisitedLink";
+ colorObj.TextColor = customVisitedColor;
+ break;
+ case "backgroundCW":
+ colorObj.Type = "Page";
+ colorObj.PageColor = customBackgroundColor;
+ break;
+ }
+
+ window.openDialog(
+ "chrome://editor/content/EdColorPicker.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ "",
+ colorObj
+ );
+
+ // User canceled the dialog
+ if (colorObj.Cancel) {
+ return;
+ }
+
+ var color = "";
+ switch (ColorWellID) {
+ case "textCW":
+ color = customTextColor = colorObj.TextColor;
+ break;
+ case "linkCW":
+ color = customLinkColor = colorObj.TextColor;
+ break;
+ case "activeCW":
+ color = customActiveColor = colorObj.TextColor;
+ break;
+ case "visitedCW":
+ color = customVisitedColor = colorObj.TextColor;
+ break;
+ case "backgroundCW":
+ color = customBackgroundColor = colorObj.BackgroundColor;
+ break;
+ }
+
+ setColorWell(ColorWellID, color);
+ SetColorPreview(ColorWellID, color);
+}
+
+function SetColorPreview(ColorWellID, color) {
+ switch (ColorWellID) {
+ case "textCW":
+ gDialog.NormalText.setAttribute(styleStr, colorStyle + color);
+ break;
+ case "linkCW":
+ gDialog.LinkText.setAttribute(styleStr, colorStyle + color);
+ break;
+ case "activeCW":
+ gDialog.ActiveLinkText.setAttribute(styleStr, colorStyle + color);
+ break;
+ case "visitedCW":
+ gDialog.VisitedLinkText.setAttribute(styleStr, colorStyle + color);
+ break;
+ case "backgroundCW":
+ // Must combine background color and image style values
+ var styleValue = backColorStyle + color;
+ if (gBackgroundImage) {
+ styleValue += ";" + backImageStyle + gBackgroundImage + ");";
+ }
+
+ gDialog.ColorPreview.setAttribute(styleStr, styleValue);
+ previewBGColor = color;
+ break;
+ }
+}
+
+function UseCustomColors() {
+ SetElementEnabledById("TextButton", true);
+ SetElementEnabledById("LinkButton", true);
+ SetElementEnabledById("ActiveLinkButton", true);
+ SetElementEnabledById("VisitedLinkButton", true);
+ SetElementEnabledById("BackgroundButton", true);
+ SetElementEnabledById("Text", true);
+ SetElementEnabledById("Link", true);
+ SetElementEnabledById("Active", true);
+ SetElementEnabledById("Visited", true);
+ SetElementEnabledById("Background", true);
+
+ SetColorPreview("textCW", customTextColor);
+ SetColorPreview("linkCW", customLinkColor);
+ SetColorPreview("activeCW", customActiveColor);
+ SetColorPreview("visitedCW", customVisitedColor);
+ SetColorPreview("backgroundCW", customBackgroundColor);
+
+ setColorWell("textCW", customTextColor);
+ setColorWell("linkCW", customLinkColor);
+ setColorWell("activeCW", customActiveColor);
+ setColorWell("visitedCW", customVisitedColor);
+ setColorWell("backgroundCW", customBackgroundColor);
+}
+
+function UseDefaultColors() {
+ SetColorPreview("textCW", defaultTextColor);
+ SetColorPreview("linkCW", defaultLinkColor);
+ SetColorPreview("activeCW", defaultActiveColor);
+ SetColorPreview("visitedCW", defaultVisitedColor);
+ SetColorPreview("backgroundCW", defaultBackgroundColor);
+
+ // Setting to blank color will remove color from buttons,
+ setColorWell("textCW", "");
+ setColorWell("linkCW", "");
+ setColorWell("activeCW", "");
+ setColorWell("visitedCW", "");
+ setColorWell("backgroundCW", "");
+
+ // Disable color buttons and labels
+ SetElementEnabledById("TextButton", false);
+ SetElementEnabledById("LinkButton", false);
+ SetElementEnabledById("ActiveLinkButton", false);
+ SetElementEnabledById("VisitedLinkButton", false);
+ SetElementEnabledById("BackgroundButton", false);
+ SetElementEnabledById("Text", false);
+ SetElementEnabledById("Link", false);
+ SetElementEnabledById("Active", false);
+ SetElementEnabledById("Visited", false);
+ SetElementEnabledById("Background", false);
+}
+
+function chooseFile() {
+ // Get a local image file, converted into URL format
+ GetLocalFileURL("img").then(fileURL => {
+ // Always try to relativize local file URLs
+ if (gHaveDocumentUrl) {
+ fileURL = MakeRelativeUrl(fileURL);
+ }
+
+ gDialog.BackgroundImageInput.value = fileURL;
+
+ SetRelativeCheckbox();
+ ValidateAndPreviewImage(true);
+ SetTextboxFocus(gDialog.BackgroundImageInput);
+ });
+}
+
+function ChangeBackgroundImage() {
+ // Don't show error message for image while user is typing
+ ValidateAndPreviewImage(false);
+ SetRelativeCheckbox();
+}
+
+function ValidateAndPreviewImage(ShowErrorMessage) {
+ // First make a string with just background color
+ var styleValue = backColorStyle + previewBGColor + ";";
+
+ var retVal = true;
+ var image = TrimString(gDialog.BackgroundImageInput.value);
+ if (image) {
+ if (isImageDataShortened(image)) {
+ gBackgroundImage = restoredImageData(gDialog.BackgroundImageInput);
+ } else {
+ gBackgroundImage = image;
+
+ // Display must use absolute URL if possible
+ var displayImage = gHaveDocumentUrl ? MakeAbsoluteUrl(image) : image;
+ styleValue += backImageStyle + displayImage + ");";
+ }
+ } else {
+ gBackgroundImage = null;
+ }
+
+ // Set style on preview (removes image if not valid)
+ gDialog.ColorPreview.setAttribute(styleStr, styleValue);
+
+ // Note that an "empty" string is valid
+ return retVal;
+}
+
+function ValidateData() {
+ var editor = GetCurrentEditor();
+ try {
+ // Colors values are updated as they are picked, no validation necessary
+ if (gDialog.DefaultColorsRadio.selected) {
+ editor.removeAttributeOrEquivalent(globalElement, textStr, true);
+ globalElement.removeAttribute(linkStr);
+ globalElement.removeAttribute(vlinkStr);
+ globalElement.removeAttribute(alinkStr);
+ editor.removeAttributeOrEquivalent(globalElement, bgcolorStr, true);
+ } else {
+ // Do NOT accept the CSS "WindowsOS" color strings!
+ // Problem: We really should try to get the actual color values
+ // from windows, but I don't know how to do that!
+ var tmpColor = customTextColor.toLowerCase();
+ if (tmpColor != "windowtext") {
+ editor.setAttributeOrEquivalent(
+ globalElement,
+ textStr,
+ customTextColor,
+ true
+ );
+ } else {
+ editor.removeAttributeOrEquivalent(globalElement, textStr, true);
+ }
+
+ tmpColor = customBackgroundColor.toLowerCase();
+ if (tmpColor != "window") {
+ editor.setAttributeOrEquivalent(
+ globalElement,
+ bgcolorStr,
+ customBackgroundColor,
+ true
+ );
+ } else {
+ editor.removeAttributeOrEquivalent(globalElement, bgcolorStr, true);
+ }
+
+ globalElement.setAttribute(linkStr, customLinkColor);
+ globalElement.setAttribute(vlinkStr, customVisitedColor);
+ globalElement.setAttribute(alinkStr, customActiveColor);
+ }
+
+ if (ValidateAndPreviewImage(true)) {
+ // A valid image may be null for no image
+ if (gBackgroundImage) {
+ globalElement.setAttribute(backgroundStr, gBackgroundImage);
+ } else {
+ editor.removeAttributeOrEquivalent(globalElement, backgroundStr, true);
+ }
+
+ return true;
+ }
+ } catch (e) {}
+ return false;
+}
+
+function onAccept(event) {
+ // If it's a file, convert to a data URL.
+ if (gBackgroundImage && /^file:/i.test(gBackgroundImage)) {
+ let nsFile = Services.io
+ .newURI(gBackgroundImage)
+ .QueryInterface(Ci.nsIFileURL).file;
+ if (nsFile.exists()) {
+ let reader = new FileReader();
+ reader.addEventListener("load", function() {
+ gBackgroundImage = reader.result;
+ gDialog.BackgroundImageInput.value = reader.result;
+ if (onAccept(event)) {
+ window.close();
+ }
+ });
+ File.createFromNsIFile(nsFile).then(file => {
+ reader.readAsDataURL(file);
+ });
+ event.preventDefault(); // Don't close just yet...
+ return false;
+ }
+ }
+ if (ValidateData()) {
+ // Copy attributes to element we are changing
+ try {
+ GetCurrentEditor().cloneAttributes(gBodyElement, globalElement);
+ } catch (e) {}
+
+ SaveWindowLocation();
+ return true; // do close the window
+ }
+ event.preventDefault();
+ return false;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdColorProps.xhtml b/comm/suite/editor/components/dialogs/content/EdColorProps.xhtml
new file mode 100644
index 0000000000..85393ed209
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdColorProps.xhtml
@@ -0,0 +1,134 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edColorPropertiesDTD SYSTEM "chrome://editor/locale/EditorColorProperties.dtd">
+%edColorPropertiesDTD;
+<!ENTITY % composeEditorOverlayDTD SYSTEM "chrome://messenger/locale/messengercompose/mailComposeEditorOverlay.dtd">
+%composeEditorOverlayDTD;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdColorProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox align="start">
+ <hbox class="groupbox-title">
+ <label class="header">&pageColors.label;</label>
+ </hbox>
+ <radiogroup id="PageColorGroup">
+ <radio id="DefaultColorsRadio" label="&defaultColorsRadio.label;" oncommand="UseDefaultColors()"
+ accesskey="&defaultColorsRadio.accessKey;"
+ tooltiptext="&defaultColorsRadio.tooltip;" />
+ <radio id="CustomColorsRadio" label="&customColorsRadio.label;" oncommand="UseCustomColors()"
+ accesskey="&customColorsRadio.accessKey;"
+ tooltiptext="&customColorsRadio.tooltip;" />
+ </radiogroup>
+ <hbox class="indent">
+ <grid>
+ <columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label id="Text" control="TextButton"
+ value="&normalText.label;&colon.character;"
+ accesskey="&normalText.accessKey;"/>
+ <button id="TextButton" class="color-button" oncommand="GetColorAndUpdate('textCW');">
+ <spacer id="textCW" class="color-well"/>
+ </button>
+ </row>
+ <row align="center">
+ <label id="Link" control="LinkButton"
+ value="&linkText.label;&colon.character;"
+ accesskey="&linkText.accessKey;"/>
+ <button id="LinkButton" class="color-button" oncommand="GetColorAndUpdate('linkCW');">
+ <spacer id="linkCW" class="color-well"/>
+ </button>
+ </row>
+ <row align="center">
+ <label id="Active" control="ActiveLinkButton"
+ value="&activeLinkText.label;&colon.character;"
+ accesskey="&activeLinkText.accessKey;"/>
+ <button id="ActiveLinkButton" class="color-button" oncommand="GetColorAndUpdate('activeCW');">
+ <spacer id="activeCW" class="color-well"/>
+ </button>
+ </row>
+ <row align="center">
+ <label id="Visited" control="VisitedLinkButton"
+ value="&visitedLinkText.label;&colon.character;"
+ accesskey="&visitedLinkText.accessKey;"/>
+ <button id="VisitedLinkButton" class="color-button" oncommand="GetColorAndUpdate('visitedCW');">
+ <spacer id="visitedCW" class="color-well"/>
+ </button>
+ </row>
+ <row align="center">
+ <label id="Background" control="BackgroundButton"
+ value="&background.label;"
+ accesskey="&background.accessKey;"/>
+ <button id="BackgroundButton" class="color-button" oncommand="GetColorAndUpdate('backgroundCW');">
+ <spacer id="backgroundCW" class="color-well"/>
+ </button>
+ </row>
+ </rows>
+ </grid>
+ <vbox id="ColorPreview" flex="1">
+ <spacer flex="1"/>
+ <label class="larger" id="NormalText" value="&normalText.label;"/>
+ <spacer flex="1"/>
+ <label class="larger" id="LinkText" value="&linkText.label;"/>
+ <spacer flex="1"/>
+ <label class="larger" id="ActiveLinkText" value="&activeLinkText.label;"/>
+ <spacer flex="1"/>
+ <label class="larger" id="VisitedLinkText" value="&visitedLinkText.label;"/>
+ <spacer flex="1"/>
+ </vbox>
+ <spacer flex="1"/>
+ </hbox>
+ <spacer class="spacer"/>
+ </groupbox>
+ <spacer class="spacer"/>
+ <label control="BackgroundImageInput"
+ value="&backgroundImage.label;"
+ tooltiptext="&backgroundImage.tooltip;"
+ accesskey="&backgroundImage.accessKey;"/>
+ <tooltip id="shortenedDataURI">
+ <label value="&backgroundImage.shortenedDataURI;"/>
+ </tooltip>
+ <textbox id="BackgroundImageInput" class="uri-element" oninput="ChangeBackgroundImage()"
+ tooltiptext="&backgroundImage.tooltip;" flex="1"/>
+ <hbox align="center">
+ <checkbox id="MakeRelativeCheckbox"
+ for="BackgroundImageInput"
+ label="&makeUrlRelative.label;"
+ accesskey="&makeUrlRelative.accessKey;"
+ oncommand="MakeInputValueRelativeOrAbsolute(this);"
+ tooltiptext="&makeUrlRelative.tooltip;"/>
+ <spacer flex="1"/>
+ <button id="ChooseFile"
+ oncommand="chooseFile()"
+ label="&chooseFileButton.label;"
+ accesskey="&chooseFileButton.accessKey;"/>
+ </hbox>
+ <spacer class="smallspacer"/>
+ <hbox>
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdConvertToTable.js b/comm/suite/editor/components/dialogs/content/EdConvertToTable.js
new file mode 100644
index 0000000000..a149e708f8
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdConvertToTable.js
@@ -0,0 +1,326 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+var gIndex;
+var gCommaIndex = "0";
+var gSpaceIndex = "1";
+var gOtherIndex = "2";
+
+// dialog initialization code
+function Startup() {
+ if (!GetCurrentEditor()) {
+ window.close();
+ return;
+ }
+
+ gDialog.sepRadioGroup = document.getElementById("SepRadioGroup");
+ gDialog.sepCharacterInput = document.getElementById("SepCharacterInput");
+ gDialog.deleteSepCharacter = document.getElementById("DeleteSepCharacter");
+ gDialog.collapseSpaces = document.getElementById("CollapseSpaces");
+
+ // We persist the user's separator character
+ gDialog.sepCharacterInput.value = gDialog.sepRadioGroup.getAttribute(
+ "character"
+ );
+
+ gIndex = gDialog.sepRadioGroup.getAttribute("index");
+
+ switch (gIndex) {
+ case gCommaIndex:
+ default:
+ gDialog.sepRadioGroup.selectedItem = document.getElementById("comma");
+ break;
+ case gSpaceIndex:
+ gDialog.sepRadioGroup.selectedItem = document.getElementById("space");
+ break;
+ case gOtherIndex:
+ gDialog.sepRadioGroup.selectedItem = document.getElementById("other");
+ break;
+ }
+
+ // Set initial enable state on character input and "collapse" checkbox
+ SelectCharacter(gIndex);
+
+ SetWindowLocation();
+}
+
+function InputSepCharacter() {
+ var str = gDialog.sepCharacterInput.value;
+
+ // Limit input to 1 character
+ if (str.length > 1) {
+ str = str.slice(0, 1);
+ }
+
+ // We can never allow tag or entity delimiters for separator character
+ if (str == "<" || str == ">" || str == "&" || str == ";" || str == " ") {
+ str = "";
+ }
+
+ gDialog.sepCharacterInput.value = str;
+}
+
+function SelectCharacter(radioGroupIndex) {
+ gIndex = radioGroupIndex;
+ SetElementEnabledById("SepCharacterInput", gIndex == gOtherIndex);
+ SetElementEnabledById("CollapseSpaces", gIndex == gSpaceIndex);
+}
+
+/* eslint-disable complexity */
+function onAccept() {
+ var sepCharacter = "";
+ switch (gIndex) {
+ case gCommaIndex:
+ sepCharacter = ",";
+ break;
+ case gSpaceIndex:
+ sepCharacter = " ";
+ break;
+ case gOtherIndex:
+ sepCharacter = gDialog.sepCharacterInput.value.slice(0, 1);
+ break;
+ }
+
+ var editor = GetCurrentEditor();
+ var str;
+ try {
+ str = editor.outputToString(
+ "text/html",
+ kOutputLFLineBreak | kOutputSelectionOnly
+ );
+ } catch (e) {}
+ if (!str) {
+ SaveWindowLocation();
+ return;
+ }
+
+ // Replace nbsp with spaces:
+ str = str.replace(/\u00a0/g, " ");
+
+ // Strip out </p> completely
+ str = str.replace(/\s*<\/p>\s*/g, "");
+
+ // Trim whitespace adjacent to <p> and <br> tags
+ // and replace <p> with <br>
+ // (which will be replaced with </tr> below)
+ str = str.replace(/\s*<p>\s*|\s*<br>\s*/g, "<br>");
+
+ // Trim leading <br>s
+ str = str.replace(/^(<br>)+/, "");
+
+ // Trim trailing <br>s
+ str = str.replace(/(<br>)+$/, "");
+
+ // Reduce multiple internal <br> to just 1
+ // TODO: Maybe add a checkbox to let user decide
+ // str = str.replace(/(<br>)+/g, "<br>");
+
+ // Trim leading and trailing spaces
+ str = str.trim();
+
+ // Remove all tag contents so we don't replace
+ // separator character within tags
+ // Also converts lists to something useful
+ var stack = [];
+ var start;
+ var end;
+ var searchStart = 0;
+ var listSeparator = "";
+ var listItemSeparator = "";
+ var endList = false;
+
+ do {
+ start = str.indexOf("<", searchStart);
+
+ if (start >= 0) {
+ end = str.indexOf(">", start + 1);
+ if (end > start) {
+ let tagContent = str.slice(start + 1, end).trim();
+
+ if (/^ol|^ul|^dl/.test(tagContent)) {
+ // Replace list tag with <BR> to start new row
+ // at beginning of second or greater list tag
+ str = str.slice(0, start) + listSeparator + str.slice(end + 1);
+ if (listSeparator == "") {
+ listSeparator = "<br>";
+ }
+
+ // Reset for list item separation into cells
+ listItemSeparator = "";
+ } else if (/^li|^dt|^dd/.test(tagContent)) {
+ // Start a new row if this is first item after the ending the last list
+ if (endList) {
+ listItemSeparator = "<br>";
+ }
+
+ // Start new cell at beginning of second or greater list items
+ str = str.slice(0, start) + listItemSeparator + str.slice(end + 1);
+
+ if (endList || listItemSeparator == "") {
+ listItemSeparator = sepCharacter;
+ }
+
+ endList = false;
+ } else {
+ // Find end tags
+ endList = /^\/ol|^\/ul|^\/dl/.test(tagContent);
+ if (endList || /^\/li|^\/dt|^\/dd/.test(tagContent)) {
+ // Strip out tag
+ str = str.slice(0, start) + str.slice(end + 1);
+ } else {
+ // Not a list-related tag: Store tag contents in an array
+ stack.push(tagContent);
+
+ // Keep the "<" and ">" while removing from source string
+ start++;
+ str = str.slice(0, start) + str.slice(end);
+ }
+ }
+ }
+ searchStart = start + 1;
+ }
+ } while (start >= 0);
+
+ // Replace separator characters with table cells
+ var replaceString;
+ if (gDialog.deleteSepCharacter.checked) {
+ replaceString = "";
+ } else {
+ // Don't delete separator character,
+ // so include it at start of string to replace
+ replaceString = sepCharacter;
+ }
+
+ replaceString += "<td>";
+
+ if (sepCharacter.length > 0) {
+ var tempStr = sepCharacter;
+ var regExpChars = ".!@#$%^&*-+[]{}()|\\/";
+ if (regExpChars.includes(sepCharacter)) {
+ tempStr = "\\" + sepCharacter;
+ }
+
+ if (gIndex == gSpaceIndex) {
+ // If checkbox is checked,
+ // one or more adjacent spaces are one separator
+ if (gDialog.collapseSpaces.checked) {
+ tempStr = "\\s+";
+ } else {
+ tempStr = "\\s";
+ }
+ }
+ var pattern = new RegExp(tempStr, "g");
+ str = str.replace(pattern, replaceString);
+ }
+
+ // Put back tag contents that we removed above
+ searchStart = 0;
+ var stackIndex = 0;
+ do {
+ start = str.indexOf("<", searchStart);
+ end = start + 1;
+ if (start >= 0 && str.charAt(end) == ">") {
+ // We really need a FIFO stack!
+ str = str.slice(0, end) + stack[stackIndex++] + str.slice(end);
+ }
+ searchStart = end;
+ } while (start >= 0);
+
+ // End table row and start another for each br or p
+ str = str.replace(/\s*<br>\s*/g, "</tr>\n<tr><td>");
+
+ // Add the table tags and the opening and closing tr/td tags
+ // Default table attributes should be same as those used in nsHTMLEditor::CreateElementWithDefaults()
+ // (Default width="100%" is used in EdInsertTable.js)
+ str =
+ '<table border="1" width="100%" cellpadding="2" cellspacing="2">\n<tr><td>' +
+ str +
+ "</tr>\n</table>\n";
+
+ editor.beginTransaction();
+
+ // Delete the selection -- makes it easier to find where table will insert
+ var nodeBeforeTable = null;
+ var nodeAfterTable = null;
+ try {
+ editor.deleteSelection(editor.eNone, editor.eStrip);
+
+ var anchorNodeBeforeInsert = editor.selection.anchorNode;
+ var offset = editor.selection.anchorOffset;
+ if (anchorNodeBeforeInsert.nodeType == Node.TEXT_NODE) {
+ // Text was split. Table should be right after the first or before
+ nodeBeforeTable = anchorNodeBeforeInsert.previousSibling;
+ nodeAfterTable = anchorNodeBeforeInsert;
+ } else {
+ // Table should be inserted right after node pointed to by selection
+ if (offset > 0) {
+ nodeBeforeTable = anchorNodeBeforeInsert.childNodes.item(offset - 1);
+ }
+
+ nodeAfterTable = anchorNodeBeforeInsert.childNodes.item(offset);
+ }
+
+ editor.insertHTML(str);
+ } catch (e) {}
+
+ var table = null;
+ if (nodeAfterTable) {
+ var previous = nodeAfterTable.previousSibling;
+ if (previous && previous.nodeName.toLowerCase() == "table") {
+ table = previous;
+ }
+ }
+ if (!table && nodeBeforeTable) {
+ var next = nodeBeforeTable.nextSibling;
+ if (next && next.nodeName.toLowerCase() == "table") {
+ table = next;
+ }
+ }
+
+ if (table) {
+ // Fixup table only if pref is set
+ var firstRow;
+ try {
+ if (Services.prefs.getBoolPref("editor.table.maintain_structure")) {
+ editor.normalizeTable(table);
+ }
+
+ firstRow = editor.getFirstRow(table);
+ } catch (e) {}
+
+ // Put caret in first cell
+ if (firstRow) {
+ var node2 = firstRow.firstChild;
+ do {
+ if (
+ node2.nodeName.toLowerCase() == "td" ||
+ node2.nodeName.toLowerCase() == "th"
+ ) {
+ try {
+ editor.selection.collapse(node2, 0);
+ } catch (e) {}
+ break;
+ }
+ node2 = node2.nextSibling;
+ } while (node2);
+ }
+ }
+
+ editor.endTransaction();
+
+ // Save persisted attributes
+ gDialog.sepRadioGroup.setAttribute("index", gIndex);
+ if (gIndex == gOtherIndex) {
+ gDialog.sepRadioGroup.setAttribute("character", sepCharacter);
+ }
+
+ SaveWindowLocation();
+}
+/* eslint-enable complexity */
diff --git a/comm/suite/editor/components/dialogs/content/EdConvertToTable.xhtml b/comm/suite/editor/components/dialogs/content/EdConvertToTable.xhtml
new file mode 100644
index 0000000000..d3d5c4a465
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdConvertToTable.xhtml
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EdConvertToTable.dtd">
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload = "Startup()"
+ style="min-width:20em">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <!--- Element-specific methods -->
+ <script src="chrome://editor/content/EdConvertToTable.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+ <description class="wrap" flex="1">&instructions1.label;</description>
+ <description class="wrap" flex="1">&instructions2.label;</description>
+ <radiogroup id="SepRadioGroup" persist="index character" index="0" character="">
+ <radio id="comma" label="&commaRadio.label;" oncommand="SelectCharacter('0');"/>
+ <radio id="space" label="&spaceRadio.label;" oncommand="SelectCharacter('1');"/>
+ <hbox>
+ <spacer class="radio-spacer"/>
+ <checkbox id="CollapseSpaces" label="&collapseSpaces.label;"
+ checked="true" persist="checked"
+ tooltiptext="&collapseSpaces.tooltip;"/>
+ </hbox>
+ <hbox align="center">
+ <radio id="other" label="&otherRadio.label;" oncommand="SelectCharacter('2');"/>
+ <textbox class="narrow" id="SepCharacterInput" oninput="InputSepCharacter()"/>
+ </hbox>
+ </radiogroup>
+ <spacer class="spacer"/>
+ <checkbox id="DeleteSepCharacter" label="&deleteCharCheck.label;" persist="checked"/>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdDialogCommon.js b/comm/suite/editor/components/dialogs/content/EdDialogCommon.js
new file mode 100644
index 0000000000..c6b7c63778
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdDialogCommon.js
@@ -0,0 +1,1038 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Each editor window must include this file
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* globals InitDialog, ChangeLinkLocation, ValidateData */
+
+// Object to attach commonly-used widgets (all dialogs should use this)
+var gDialog = {};
+
+var gHaveDocumentUrl = false;
+var gValidationError = false;
+
+// Use for 'defaultIndex' param in InitPixelOrPercentMenulist
+const gPixel = 0;
+const gPercent = 1;
+
+const gMaxPixels = 100000; // Used for image size, borders, spacing, and padding
+// Gecko code uses 1000 for maximum rowspan, colspan
+// Also, editing performance is really bad above this
+const gMaxRows = 1000;
+const gMaxColumns = 1000;
+const gMaxTableSize = 1000000; // Width or height of table or cells
+
+// For dialogs that expand in size. Default is smaller size see "onMoreFewer()" below
+var SeeMore = false;
+
+// A XUL element with id="location" for managing
+// dialog location relative to parent window
+var gLocation;
+
+// The element being edited - so AdvancedEdit can have access to it
+var globalElement;
+
+/* Validate contents of an input field
+ *
+ * inputWidget The 'textbox' XUL element for text input of the attribute's value
+ * listWidget The 'menulist' XUL element for choosing "pixel" or "percent"
+ * May be null when no pixel/percent is used.
+ * minVal minimum allowed for input widget's value
+ * maxVal maximum allowed for input widget's value
+ * (when "listWidget" is used, maxVal is used for "pixel" maximum,
+ * 100% is assumed if "percent" is the user's choice)
+ * element The DOM element that we set the attribute on. May be null.
+ * attName Name of the attribute to set. May be null or ignored if "element" is null
+ * mustHaveValue If true, error dialog is displayed if "value" is empty string
+ *
+ * This calls "ValidateNumberRange()", which puts up an error dialog to inform the user.
+ * If error, we also:
+ * Shift focus and select contents of the inputWidget,
+ * Switch to appropriate panel of tabbed dialog if user implements "SwitchToValidate()",
+ * and/or will expand the dialog to full size if "More / Fewer" feature is implemented
+ *
+ * Returns the "value" as a string, or "" if error or input contents are empty
+ * The global "gValidationError" variable is set true if error was found
+ */
+function ValidateNumber(
+ inputWidget,
+ listWidget,
+ minVal,
+ maxVal,
+ element,
+ attName,
+ mustHaveValue,
+ mustShowMoreSection
+) {
+ if (!inputWidget) {
+ gValidationError = true;
+ return "";
+ }
+
+ // Global error return value
+ gValidationError = false;
+ var maxLimit = maxVal;
+ var isPercent = false;
+
+ var numString = TrimString(inputWidget.value);
+ if (numString || mustHaveValue) {
+ if (listWidget) {
+ isPercent = listWidget.selectedIndex == 1;
+ }
+ if (isPercent) {
+ maxLimit = 100;
+ }
+
+ // This method puts up the error message
+ numString = ValidateNumberRange(numString, minVal, maxLimit, mustHaveValue);
+ if (!numString) {
+ // Switch to appropriate panel for error reporting
+ SwitchToValidatePanel();
+
+ // or expand dialog for users of "More / Fewer" button
+ if (
+ "dialog" in window &&
+ window.dialog &&
+ "MoreSection" in gDialog &&
+ gDialog.MoreSection
+ ) {
+ if (!SeeMore) {
+ onMoreFewer();
+ }
+ }
+
+ // Error - shift to offending input widget
+ SetTextboxFocus(inputWidget);
+ gValidationError = true;
+ } else {
+ if (isPercent) {
+ numString += "%";
+ }
+ if (element) {
+ GetCurrentEditor().setAttributeOrEquivalent(
+ element,
+ attName,
+ numString,
+ true
+ );
+ }
+ }
+ } else if (element) {
+ GetCurrentEditor().removeAttributeOrEquivalent(element, attName, true);
+ }
+ return numString;
+}
+
+/* Validate contents of an input field
+ *
+ * value number to validate
+ * minVal minimum allowed for input widget's value
+ * maxVal maximum allowed for input widget's value
+ * (when "listWidget" is used, maxVal is used for "pixel" maximum,
+ * 100% is assumed if "percent" is the user's choice)
+ * mustHaveValue If true, error dialog is displayed if "value" is empty string
+ *
+ * If inputWidget's value is outside of range, or is empty when "mustHaveValue" = true,
+ * an error dialog is popuped up to inform the user. The focus is shifted
+ * to the inputWidget.
+ *
+ * Returns the "value" as a string, or "" if error or input contents are empty
+ * The global "gValidationError" variable is set true if error was found
+ */
+function ValidateNumberRange(value, minValue, maxValue, mustHaveValue) {
+ // Initialize global error flag
+ gValidationError = false;
+ value = TrimString(String(value));
+
+ // We don't show error for empty string unless caller wants to
+ if (!value && !mustHaveValue) {
+ return "";
+ }
+
+ var numberStr = "";
+
+ if (value.length > 0) {
+ // Extract just numeric characters
+ var number = Number(value.replace(/\D+/g, ""));
+ if (number >= minValue && number <= maxValue) {
+ // Return string version of the number
+ return String(number);
+ }
+ numberStr = String(number);
+ }
+
+ var message = "";
+
+ if (numberStr.length > 0) {
+ // We have a number from user outside of allowed range
+ message = GetString("ValidateRangeMsg");
+ message = message.replace(/%n%/, numberStr);
+ message += "\n ";
+ }
+ message += GetString("ValidateNumberMsg");
+
+ // Replace variable placeholders in message with number values
+ message = message.replace(/%min%/, minValue).replace(/%max%/, maxValue);
+ ShowInputErrorMessage(message);
+
+ // Return an empty string to indicate error
+ gValidationError = true;
+ return "";
+}
+
+function SetTextboxFocusById(id) {
+ SetTextboxFocus(document.getElementById(id));
+}
+
+function SetTextboxFocus(textbox) {
+ if (textbox) {
+ // XXX Using the setTimeout is hacky workaround for bug 103197
+ // Must create a new function to keep "textbox" in scope
+ setTimeout(
+ function(textbox) {
+ textbox.focus();
+ textbox.select();
+ },
+ 0,
+ textbox
+ );
+ }
+}
+
+function ShowInputErrorMessage(message) {
+ Services.prompt.alert(window, GetString("InputError"), message);
+ window.focus();
+}
+
+// Get the text appropriate to parent container
+// to determine what a "%" value is referring to.
+// elementForAtt is element we are actually setting attributes on
+// (a temporary copy of element in the doc to allow canceling),
+// but elementInDoc is needed to find parent context in document
+function GetAppropriatePercentString(elementForAtt, elementInDoc) {
+ var editor = GetCurrentEditor();
+ try {
+ var name = elementForAtt.nodeName.toLowerCase();
+ if (name == "td" || name == "th") {
+ return GetString("PercentOfTable");
+ }
+
+ // Check if element is within a table cell
+ if (editor.getElementOrParentByTagName("td", elementInDoc)) {
+ return GetString("PercentOfCell");
+ }
+ return GetString("PercentOfWindow");
+ } catch (e) {
+ return "";
+ }
+}
+
+function ClearListbox(listbox) {
+ if (listbox) {
+ listbox.clearSelection();
+ while (listbox.hasChildNodes()) {
+ listbox.lastChild.remove();
+ }
+ }
+}
+
+function forceInteger(elementID) {
+ var editField = document.getElementById(elementID);
+ if (!editField) {
+ return;
+ }
+
+ var stringIn = editField.value;
+ if (stringIn && stringIn.length > 0) {
+ // Strip out all nonnumeric characters
+ stringIn = stringIn.replace(/\D+/g, "");
+ if (!stringIn) {
+ stringIn = "";
+ }
+
+ // Write back only if changed
+ if (stringIn != editField.value) {
+ editField.value = stringIn;
+ }
+ }
+}
+
+function InitPixelOrPercentMenulist(
+ elementForAtt,
+ elementInDoc,
+ attribute,
+ menulistID,
+ defaultIndex
+) {
+ if (!defaultIndex) {
+ defaultIndex = gPixel;
+ }
+
+ // var size = elementForAtt.getAttribute(attribute);
+ var size = GetHTMLOrCSSStyleValue(elementForAtt, attribute, attribute);
+ var menulist = document.getElementById(menulistID);
+ var pixelItem;
+ var percentItem;
+
+ if (!menulist) {
+ dump("NO MENULIST found for ID=" + menulistID + "\n");
+ return size;
+ }
+
+ menulist.removeAllItems();
+ pixelItem = menulist.appendItem(GetString("Pixels"));
+
+ if (!pixelItem) {
+ return 0;
+ }
+
+ percentItem = menulist.appendItem(
+ GetAppropriatePercentString(elementForAtt, elementInDoc)
+ );
+ if (size && size.length > 0) {
+ // Search for a "%" or "px"
+ if (size.includes("%")) {
+ // Strip out the %
+ size = size.substr(0, size.indexOf("%"));
+ if (percentItem) {
+ menulist.selectedItem = percentItem;
+ }
+ } else {
+ if (size.includes("px")) {
+ // Strip out the px
+ size = size.substr(0, size.indexOf("px"));
+ }
+ menulist.selectedItem = pixelItem;
+ }
+ } else {
+ menulist.selectedIndex = defaultIndex;
+ }
+
+ return size;
+}
+
+function onAdvancedEdit() {
+ // First validate data from widgets in the "simpler" property dialog
+ if (ValidateData()) {
+ // Set true if OK is clicked in the Advanced Edit dialog
+ window.AdvancedEditOK = false;
+ // Open the AdvancedEdit dialog, passing in the element to be edited
+ // (the copy named "globalElement")
+ window.openDialog(
+ "chrome://editor/content/EdAdvancedEdit.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal,resizable=yes",
+ "",
+ globalElement
+ );
+ window.focus();
+ if (window.AdvancedEditOK) {
+ // Copy edited attributes to the dialog widgets:
+ InitDialog();
+ }
+ }
+}
+
+function getColor(ColorPickerID) {
+ var colorPicker = document.getElementById(ColorPickerID);
+ var color;
+ if (colorPicker) {
+ // Extract color from colorPicker and assign to colorWell.
+ color = colorPicker.getAttribute("color");
+ if (color && color == "") {
+ return null;
+ }
+ // Clear color so next if it's called again before
+ // color picker is actually used, we dedect the "don't set color" state
+ colorPicker.setAttribute("color", "");
+ }
+
+ return color;
+}
+
+function setColorWell(ColorWellID, color) {
+ var colorWell = document.getElementById(ColorWellID);
+ if (colorWell) {
+ if (!color || color == "") {
+ // Don't set color (use default)
+ // Trigger change to not show color swatch
+ colorWell.setAttribute("default", "true");
+ // Style in CSS sets "background-color",
+ // but color won't clear unless we do this:
+ colorWell.removeAttribute("style");
+ } else {
+ colorWell.removeAttribute("default");
+ // Use setAttribute so colorwell can be a XUL element, such as button
+ colorWell.setAttribute("style", "background-color:" + color);
+ }
+ }
+}
+
+function getColorAndSetColorWell(ColorPickerID, ColorWellID) {
+ var color = getColor(ColorPickerID);
+ setColorWell(ColorWellID, color);
+ return color;
+}
+
+function InitMoreFewer() {
+ // Set SeeMore bool to the OPPOSITE of the current state,
+ // which is automatically saved by using the 'persist="more"'
+ // attribute on the gDialog.MoreFewerButton button
+ // onMoreFewer will toggle it and redraw the dialog
+ SeeMore = gDialog.MoreFewerButton.getAttribute("more") != "1";
+ onMoreFewer();
+ gDialog.MoreFewerButton.setAttribute(
+ "accesskey",
+ GetString("PropertiesAccessKey")
+ );
+}
+
+function onMoreFewer() {
+ if (SeeMore) {
+ gDialog.MoreSection.collapsed = true;
+ gDialog.MoreFewerButton.setAttribute("more", "0");
+ gDialog.MoreFewerButton.setAttribute("label", GetString("MoreProperties"));
+ SeeMore = false;
+ } else {
+ gDialog.MoreSection.collapsed = false;
+ gDialog.MoreFewerButton.setAttribute("more", "1");
+ gDialog.MoreFewerButton.setAttribute("label", GetString("FewerProperties"));
+ SeeMore = true;
+ }
+ window.sizeToContent();
+}
+
+function SwitchToValidatePanel() {
+ // no default implementation
+ // Only EdTableProps.js currently implements this
+}
+
+const nsIFilePicker = Ci.nsIFilePicker;
+
+/**
+ * @return {Promise} URL spec of the file chosen, or null
+ */
+function GetLocalFileURL(filterType) {
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ var fileType = "html";
+
+ if (filterType == "img") {
+ fp.init(window, GetString("SelectImageFile"), nsIFilePicker.modeOpen);
+ fp.appendFilters(nsIFilePicker.filterImages);
+ fileType = "image";
+ } else if (filterType.startsWith("html")) {
+ // Current usage of this is in Link dialog,
+ // where we always want HTML first
+ fp.init(window, GetString("OpenHTMLFile"), nsIFilePicker.modeOpen);
+
+ // When loading into Composer, direct user to prefer HTML files and text files,
+ // so we call separately to control the order of the filter list
+ fp.appendFilters(nsIFilePicker.filterHTML);
+ fp.appendFilters(nsIFilePicker.filterText);
+
+ // Link dialog also allows linking to images
+ if (filterType.includes("img", 1)) {
+ fp.appendFilters(nsIFilePicker.filterImages);
+ }
+ }
+ // Default or last filter is "All Files"
+ fp.appendFilters(nsIFilePicker.filterAll);
+
+ // set the file picker's current directory to last-opened location saved in prefs
+ SetFilePickerDirectory(fp, fileType);
+
+ return new Promise(resolve => {
+ fp.open(rv => {
+ if (rv != nsIFilePicker.returnOK || !fp.file) {
+ resolve(null);
+ return;
+ }
+ SaveFilePickerDirectory(fp, fileType);
+ resolve(fp.fileURL.spec);
+ });
+ });
+}
+
+function GetMetaElementByAttribute(name, value) {
+ if (name) {
+ name = name.toLowerCase();
+ let editor = GetCurrentEditor();
+ try {
+ return editor.document.querySelector(
+ "meta[" + name + '="' + value + '"]'
+ );
+ } catch (e) {}
+ }
+ return null;
+}
+
+function CreateMetaElementWithAttribute(name, value) {
+ let editor = GetCurrentEditor();
+ try {
+ let metaElement = editor.createElementWithDefaults("meta");
+ if (name) {
+ metaElement.setAttribute(name, value);
+ }
+ return metaElement;
+ } catch (e) {}
+ return null;
+}
+
+// Change "content" attribute on a META element,
+// or delete entire element it if content is empty
+// This uses undoable editor transactions
+function SetMetaElementContent(metaElement, content, insertNew, prepend) {
+ if (metaElement) {
+ var editor = GetCurrentEditor();
+ try {
+ if (!content || content == "") {
+ if (!insertNew) {
+ editor.deleteNode(metaElement);
+ }
+ } else if (insertNew) {
+ metaElement.setAttribute("content", content);
+ if (prepend) {
+ PrependHeadElement(metaElement);
+ } else {
+ AppendHeadElement(metaElement);
+ }
+ } else {
+ editor.setAttribute(metaElement, "content", content);
+ }
+ } catch (e) {}
+ }
+}
+
+function GetHeadElement() {
+ var editor = GetCurrentEditor();
+ try {
+ return editor.document.querySelector("head");
+ } catch (e) {}
+
+ return null;
+}
+
+function PrependHeadElement(element) {
+ var head = GetHeadElement();
+ if (head) {
+ var editor = GetCurrentEditor();
+ try {
+ // Use editor's undoable transaction
+ // XXX Here tried to prevent updating Selection with unknown 4th argument,
+ // but nsIEditor.setShouldTxnSetSelection is not used for that.
+ editor.insertNode(element, head, 0);
+ } catch (e) {}
+ }
+}
+
+function AppendHeadElement(element) {
+ var head = GetHeadElement();
+ if (head) {
+ var position = 0;
+ if (head.hasChildNodes()) {
+ position = head.childNodes.length;
+ }
+
+ var editor = GetCurrentEditor();
+ try {
+ // Use editor's undoable transaction
+ // XXX Here tried to prevent updating Selection with unknown 4th argument,
+ // but nsIEditor.setShouldTxnSetSelection is not used for that.
+ editor.insertNode(element, head, position);
+ } catch (e) {}
+ }
+}
+
+function SetWindowLocation() {
+ gLocation = document.getElementById("location");
+ if (gLocation) {
+ window.screenX = Math.max(
+ 0,
+ Math.min(
+ window.opener.screenX + Number(gLocation.getAttribute("offsetX")),
+ screen.availWidth - window.outerWidth
+ )
+ );
+ window.screenY = Math.max(
+ 0,
+ Math.min(
+ window.opener.screenY + Number(gLocation.getAttribute("offsetY")),
+ screen.availHeight - window.outerHeight
+ )
+ );
+ }
+}
+
+function SaveWindowLocation() {
+ if (gLocation) {
+ gLocation.setAttribute("offsetX", window.screenX - window.opener.screenX);
+ gLocation.setAttribute("offsetY", window.screenY - window.opener.screenY);
+ }
+}
+
+function onCancel() {
+ SaveWindowLocation();
+}
+
+function SetRelativeCheckbox(checkbox) {
+ if (!checkbox) {
+ checkbox = document.getElementById("MakeRelativeCheckbox");
+ if (!checkbox) {
+ return;
+ }
+ }
+
+ var editor = GetCurrentEditor();
+ // Mail never allows relative URLs, so hide the checkbox
+ if (editor && editor.flags & Ci.nsIEditor.eEditorMailMask) {
+ checkbox.collapsed = true;
+ return;
+ }
+
+ var input = document.getElementById(checkbox.getAttribute("for"));
+ if (!input) {
+ return;
+ }
+
+ var url = TrimString(input.value);
+ var urlScheme = GetScheme(url);
+
+ // Check it if url is relative (no scheme).
+ checkbox.checked = url.length > 0 && !urlScheme;
+
+ // Now do checkbox enabling:
+ var enable = false;
+
+ var docUrl = GetDocumentBaseUrl();
+ var docScheme = GetScheme(docUrl);
+
+ if (url && docUrl && docScheme) {
+ if (urlScheme) {
+ // Url is absolute
+ // If we can make a relative URL, then enable must be true!
+ // (this lets the smarts of MakeRelativeUrl do all the hard work)
+ enable = GetScheme(MakeRelativeUrl(url)).length == 0;
+ } else if (url[0] == "#") {
+ // Url is relative
+ // Check if url is a named anchor
+ // but document doesn't have a filename
+ // (it's probably "index.html" or "index.htm",
+ // but we don't want to allow a malformed URL)
+ var docFilename = GetFilename(docUrl);
+ enable = docFilename.length > 0;
+ } else {
+ // Any other url is assumed
+ // to be ok to try to make absolute
+ enable = true;
+ }
+ }
+
+ SetElementEnabled(checkbox, enable);
+}
+
+// oncommand handler for the Relativize checkbox in EditorOverlay.xhtml
+function MakeInputValueRelativeOrAbsolute(checkbox) {
+ var input = document.getElementById(checkbox.getAttribute("for"));
+ if (!input) {
+ return;
+ }
+
+ var docUrl = GetDocumentBaseUrl();
+ if (!docUrl) {
+ // Checkbox should be disabled if not saved,
+ // but keep this error message in case we change that
+ Services.prompt.alert(window, "", GetString("SaveToUseRelativeUrl"));
+ window.focus();
+ } else {
+ // Note that "checked" is opposite of its last state,
+ // which determines what we want to do here
+ if (checkbox.checked) {
+ input.value = MakeRelativeUrl(input.value);
+ } else {
+ input.value = MakeAbsoluteUrl(input.value);
+ }
+
+ // Reset checkbox to reflect url state
+ SetRelativeCheckbox(checkbox);
+ }
+}
+
+var IsBlockParent = [
+ "applet",
+ "blockquote",
+ "body",
+ "center",
+ "dd",
+ "div",
+ "form",
+ "li",
+ "noscript",
+ "object",
+ "td",
+ "th",
+];
+
+var NotAnInlineParent = [
+ "col",
+ "colgroup",
+ "dl",
+ "dir",
+ "menu",
+ "ol",
+ "table",
+ "tbody",
+ "tfoot",
+ "thead",
+ "tr",
+ "ul",
+];
+
+function nodeIsBreak(editor, node) {
+ return !node || node.localName == "br" || editor.nodeIsBlock(node);
+}
+
+function InsertElementAroundSelection(element) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+
+ try {
+ // First get the selection as a single range
+ var range, start, end, offset;
+ var count = editor.selection.rangeCount;
+ if (count == 1) {
+ range = editor.selection.getRangeAt(0).cloneRange();
+ } else {
+ range = editor.document.createRange();
+ start = editor.selection.getRangeAt(0);
+ range.setStart(start.startContainer, start.startOffset);
+ end = editor.selection.getRangeAt(--count);
+ range.setEnd(end.endContainer, end.endOffset);
+ }
+
+ // Flatten the selection to child nodes of the common ancestor
+ while (range.startContainer != range.commonAncestorContainer) {
+ range.setStartBefore(range.startContainer);
+ }
+ while (range.endContainer != range.commonAncestorContainer) {
+ range.setEndAfter(range.endContainer);
+ }
+
+ if (editor.nodeIsBlock(element)) {
+ // Block element parent must be a valid block
+ while (!IsBlockParent.includes(range.commonAncestorContainer.localName)) {
+ range.selectNode(range.commonAncestorContainer);
+ }
+ } else {
+ if (!nodeIsBreak(editor, range.commonAncestorContainer)) {
+ // Fail if we're not inserting a block (use setInlineProperty instead)
+ return false;
+ }
+ if (NotAnInlineParent.includes(range.commonAncestorContainer.localName)) {
+ // Inline element parent must not be an invalid block
+ do {
+ range.selectNode(range.commonAncestorContainer);
+ } while (
+ NotAnInlineParent.includes(range.commonAncestorContainer.localName)
+ );
+ } else {
+ // Further insert block check
+ for (var i = range.startOffset; ; i++) {
+ if (i == range.endOffset) {
+ return false;
+ }
+ if (
+ nodeIsBreak(editor, range.commonAncestorContainer.childNodes[i])
+ ) {
+ break;
+ }
+ }
+ }
+ }
+
+ // The range may be contained by body text, which should all be selected.
+ offset = range.startOffset;
+ start = range.startContainer.childNodes[offset];
+ if (!nodeIsBreak(editor, start)) {
+ while (!nodeIsBreak(editor, start.previousSibling)) {
+ start = start.previousSibling;
+ offset--;
+ }
+ }
+ end = range.endContainer.childNodes[range.endOffset];
+ if (end && !nodeIsBreak(editor, end.previousSibling)) {
+ while (!nodeIsBreak(editor, end)) {
+ end = end.nextSibling;
+ }
+ }
+
+ // Now insert the node
+ // XXX Here tried to prevent updating Selection with unknown 4th argument,
+ // but nsIEditor.setShouldTxnSetSelection is not used for that.
+ editor.insertNode(element, range.commonAncestorContainer, offset);
+ offset = element.childNodes.length;
+ if (!editor.nodeIsBlock(element)) {
+ editor.setShouldTxnSetSelection(false);
+ }
+
+ // Move all the old child nodes to the element
+ var empty = true;
+ while (start != end) {
+ var next = start.nextSibling;
+ editor.deleteNode(start);
+ editor.insertNode(start, element, element.childNodes.length);
+ empty = false;
+ start = next;
+ }
+ if (!editor.nodeIsBlock(element)) {
+ editor.setShouldTxnSetSelection(true);
+ } else {
+ // Also move a trailing <br>
+ if (start && start.localName == "br") {
+ editor.deleteNode(start);
+ editor.insertNode(start, element, element.childNodes.length);
+ empty = false;
+ }
+ // Still nothing? Insert a <br> so the node is not empty
+ if (empty) {
+ editor.insertNode(
+ editor.createElementWithDefaults("br"),
+ element,
+ element.childNodes.length
+ );
+ }
+
+ // Hack to set the selection just inside the element
+ editor.insertNode(editor.document.createTextNode(""), element, offset);
+ }
+ } finally {
+ editor.endTransaction();
+ }
+
+ return true;
+}
+
+function nodeIsBlank(node) {
+ return node && node.nodeType == Node.TEXT_NODE && !/\S/.test(node.data);
+}
+
+function nodeBeginsBlock(editor, node) {
+ while (nodeIsBlank(node)) {
+ node = node.nextSibling;
+ }
+ return nodeIsBreak(editor, node);
+}
+
+function nodeEndsBlock(editor, node) {
+ while (nodeIsBlank(node)) {
+ node = node.previousSibling;
+ }
+ return nodeIsBreak(editor, node);
+}
+
+// C++ function isn't exposed to JS :-(
+function RemoveBlockContainer(element) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+
+ try {
+ var range = editor.document.createRange();
+ range.selectNode(element);
+ var offset = range.startOffset;
+ var parent = element.parentNode;
+
+ // May need to insert a break after the removed element
+ if (
+ !nodeBeginsBlock(editor, element.nextSibling) &&
+ !nodeEndsBlock(editor, element.lastChild)
+ ) {
+ editor.insertNode(
+ editor.createElementWithDefaults("br"),
+ parent,
+ range.endOffset
+ );
+ }
+
+ // May need to insert a break before the removed element, or if it was empty
+ if (
+ !nodeEndsBlock(editor, element.previousSibling) &&
+ !nodeBeginsBlock(editor, element.firstChild || element.nextSibling)
+ ) {
+ editor.insertNode(
+ editor.createElementWithDefaults("br"),
+ parent,
+ offset++
+ );
+ }
+
+ // Now remove the element
+ editor.deleteNode(element);
+
+ // Need to copy the contained nodes?
+ for (var i = 0; i < element.childNodes.length; i++) {
+ editor.insertNode(
+ element.childNodes[i].cloneNode(true),
+ parent,
+ offset++
+ );
+ }
+ } finally {
+ editor.endTransaction();
+ }
+}
+
+// C++ function isn't exposed to JS :-(
+function RemoveContainer(element) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+
+ try {
+ var range = editor.document.createRange();
+ var parent = element.parentNode;
+ // Allow for automatic joining of text nodes
+ // so we can't delete the container yet
+ // so we need to copy the contained nodes
+ for (var i = 0; i < element.childNodes.length; i++) {
+ range.selectNode(element);
+ editor.insertNode(
+ element.childNodes[i].cloneNode(true),
+ parent,
+ range.startOffset
+ );
+ }
+ // Now remove the element
+ editor.deleteNode(element);
+ } finally {
+ editor.endTransaction();
+ }
+}
+
+function FillLinkMenulist(linkMenulist, headingsArray) {
+ var menupopup = linkMenulist.firstChild;
+ var editor = GetCurrentEditor();
+ try {
+ var treeWalker = editor.document.createTreeWalker(
+ editor.document,
+ 1,
+ null,
+ true
+ );
+ var headingList = [];
+ var anchorList = []; // for sorting
+ var anchorMap = {}; // for weeding out duplicates and making heading anchors unique
+ var anchor;
+ var i;
+ for (
+ var element = treeWalker.nextNode();
+ element;
+ element = treeWalker.nextNode()
+ ) {
+ // grab headings
+ // Skip headings that already have a named anchor as their first child
+ // (this may miss nearby anchors, but at least we don't insert another
+ // under the same heading)
+ if (
+ element instanceof HTMLHeadingElement &&
+ element.textContent &&
+ !(
+ element.firstChild instanceof HTMLAnchorElement &&
+ element.firstChild.name
+ )
+ ) {
+ headingList.push(element);
+ }
+
+ // grab named anchors
+ if (element instanceof HTMLAnchorElement && element.name) {
+ anchor = "#" + element.name;
+ if (!(anchor in anchorMap)) {
+ anchorList.push({ anchor, sortkey: anchor.toLowerCase() });
+ anchorMap[anchor] = true;
+ }
+ }
+
+ // grab IDs
+ if (element.id) {
+ anchor = "#" + element.id;
+ if (!(anchor in anchorMap)) {
+ anchorList.push({ anchor, sortkey: anchor.toLowerCase() });
+ anchorMap[anchor] = true;
+ }
+ }
+ }
+ // add anchor for headings
+ for (i = 0; i < headingList.length; i++) {
+ var heading = headingList[i];
+
+ // Use just first 40 characters, don't add "...",
+ // and replace whitespace with "_" and strip non-word characters
+ anchor =
+ "#" +
+ ConvertToCDATAString(
+ TruncateStringAtWordEnd(heading.textContent, 40, false)
+ );
+
+ // Append "_" to any name already in the list
+ while (anchor in anchorMap) {
+ anchor += "_";
+ }
+ anchorList.push({ anchor, sortkey: anchor.toLowerCase() });
+ anchorMap[anchor] = true;
+
+ // Save nodes in an array so we can create anchor node under it later
+ headingsArray[anchor] = heading;
+ }
+ if (anchorList.length) {
+ // case insensitive sort
+ anchorList.sort((a, b) => {
+ if (a.sortkey < b.sortkey) {
+ return -1;
+ }
+ if (a.sortkey > b.sortkey) {
+ return 1;
+ }
+ return 0;
+ });
+
+ for (i = 0; i < anchorList.length; i++) {
+ createMenuItem(menupopup, anchorList[i].anchor);
+ }
+ } else {
+ // Don't bother with named anchors in Mail.
+ if (editor && editor.flags & Ci.nsIEditor.eEditorMailMask) {
+ menupopup.remove();
+ linkMenulist.removeAttribute("enablehistory");
+ return;
+ }
+ var item = createMenuItem(
+ menupopup,
+ GetString("NoNamedAnchorsOrHeadings")
+ );
+ item.setAttribute("disabled", "true");
+ }
+ } catch (e) {}
+}
+
+function createMenuItem(aMenuPopup, aLabel) {
+ var menuitem = document.createXULElement("menuitem");
+ menuitem.setAttribute("label", aLabel);
+ aMenuPopup.appendChild(menuitem);
+ return menuitem;
+}
+
+// Shared by Image and Link dialogs for the "Choose" button for links
+function chooseLinkFile() {
+ GetLocalFileURL("html, img").then(fileURL => {
+ // Always try to relativize local file URLs
+ if (gHaveDocumentUrl) {
+ fileURL = MakeRelativeUrl(fileURL);
+ }
+
+ gDialog.hrefInput.value = fileURL;
+
+ // Do stuff specific to a particular dialog
+ // (This is defined separately in Image and Link dialogs)
+ ChangeLinkLocation();
+ });
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdDialogTemplate.js b/comm/suite/editor/components/dialogs/content/EdDialogTemplate.js
new file mode 100644
index 0000000000..ee74e8c871
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdDialogTemplate.js
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// Cancel() is in EdDialogCommon.js
+var insertNew = true;
+var tagname = "TAG NAME";
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ if (!GetCurrentEditor()) {
+ window.close();
+ return;
+ }
+ // gDialog is declared in EdDialogCommon.js
+ // Set commonly-used widgets like this:
+ gDialog.fooButton = document.getElementById("fooButton");
+
+ InitDialog();
+
+ // Set window location relative to parent window (based on persisted attributes)
+ SetWindowLocation();
+
+ // Set focus to first widget in dialog, e.g.:
+ SetTextboxFocus(gDialog.fooButton);
+}
+
+function InitDialog() {
+ // Initialize all dialog widgets here,
+ // e.g., get attributes from an element for property dialog
+}
+
+function onAccept() {
+ // Validate all user data and set attributes and possibly insert new element here
+ // If there's an error the user must correct, return false to keep dialog open.
+
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdDialogTemplate.xhtml b/comm/suite/editor/components/dialogs/content/EdDialogTemplate.xhtml
new file mode 100644
index 0000000000..8f76af4654
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdDialogTemplate.xhtml
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<?xul-overlay href="chrome://editor/content/EdDialogOverlay.xhtml"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/Ed?????????.dtd">
+<!-- dialog containing a control requiring initial setup -->
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/Ed?????.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdDictionary.js b/comm/suite/editor/components/dialogs/content/EdDictionary.js
new file mode 100644
index 0000000000..892e92dd1a
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdDictionary.js
@@ -0,0 +1,164 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var gSpellChecker;
+var gWordToAdd;
+
+function Startup() {
+ if (!GetCurrentEditor()) {
+ window.close();
+ return;
+ }
+ // Get the SpellChecker shell
+ if ("gSpellChecker" in window.opener && window.opener.gSpellChecker) {
+ gSpellChecker = window.opener.gSpellChecker;
+ }
+
+ if (!gSpellChecker) {
+ dump("SpellChecker not found!!!\n");
+ window.close();
+ return;
+ }
+ // The word to add word is passed as the 2nd extra parameter in window.openDialog()
+ gWordToAdd = window.arguments[1];
+
+ gDialog.WordInput = document.getElementById("WordInput");
+ gDialog.DictionaryList = document.getElementById("DictionaryList");
+
+ gDialog.WordInput.value = gWordToAdd;
+ FillDictionaryList();
+
+ // Select the supplied word if it is already in the list
+ SelectWordToAddInList();
+ SetTextboxFocus(gDialog.WordInput);
+}
+
+function ValidateWordToAdd() {
+ gWordToAdd = TrimString(gDialog.WordInput.value);
+ if (gWordToAdd.length > 0) {
+ return true;
+ }
+ return false;
+}
+
+function SelectWordToAddInList() {
+ for (var i = 0; i < gDialog.DictionaryList.getRowCount(); i++) {
+ var wordInList = gDialog.DictionaryList.getItemAtIndex(i);
+ if (wordInList && gWordToAdd == wordInList.label) {
+ gDialog.DictionaryList.selectedIndex = i;
+ break;
+ }
+ }
+}
+
+function AddWord() {
+ if (ValidateWordToAdd()) {
+ try {
+ gSpellChecker.AddWordToDictionary(gWordToAdd);
+ } catch (e) {
+ dump(
+ "Exception occurred in gSpellChecker.AddWordToDictionary\nWord to add probably already existed\n"
+ );
+ }
+
+ // Rebuild the dialog list
+ FillDictionaryList();
+
+ SelectWordToAddInList();
+ gDialog.WordInput.value = "";
+ }
+}
+
+function ReplaceWord() {
+ if (ValidateWordToAdd()) {
+ var selItem = gDialog.DictionaryList.selectedItem;
+ if (selItem) {
+ try {
+ gSpellChecker.RemoveWordFromDictionary(selItem.label);
+ } catch (e) {}
+
+ try {
+ // Add to the dictionary list
+ gSpellChecker.AddWordToDictionary(gWordToAdd);
+
+ // Just change the text on the selected item instead of rebuilding the list.
+ // The items are richlist items, so the label sits in the first child.
+ selItem.firstChild.setAttribute("value", gWordToAdd);
+ } catch (e) {
+ // Rebuild list and select the word - it was probably already in the list
+ dump("Exception occurred adding word in ReplaceWord\n");
+ FillDictionaryList();
+ SelectWordToAddInList();
+ }
+ }
+ }
+}
+
+function RemoveWord() {
+ var selIndex = gDialog.DictionaryList.selectedIndex;
+ if (selIndex >= 0) {
+ var word = gDialog.DictionaryList.selectedItem.label;
+
+ // Remove word from list
+ gDialog.DictionaryList.selectedItem.remove();
+
+ // Remove from dictionary
+ try {
+ // Not working: BUG 43348
+ gSpellChecker.RemoveWordFromDictionary(word);
+ } catch (e) {
+ dump("Failed to remove word from dictionary\n");
+ }
+
+ ResetSelectedItem(selIndex);
+ }
+}
+
+function FillDictionaryList() {
+ var selIndex = gDialog.DictionaryList.selectedIndex;
+
+ // Clear the current contents of the list
+ ClearListbox(gDialog.DictionaryList);
+
+ // Get the list from the spell checker
+ gSpellChecker.GetPersonalDictionary();
+
+ var haveList = false;
+
+ // Get words until an empty string is returned
+ do {
+ var word = gSpellChecker.GetPersonalDictionaryWord();
+ if (word != "") {
+ gDialog.DictionaryList.appendItem(word, "");
+ haveList = true;
+ }
+ } while (word != "");
+
+ // XXX: BUG 74467: If list is empty, it doesn't layout to full height correctly
+ // (ignores "rows" attribute) (bug is latered, so we are fixing here for now)
+ if (!haveList) {
+ gDialog.DictionaryList.appendItem("", "");
+ }
+
+ ResetSelectedItem(selIndex);
+}
+
+function ResetSelectedItem(index) {
+ var lastIndex = gDialog.DictionaryList.getRowCount() - 1;
+ if (index > lastIndex) {
+ index = lastIndex;
+ }
+
+ // If we didn't have a selected item,
+ // set it to the first item
+ if (index == -1 && lastIndex >= 0) {
+ index = 0;
+ }
+
+ gDialog.DictionaryList.selectedIndex = index;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdDictionary.xhtml b/comm/suite/editor/components/dialogs/content/EdDictionary.xhtml
new file mode 100644
index 0000000000..cebbe1c192
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdDictionary.xhtml
@@ -0,0 +1,59 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorPersonalDictionary.dtd">
+<dialog buttons="cancel" title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ persist="screenX screenY"
+ onload="Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdDictionary.js"/>
+
+ <grid>
+ <columns><column style="width: 15em" flex="1"/><column flex="1"/></columns>
+ <rows>
+ <row>
+ <label value="&wordEditField.label;"
+ control="WordInput"
+ accesskey="&wordEditField.accessKey;"/>
+ <spacer/>
+ </row>
+ <row>
+ <textbox id="WordInput" flex="1"/>
+ <button id="AddWord" oncommand="AddWord()" label="&AddButton.label;"
+ accesskey="&AddButton.accessKey;"/>
+ </row>
+ <row>
+ <label value="&DictionaryList.label;"
+ control="DictionaryList"
+ accesskey="&DictionaryList.accessKey;"/>
+ <spacer/>
+ </row>
+ <row>
+ <richlistbox id="DictionaryList"
+ class="theme-listbox"
+ flex="1"
+ height="150px"/>
+ <vbox flex="1">
+ <button id="ReplaceWord" oncommand="ReplaceWord()" label="&ReplaceButton.label;"
+ accesskey="&ReplaceButton.accessKey;"/>
+ <spacer class="spacer"/>
+ <button id="RemoveWord" oncommand="RemoveWord()" label="&RemoveButton.label;"
+ accesskey="&RemoveButton.accessKey;"/>
+ <spacer class="spacer"/>
+ <spacer flex="1"/>
+ <button dlgtype="cancel" class="exit-dialog" id="close" label="&CloseButton.label;"
+ default="true" oncommand="onClose();"
+ accesskey="&CloseButton.accessKey;"/>
+ </vbox>
+ </row>
+ </rows>
+ </grid>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdFieldSetProps.js b/comm/suite/editor/components/dialogs/content/EdFieldSetProps.js
new file mode 100644
index 0000000000..1799a2b28f
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdFieldSetProps.js
@@ -0,0 +1,196 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var insertNew;
+var fieldsetElement;
+var newLegend;
+var legendElement;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ dump("Failed to get active editor!\n");
+ window.close();
+ return;
+ }
+
+ gDialog.editText = document.getElementById("EditText");
+ gDialog.legendText = document.getElementById("LegendText");
+ gDialog.legendAlign = document.getElementById("LegendAlign");
+ gDialog.RemoveFieldSet = document.getElementById("RemoveFieldSet");
+
+ // Get a single selected field set element
+ const kTagName = "fieldset";
+ try {
+ // Find a selected fieldset, or if one is at start or end of selection.
+ fieldsetElement = editor.getSelectedElement(kTagName);
+ if (!fieldsetElement) {
+ fieldsetElement = editor.getElementOrParentByTagName(
+ kTagName,
+ editor.selection.anchorNode
+ );
+ }
+ if (!fieldsetElement) {
+ fieldsetElement = editor.getElementOrParentByTagName(
+ kTagName,
+ editor.selection.focusNode
+ );
+ }
+ } catch (e) {}
+
+ if (fieldsetElement) {
+ // We found an element and don't need to insert one
+ insertNew = false;
+ } else {
+ insertNew = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+ try {
+ fieldsetElement = editor.createElementWithDefaults(kTagName);
+ } catch (e) {}
+
+ if (!fieldsetElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+ // Hide button removing existing fieldset
+ gDialog.RemoveFieldSet.hidden = true;
+ }
+
+ legendElement = fieldsetElement.querySelector("legend");
+ if (legendElement) {
+ newLegend = false;
+ var range = editor.document.createRange();
+ range.selectNode(legendElement);
+ gDialog.legendText.value = range.toString();
+ if (legendElement.innerHTML.includes("<")) {
+ gDialog.editText.checked = false;
+ gDialog.editText.disabled = false;
+ gDialog.legendText.disabled = true;
+ gDialog.editText.addEventListener(
+ "command",
+ () =>
+ Services.prompt.alert(
+ window,
+ GetString("Alert"),
+ GetString("EditTextWarning")
+ ),
+ { capture: false, once: true }
+ );
+ gDialog.RemoveFieldSet.focus();
+ } else {
+ SetTextboxFocus(gDialog.legendText);
+ }
+ } else {
+ newLegend = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+
+ legendElement = editor.createElementWithDefaults("legend");
+ if (!legendElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+ SetTextboxFocus(gDialog.legendText);
+ }
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = legendElement.cloneNode(false);
+
+ InitDialog();
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ gDialog.legendAlign.value = GetHTMLOrCSSStyleValue(
+ globalElement,
+ "align",
+ "caption-side"
+ );
+}
+
+function RemoveFieldSet() {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+ try {
+ if (!newLegend) {
+ editor.deleteNode(legendElement);
+ }
+ RemoveBlockContainer(fieldsetElement);
+ } finally {
+ editor.endTransaction();
+ }
+ SaveWindowLocation();
+ window.close();
+}
+
+function ValidateData() {
+ if (gDialog.legendAlign.value) {
+ globalElement.setAttribute("align", gDialog.legendAlign.value);
+ } else {
+ globalElement.removeAttribute("align");
+ }
+ return true;
+}
+
+function onAccept() {
+ // All values are valid - copy to actual element in doc
+ ValidateData();
+
+ var editor = GetCurrentEditor();
+
+ editor.beginTransaction();
+
+ try {
+ editor.cloneAttributes(legendElement, globalElement);
+
+ if (insertNew) {
+ if (gDialog.legendText.value) {
+ fieldsetElement.appendChild(legendElement);
+ legendElement.appendChild(
+ editor.document.createTextNode(gDialog.legendText.value)
+ );
+ }
+ InsertElementAroundSelection(fieldsetElement);
+ } else if (gDialog.editText.checked) {
+ editor.setShouldTxnSetSelection(false);
+
+ if (gDialog.legendText.value) {
+ if (newLegend) {
+ editor.insertNode(legendElement, fieldsetElement, 0);
+ } else {
+ while (legendElement.firstChild) {
+ editor.deleteNode(legendElement.lastChild);
+ }
+ }
+ editor.insertNode(
+ editor.document.createTextNode(gDialog.legendText.value),
+ legendElement,
+ 0
+ );
+ } else if (!newLegend) {
+ editor.deleteNode(legendElement);
+ }
+
+ editor.setShouldTxnSetSelection(true);
+ }
+ } finally {
+ editor.endTransaction();
+ }
+
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdFieldSetProps.xhtml b/comm/suite/editor/components/dialogs/content/EdFieldSetProps.xhtml
new file mode 100644
index 0000000000..0d67fad276
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdFieldSetProps.xhtml
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edFieldSetProperties SYSTEM "chrome://editor/locale/EditorFieldSetProperties.dtd">
+%edFieldSetProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ buttons="accept,cancel">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdFieldSetProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header" accesskey="&Legend.accesskey;">&Legend.label;</label>
+ </hbox>
+ <grid><columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <checkbox id="EditText" label="&EditLegendText.label;" accesskey="&EditLegendText.accesskey;" checked="true" disabled="true"
+ oncommand="gDialog.legendText.disabled = !gDialog.editText.checked;"/>
+ <textbox id="LegendText" accesskey="&Legend.accesskey;"/>
+ </row>
+ <row align="center">
+ <label control="LegendAlign" value="&LegendAlign.label;" accesskey="&LegendAlign.accesskey;"/>
+ <menulist id="LegendAlign">
+ <menupopup>
+ <menuitem label="&AlignDefault.label;"/>
+ <menuitem label="&AlignLeft.label;" value="left"/>
+ <menuitem label="&AlignCenter.label;" value="center"/>
+ <menuitem label="&AlignRight.label;" value="right"/>
+ </menupopup>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <!-- from EdDialogOverlay -->
+ <hbox flex="1" style="margin-top: 0.2em">
+ <button id="RemoveFieldSet" label="&RemoveFieldSet.label;" accesskey="&RemoveFieldSet.accesskey;" oncommand="RemoveFieldSet();"/>
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator class="groove"/>
+
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdFormProps.js b/comm/suite/editor/components/dialogs/content/EdFormProps.js
new file mode 100644
index 0000000000..9391fa4316
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdFormProps.js
@@ -0,0 +1,136 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var gForm;
+var insertNew;
+var formElement;
+var formActionWarning;
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ dump("Failed to get active editor!\n");
+ window.close();
+ return;
+ }
+
+ gForm = {
+ Name: document.getElementById("FormName"),
+ Action: document.getElementById("FormAction"),
+ Method: document.getElementById("FormMethod"),
+ EncType: document.getElementById("FormEncType"),
+ Target: document.getElementById("FormTarget"),
+ };
+ gDialog.MoreSection = document.getElementById("MoreSection");
+ gDialog.MoreFewerButton = document.getElementById("MoreFewerButton");
+ gDialog.RemoveForm = document.getElementById("RemoveForm");
+
+ // Get a single selected form element
+ const kTagName = "form";
+ try {
+ formElement = editor.getSelectedElement(kTagName);
+ if (!formElement) {
+ formElement = editor.getElementOrParentByTagName(
+ kTagName,
+ editor.selection.anchorNode
+ );
+ }
+ if (!formElement) {
+ formElement = editor.getElementOrParentByTagName(
+ kTagName,
+ editor.selection.focusNode
+ );
+ }
+ } catch (e) {}
+
+ if (formElement) {
+ // We found an element and don't need to insert one
+ insertNew = false;
+ formActionWarning = formElement.hasAttribute("action");
+ } else {
+ insertNew = true;
+ formActionWarning = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+ try {
+ formElement = editor.createElementWithDefaults(kTagName);
+ } catch (e) {}
+
+ if (!formElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+ // Hide button removing existing form
+ gDialog.RemoveForm.hidden = true;
+ }
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = formElement.cloneNode(false);
+
+ InitDialog();
+
+ InitMoreFewer();
+
+ SetTextboxFocus(gForm.Name);
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ for (var attribute in gForm) {
+ gForm[attribute].value = globalElement.getAttribute(attribute);
+ }
+}
+
+function RemoveForm() {
+ RemoveBlockContainer(formElement);
+ SaveWindowLocation();
+ window.close();
+}
+
+function ValidateData() {
+ for (var attribute in gForm) {
+ if (gForm[attribute].value) {
+ globalElement.setAttribute(attribute, gForm[attribute].value);
+ } else {
+ globalElement.removeAttribute(attribute);
+ }
+ }
+ return true;
+}
+
+function onAccept(event) {
+ if (formActionWarning && !gForm.Action.value) {
+ Services.prompt.alert(
+ window,
+ GetString("Alert"),
+ GetString("NoFormAction")
+ );
+ gForm.Action.focus();
+ formActionWarning = false;
+ event.preventDefault();
+ return;
+ }
+ // All values are valid - copy to actual element in doc or
+ // element created to insert
+ ValidateData();
+
+ var editor = GetCurrentEditor();
+
+ editor.cloneAttributes(formElement, globalElement);
+
+ if (insertNew) {
+ InsertElementAroundSelection(formElement);
+ }
+
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdFormProps.xhtml b/comm/suite/editor/components/dialogs/content/EdFormProps.xhtml
new file mode 100644
index 0000000000..275daef785
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdFormProps.xhtml
@@ -0,0 +1,98 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/menulist.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edFormProperties SYSTEM "chrome://editor/locale/EditorFormProperties.dtd">
+%edFormProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ buttons="accept,cancel">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdFormProps.js"/>
+
+ <script src="chrome://messenger/content/customElements.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&Settings.label;</label>
+ </hbox>
+ <grid><columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label control="FormName" value="&FormName.label;" accesskey="&FormName.accesskey;"/>
+ <textbox id="FormName"/>
+ </row>
+ <row align="center">
+ <label control="FormAction" value="&FormAction.label;" accesskey="&FormAction.accesskey;"/>
+ <textbox id="FormAction"/>
+ </row>
+ <row align="center">
+ <label control="FormMethod" value="&FormMethod.label;" accesskey="&FormMethod.accesskey;"/>
+ <hbox>
+ <menulist is="menulist-editable" id="FormMethod" editable="true" autoSelectMenuitem="true">
+ <menupopup>
+ <menuitem label="GET"/>
+ <menuitem label="POST"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ </row>
+ <hbox>
+ <button id="MoreFewerButton" oncommand="onMoreFewer();" persist="more"/>
+ </hbox>
+ <rows id="MoreSection">
+ <row align="center">
+ <label control="FormEncType" value="&FormEncType.label;" accesskey="&FormEncType.accesskey;"/>
+ <menulist is="menulist-editable" id="FormEncType" editable="true" autoSelectMenuitem="true">
+ <menupopup>
+ <menuitem label="application/x-www-form-urlencoded"/>
+ <menuitem label="multipart/form-data"/>
+ <menuitem label="text/plain"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row align="center">
+ <label control="FormTarget" value="&FormTarget.label;" accesskey="&FormTarget.accesskey;"/>
+ <menulist is="menulist-editable" id="FormTarget" editable="true" autoSelectMenuitem="true">
+ <menupopup>
+ <menuitem label="_blank"/>
+ <menuitem label="_self"/>
+ <menuitem label="_parent"/>
+ <menuitem label="_top"/>
+ </menupopup>
+ </menulist>
+ </row>
+ </rows>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <!-- from EdDialogOverlay -->
+ <hbox flex="1" style="margin-top: 0.2em">
+ <button id="RemoveForm" label="&RemoveForm.label;" accesskey="&RemoveForm.accesskey;" oncommand="RemoveForm();"/>
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdHLineProps.js b/comm/suite/editor/components/dialogs/content/EdHLineProps.js
new file mode 100644
index 0000000000..eff9c06a41
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdHLineProps.js
@@ -0,0 +1,227 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var tagName = "hr";
+var gHLineElement;
+var width;
+var height;
+var align;
+var shading;
+const gMaxHRSize = 1000; // This is hard-coded in nsHTMLHRElement::StringToAttribute()
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+ try {
+ // Get the selected horizontal line
+ gHLineElement = editor.getSelectedElement(tagName);
+ } catch (e) {}
+
+ if (!gHLineElement) {
+ // We should never be here if not editing an existing HLine
+ window.close();
+ return;
+ }
+ gDialog.heightInput = document.getElementById("height");
+ gDialog.widthInput = document.getElementById("width");
+ gDialog.leftAlign = document.getElementById("leftAlign");
+ gDialog.centerAlign = document.getElementById("centerAlign");
+ gDialog.rightAlign = document.getElementById("rightAlign");
+ gDialog.alignGroup = gDialog.rightAlign.radioGroup;
+ gDialog.shading = document.getElementById("3dShading");
+ gDialog.pixelOrPercentMenulist = document.getElementById(
+ "pixelOrPercentMenulist"
+ );
+
+ // Make a copy to use for AdvancedEdit and onSaveDefault
+ globalElement = gHLineElement.cloneNode(false);
+
+ // Initialize control values based on existing attributes
+ InitDialog();
+
+ // SET FOCUS TO FIRST CONTROL
+ SetTextboxFocus(gDialog.widthInput);
+
+ // Resize window
+ window.sizeToContent();
+
+ SetWindowLocation();
+}
+
+// Set dialog widgets with attribute data
+// We get them from globalElement copy so this can be used
+// by AdvancedEdit(), which is shared by all property dialogs
+function InitDialog() {
+ // Just to be confusing, "size" is used instead of height because it does
+ // not accept % values, only pixels
+ var height = GetHTMLOrCSSStyleValue(globalElement, "size", "height");
+ if (height.includes("px")) {
+ height = height.substr(0, height.indexOf("px"));
+ }
+ if (!height) {
+ height = 2; // Default value
+ }
+
+ // We will use "height" here and in UI
+ gDialog.heightInput.value = height;
+
+ // Get the width attribute of the element, stripping out "%"
+ // This sets contents of menulist (adds pixel and percent menuitems elements)
+ gDialog.widthInput.value = InitPixelOrPercentMenulist(
+ globalElement,
+ gHLineElement,
+ "width",
+ "pixelOrPercentMenulist"
+ );
+
+ var marginLeft = GetHTMLOrCSSStyleValue(
+ globalElement,
+ "align",
+ "margin-left"
+ ).toLowerCase();
+ var marginRight = GetHTMLOrCSSStyleValue(
+ globalElement,
+ "align",
+ "margin-right"
+ ).toLowerCase();
+ align = marginLeft + " " + marginRight;
+ gDialog.leftAlign.checked = align == "left left" || align == "0px auto";
+ gDialog.centerAlign.checked =
+ align == "center center" || align == "auto auto" || align == " ";
+ gDialog.rightAlign.checked = align == "right right" || align == "auto 0px";
+
+ if (gDialog.centerAlign.checked) {
+ gDialog.alignGroup.selectedItem = gDialog.centerAlign;
+ } else if (gDialog.rightAlign.checked) {
+ gDialog.alignGroup.selectedItem = gDialog.rightAlign;
+ } else {
+ gDialog.alignGroup.selectedItem = gDialog.leftAlign;
+ }
+
+ gDialog.shading.checked = !globalElement.hasAttribute("noshade");
+}
+
+function onSaveDefault() {
+ // "false" means set attributes on the globalElement,
+ // not the real element being edited
+ if (ValidateData()) {
+ var alignInt;
+ if (align == "left") {
+ alignInt = 0;
+ } else if (align == "right") {
+ alignInt = 2;
+ } else {
+ alignInt = 1;
+ }
+ Services.prefs.setIntPref("editor.hrule.align", alignInt);
+
+ var percent;
+ var widthInt;
+ var heightInt;
+
+ if (width) {
+ if (width.includes("%")) {
+ percent = true;
+ widthInt = Number(width.substr(0, width.indexOf("%")));
+ } else {
+ percent = false;
+ widthInt = Number(width);
+ }
+ } else {
+ percent = true;
+ widthInt = Number(100);
+ }
+
+ heightInt = height ? Number(height) : 2;
+
+ Services.prefs.setIntPref("editor.hrule.width", widthInt);
+ Services.prefs.setBoolPref("editor.hrule.width_percent", percent);
+ Services.prefs.setIntPref("editor.hrule.height", heightInt);
+ Services.prefs.setBoolPref("editor.hrule.shading", shading);
+
+ // Write the prefs out NOW!
+ Services.prefs.savePrefFile(null);
+ }
+}
+
+// Get and validate data from widgets.
+// Set attributes on globalElement so they can be accessed by AdvancedEdit()
+function ValidateData() {
+ // Height is always pixels
+ height = ValidateNumber(
+ gDialog.heightInput,
+ null,
+ 1,
+ gMaxHRSize,
+ globalElement,
+ "size",
+ false
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ width = ValidateNumber(
+ gDialog.widthInput,
+ gDialog.pixelOrPercentMenulist,
+ 1,
+ gMaxPixels,
+ globalElement,
+ "width",
+ false
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ align = "left";
+ if (gDialog.centerAlign.selected) {
+ // Don't write out default attribute
+ align = "";
+ } else if (gDialog.rightAlign.selected) {
+ align = "right";
+ }
+ if (align) {
+ globalElement.setAttribute("align", align);
+ } else {
+ try {
+ GetCurrentEditor().removeAttributeOrEquivalent(
+ globalElement,
+ "align",
+ true
+ );
+ } catch (e) {}
+ }
+
+ if (gDialog.shading.checked) {
+ shading = true;
+ globalElement.removeAttribute("noshade");
+ } else {
+ shading = false;
+ globalElement.setAttribute("noshade", "noshade");
+ }
+ return true;
+}
+
+function onAccept(event) {
+ if (ValidateData()) {
+ // Copy attributes from the globalElement to the document element
+ try {
+ GetCurrentEditor().cloneAttributes(gHLineElement, globalElement);
+ } catch (e) {}
+ return;
+ }
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdHLineProps.xhtml b/comm/suite/editor/components/dialogs/content/EdHLineProps.xhtml
new file mode 100644
index 0000000000..fbcfa3b594
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdHLineProps.xhtml
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edHLineProperties SYSTEM "chrome://editor/locale/EditorHLineProperties.dtd">
+%edHLineProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <!--- Element-specific methods -->
+ <script src="chrome://editor/content/EdHLineProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&dimensionsBox.label;</label>
+ </hbox>
+ <grid>
+ <columns><column/><column/><column /></columns>
+ <rows>
+ <row align="center">
+ <label control="width"
+ value="&widthEditField.label;"
+ accesskey="&widthEditField.accessKey;"/>
+ <textbox class="narrow" id="width" flex="1" oninput="forceInteger('width')"/>
+ <menulist id="pixelOrPercentMenulist" />
+ <!-- menupopup and menuitems added by JS -->
+ </row>
+ <row align="center">
+ <label control="height"
+ value="&heightEditField.label;"
+ accesskey="&heightEditField.accessKey;"/>
+ <textbox class="narrow" id="height" oninput="forceInteger('height')"/>
+ <label value="&pixelsPopup.value;" />
+ </row>
+ </rows>
+ </grid>
+ <checkbox id="3dShading" label="&threeDShading.label;" accesskey="&threeDShading.accessKey;"/>
+ </groupbox>
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&alignmentBox.label;</label>
+ </hbox>
+ <radiogroup id="alignmentGroup" orient="horizontal">
+ <spacer class="spacer"/>
+ <radio id="leftAlign" label="&leftRadio.label;" accesskey="&leftRadio.accessKey;"/>
+ <radio id="centerAlign" label="&centerRadio.label;" accesskey="&centerRadio.accessKey;"/>
+ <radio id="rightAlign" label="&rightRadio.label;" accesskey="&rightRadio.accessKey;"/>
+ </radiogroup>
+ </groupbox>
+ <spacer class="spacer"/>
+ <hbox>
+ <button id="SaveDefault" label="&saveSettings.label;"
+ accesskey="&saveSettings.accessKey;"
+ oncommand="onSaveDefault()"
+ tooltiptext="&saveSettings.tooltip;" />
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdImageDialog.js b/comm/suite/editor/components/dialogs/content/EdImageDialog.js
new file mode 100644
index 0000000000..0e697dbd6f
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdImageDialog.js
@@ -0,0 +1,661 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ Note: We encourage non-empty alt text for images inserted into a page.
+ When there's no alt text, we always write 'alt=""' as the attribute, since "alt" is a required attribute.
+ We allow users to not have alt text by checking a "Don't use alterate text" radio button,
+ and we don't accept spaces as valid alt text. A space used to be required to avoid the error message
+ if user didn't enter alt text, but is unnecessary now that we no longer annoy the user
+ with the error dialog if alt="" is present on an img element.
+ We trim all spaces at the beginning and end of user's alt text
+*/
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var gInsertNewImage = true;
+var gDoAltTextError = false;
+var gConstrainOn = false;
+// Note used in current version, but these are set correctly
+// and could be used to reset width and height used for constrain ratio
+var gConstrainWidth = 0;
+var gConstrainHeight = 0;
+var imageElement;
+var gImageMap = 0;
+var gCanRemoveImageMap = false;
+var gRemoveImageMap = false;
+var gImageMapDisabled = false;
+var gActualWidth = "";
+var gActualHeight = "";
+var gOriginalSrc = "";
+var gTimerID;
+var gValidateTab;
+var gInsertNewIMap;
+
+// These must correspond to values in EditorDialog.css for each theme
+// (unfortunately, setting "style" attribute here doesn't work!)
+var gPreviewImageWidth = 80;
+var gPreviewImageHeight = 50;
+
+// dialog initialization code
+
+function ImageStartup() {
+ gDialog.tabBox = document.getElementById("TabBox");
+ gDialog.tabLocation = document.getElementById("imageLocationTab");
+ gDialog.tabDimensions = document.getElementById("imageDimensionsTab");
+ gDialog.tabBorder = document.getElementById("imageBorderTab");
+ gDialog.srcInput = document.getElementById("srcInput");
+ gDialog.titleInput = document.getElementById("titleInput");
+ gDialog.altTextInput = document.getElementById("altTextInput");
+ gDialog.altTextRadioGroup = document.getElementById("altTextRadioGroup");
+ gDialog.altTextRadio = document.getElementById("altTextRadio");
+ gDialog.noAltTextRadio = document.getElementById("noAltTextRadio");
+ gDialog.actualSizeRadio = document.getElementById("actualSizeRadio");
+ gDialog.constrainCheckbox = document.getElementById("constrainCheckbox");
+ gDialog.widthInput = document.getElementById("widthInput");
+ gDialog.heightInput = document.getElementById("heightInput");
+ gDialog.widthUnitsMenulist = document.getElementById("widthUnitsMenulist");
+ gDialog.heightUnitsMenulist = document.getElementById("heightUnitsMenulist");
+ gDialog.imagelrInput = document.getElementById("imageleftrightInput");
+ gDialog.imagetbInput = document.getElementById("imagetopbottomInput");
+ gDialog.border = document.getElementById("border");
+ gDialog.alignTypeSelect = document.getElementById("alignTypeSelect");
+ gDialog.ImageHolder = document.getElementById("preview-image-holder");
+ gDialog.PreviewWidth = document.getElementById("PreviewWidth");
+ gDialog.PreviewHeight = document.getElementById("PreviewHeight");
+ gDialog.PreviewSize = document.getElementById("PreviewSize");
+ gDialog.PreviewImage = null;
+ gDialog.OkButton = document.documentElement.getButton("accept");
+}
+
+// Set dialog widgets with attribute data
+// We get them from globalElement copy so this can be used
+// by AdvancedEdit(), which is shared by all property dialogs
+function InitImage() {
+ // Set the controls to the image's attributes
+ var src = globalElement.getAttribute("src");
+
+ // For image insertion the 'src' attribute is null.
+ if (src) {
+ // Shorten data URIs for display.
+ shortenImageData(src, gDialog.srcInput);
+ }
+
+ // Set "Relativize" checkbox according to current URL state
+ SetRelativeCheckbox();
+
+ // Force loading of image from its source and show preview image
+ LoadPreviewImage();
+
+ gDialog.titleInput.value = globalElement.getAttribute("title");
+
+ var hasAltText = globalElement.hasAttribute("alt");
+ var altText = globalElement.getAttribute("alt");
+ gDialog.altTextInput.value = altText;
+ if (altText || (!hasAltText && globalElement.hasAttribute("src"))) {
+ gDialog.altTextRadioGroup.selectedItem = gDialog.altTextRadio;
+ } else if (hasAltText) {
+ gDialog.altTextRadioGroup.selectedItem = gDialog.noAltTextRadio;
+ }
+ SetAltTextDisabled(
+ gDialog.altTextRadioGroup.selectedItem == gDialog.noAltTextRadio
+ );
+
+ // setup the height and width widgets
+ var width = InitPixelOrPercentMenulist(
+ globalElement,
+ gInsertNewImage ? null : imageElement,
+ "width",
+ "widthUnitsMenulist",
+ gPixel
+ );
+ var height = InitPixelOrPercentMenulist(
+ globalElement,
+ gInsertNewImage ? null : imageElement,
+ "height",
+ "heightUnitsMenulist",
+ gPixel
+ );
+
+ // Set actual radio button if both set values are the same as actual
+ SetSizeWidgets(width, height);
+
+ gDialog.widthInput.value = gConstrainWidth = width || gActualWidth || "";
+ gDialog.heightInput.value = gConstrainHeight = height || gActualHeight || "";
+
+ // set spacing editfields
+ gDialog.imagelrInput.value = globalElement.getAttribute("hspace");
+ gDialog.imagetbInput.value = globalElement.getAttribute("vspace");
+
+ // dialog.border.value = globalElement.getAttribute("border");
+ var bv = GetHTMLOrCSSStyleValue(globalElement, "border", "border-top-width");
+ if (bv.includes("px")) {
+ // Strip out the px
+ bv = bv.substr(0, bv.indexOf("px"));
+ } else if (bv == "thin") {
+ bv = "1";
+ } else if (bv == "medium") {
+ bv = "3";
+ } else if (bv == "thick") {
+ bv = "5";
+ }
+ gDialog.border.value = bv;
+
+ // Get alignment setting
+ var align = globalElement.getAttribute("align");
+ if (align) {
+ align = align.toLowerCase();
+ }
+
+ switch (align) {
+ case "top":
+ case "middle":
+ case "right":
+ case "left":
+ gDialog.alignTypeSelect.value = align;
+ break;
+ default:
+ // Default or "bottom"
+ gDialog.alignTypeSelect.value = "bottom";
+ }
+
+ // Get image map for image
+ gImageMap = GetImageMap();
+
+ doOverallEnabling();
+ doDimensionEnabling();
+}
+
+function SetSizeWidgets(width, height) {
+ if (
+ !(width || height) ||
+ (gActualWidth &&
+ gActualHeight &&
+ width == gActualWidth &&
+ height == gActualHeight)
+ ) {
+ gDialog.actualSizeRadio.radioGroup.selectedItem = gDialog.actualSizeRadio;
+ }
+
+ if (!gDialog.actualSizeRadio.selected) {
+ // Decide if user's sizes are in the same ratio as actual sizes
+ if (gActualWidth && gActualHeight) {
+ if (gActualWidth > gActualHeight) {
+ gDialog.constrainCheckbox.checked =
+ Math.round((gActualHeight * width) / gActualWidth) == height;
+ } else {
+ gDialog.constrainCheckbox.checked =
+ Math.round((gActualWidth * height) / gActualHeight) == width;
+ }
+ }
+ }
+}
+
+// Disable alt text input when "Don't use alt" radio is checked
+function SetAltTextDisabled(disable) {
+ gDialog.altTextInput.disabled = disable;
+}
+
+function GetImageMap() {
+ var usemap = globalElement.getAttribute("usemap");
+ if (usemap) {
+ gCanRemoveImageMap = true;
+ let mapname = usemap.substr(1);
+ try {
+ return GetCurrentEditor().document.querySelector(
+ '[name="' + mapname + '"]'
+ );
+ } catch (e) {}
+ } else {
+ gCanRemoveImageMap = false;
+ }
+
+ return null;
+}
+
+function chooseFile() {
+ if (gTimerID) {
+ clearTimeout(gTimerID);
+ }
+
+ // Put focus into the input field
+ SetTextboxFocus(gDialog.srcInput);
+
+ GetLocalFileURL("img").then(fileURL => {
+ // Always try to relativize local file URLs
+ if (gHaveDocumentUrl) {
+ fileURL = MakeRelativeUrl(fileURL);
+ }
+
+ gDialog.srcInput.value = fileURL;
+
+ SetRelativeCheckbox();
+ doOverallEnabling();
+ LoadPreviewImage();
+ });
+}
+
+function PreviewImageLoaded() {
+ if (gDialog.PreviewImage) {
+ // Image loading has completed -- we can get actual width
+ gActualWidth = gDialog.PreviewImage.naturalWidth;
+ gActualHeight = gDialog.PreviewImage.naturalHeight;
+
+ if (gActualWidth && gActualHeight) {
+ // Use actual size or scale to fit preview if either dimension is too large
+ var width = gActualWidth;
+ var height = gActualHeight;
+ if (gActualWidth > gPreviewImageWidth) {
+ width = gPreviewImageWidth;
+ height = gActualHeight * (gPreviewImageWidth / gActualWidth);
+ }
+ if (height > gPreviewImageHeight) {
+ height = gPreviewImageHeight;
+ width = gActualWidth * (gPreviewImageHeight / gActualHeight);
+ }
+ gDialog.PreviewImage.width = width;
+ gDialog.PreviewImage.height = height;
+
+ gDialog.PreviewWidth.setAttribute("value", gActualWidth);
+ gDialog.PreviewHeight.setAttribute("value", gActualHeight);
+
+ gDialog.PreviewSize.collapsed = false;
+ gDialog.ImageHolder.collapsed = false;
+
+ SetSizeWidgets(gDialog.widthInput.value, gDialog.heightInput.value);
+ }
+
+ if (gDialog.actualSizeRadio.selected) {
+ SetActualSize();
+ }
+ }
+}
+
+function LoadPreviewImage() {
+ gDialog.PreviewSize.collapsed = true;
+ // XXXbz workaround for bug 265416 / bug 266284
+ gDialog.ImageHolder.collapsed = true;
+
+ var imageSrc = TrimString(gDialog.srcInput.value);
+ if (!imageSrc) {
+ return;
+ }
+ if (isImageDataShortened(imageSrc)) {
+ imageSrc = restoredImageData(gDialog.srcInput);
+ }
+
+ try {
+ // Remove the image URL from image cache so it loads fresh
+ // (if we don't do this, loads after the first will always use image cache
+ // and we won't see image edit changes or be able to get actual width and height)
+
+ // We must have an absolute URL to preview it or remove it from the cache
+ imageSrc = MakeAbsoluteUrl(imageSrc);
+
+ if (GetScheme(imageSrc)) {
+ let uri = Services.io.newURI(imageSrc);
+ if (uri) {
+ let imgCache = Cc["@mozilla.org/image/cache;1"].getService(
+ Ci.imgICache
+ );
+
+ // This returns error if image wasn't in the cache; ignore that
+ imgCache.removeEntry(uri);
+ }
+ }
+ } catch (e) {}
+
+ if (gDialog.PreviewImage) {
+ removeEventListener("load", PreviewImageLoaded, true);
+ }
+
+ if (gDialog.ImageHolder.hasChildNodes()) {
+ gDialog.ImageHolder.firstChild.remove();
+ }
+
+ gDialog.PreviewImage = document.createElementNS(
+ "http://www.w3.org/1999/xhtml",
+ "img"
+ );
+ if (gDialog.PreviewImage) {
+ // set the src before appending to the document -- see bug 198435 for why
+ // this is needed.
+ // XXXbz that bug is long-since fixed. Is this still needed?
+ gDialog.PreviewImage.addEventListener("load", PreviewImageLoaded, true);
+ gDialog.PreviewImage.src = imageSrc;
+ gDialog.ImageHolder.appendChild(gDialog.PreviewImage);
+ }
+}
+
+function SetActualSize() {
+ gDialog.widthInput.value = gActualWidth ? gActualWidth : "";
+ gDialog.widthUnitsMenulist.selectedIndex = 0;
+ gDialog.heightInput.value = gActualHeight ? gActualHeight : "";
+ gDialog.heightUnitsMenulist.selectedIndex = 0;
+ doDimensionEnabling();
+}
+
+function ChangeImageSrc() {
+ if (gTimerID) {
+ clearTimeout(gTimerID);
+ }
+
+ gTimerID = setTimeout(LoadPreviewImage, 800);
+
+ SetRelativeCheckbox();
+ doOverallEnabling();
+}
+
+function doDimensionEnabling() {
+ // Enabled unless "Actual Size" is selected
+ var enable = !gDialog.actualSizeRadio.selected;
+
+ // BUG 74145: After input field is disabled,
+ // setting it enabled causes blinking caret to appear
+ // even though focus isn't set to it.
+ SetElementEnabledById("heightInput", enable);
+ SetElementEnabledById("heightLabel", enable);
+ SetElementEnabledById("heightUnitsMenulist", enable);
+
+ SetElementEnabledById("widthInput", enable);
+ SetElementEnabledById("widthLabel", enable);
+ SetElementEnabledById("widthUnitsMenulist", enable);
+
+ var constrainEnable =
+ enable &&
+ gDialog.widthUnitsMenulist.selectedIndex == 0 &&
+ gDialog.heightUnitsMenulist.selectedIndex == 0;
+
+ SetElementEnabledById("constrainCheckbox", constrainEnable);
+}
+
+function doOverallEnabling() {
+ var enabled = TrimString(gDialog.srcInput.value) != "";
+
+ SetElementEnabled(gDialog.OkButton, enabled);
+ SetElementEnabledById("AdvancedEditButton1", enabled);
+ SetElementEnabledById("imagemapLabel", enabled);
+ SetElementEnabledById("removeImageMap", gCanRemoveImageMap);
+}
+
+function ToggleConstrain() {
+ // If just turned on, save the current width and height as basis for constrain ratio
+ // Thus clicking on/off lets user say "Use these values as aspect ration"
+ if (
+ gDialog.constrainCheckbox.checked &&
+ !gDialog.constrainCheckbox.disabled &&
+ gDialog.widthUnitsMenulist.selectedIndex == 0 &&
+ gDialog.heightUnitsMenulist.selectedIndex == 0
+ ) {
+ gConstrainWidth = Number(TrimString(gDialog.widthInput.value));
+ gConstrainHeight = Number(TrimString(gDialog.heightInput.value));
+ }
+}
+
+function constrainProportions(srcID, destID) {
+ var srcElement = document.getElementById(srcID);
+ if (!srcElement) {
+ return;
+ }
+
+ var destElement = document.getElementById(destID);
+ if (!destElement) {
+ return;
+ }
+
+ // always force an integer (whether we are constraining or not)
+ forceInteger(srcID);
+
+ if (
+ !gActualWidth ||
+ !gActualHeight ||
+ !(gDialog.constrainCheckbox.checked && !gDialog.constrainCheckbox.disabled)
+ ) {
+ return;
+ }
+
+ // double-check that neither width nor height is in percent mode; bail if so!
+ if (
+ gDialog.widthUnitsMenulist.selectedIndex != 0 ||
+ gDialog.heightUnitsMenulist.selectedIndex != 0
+ ) {
+ return;
+ }
+
+ // This always uses the actual width and height ratios
+ // which is kind of funky if you change one number without the constrain
+ // and then turn constrain on and change a number
+ // I prefer the old strategy (below) but I can see some merit to this solution
+ if (srcID == "widthInput") {
+ destElement.value = Math.round(
+ (srcElement.value * gActualHeight) / gActualWidth
+ );
+ } else {
+ destElement.value = Math.round(
+ (srcElement.value * gActualWidth) / gActualHeight
+ );
+ }
+
+ /*
+ // With this strategy, the width and height ratio
+ // can be reset to whatever the user entered.
+ if (srcID == "widthInput") {
+ destElement.value = Math.round( srcElement.value * gConstrainHeight / gConstrainWidth );
+ } else {
+ destElement.value = Math.round( srcElement.value * gConstrainWidth / gConstrainHeight );
+ }
+ */
+}
+
+function removeImageMap() {
+ gRemoveImageMap = true;
+ gCanRemoveImageMap = false;
+ SetElementEnabledById("removeImageMap", false);
+}
+
+function SwitchToValidatePanel() {
+ if (
+ gDialog.tabBox &&
+ gValidateTab &&
+ gDialog.tabBox.selectedTab != gValidateTab
+ ) {
+ gDialog.tabBox.selectedTab = gValidateTab;
+ }
+}
+
+// Get data from widgets, validate, and set for the global element
+// accessible to AdvancedEdit() [in EdDialogCommon.js]
+function ValidateImage() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ return false;
+ }
+
+ gValidateTab = gDialog.tabLocation;
+ if (!gDialog.srcInput.value) {
+ Services.prompt.alert(
+ window,
+ GetString("Alert"),
+ GetString("MissingImageError")
+ );
+ SwitchToValidatePanel();
+ gDialog.srcInput.focus();
+ return false;
+ }
+
+ // We must convert to "file:///" or "http://" format else image doesn't load!
+ let src = gDialog.srcInput.value.trim();
+
+ if (isImageDataShortened(src)) {
+ src = restoredImageData(gDialog.srcInput);
+ } else {
+ var checkbox = document.getElementById("MakeRelativeCheckbox");
+ try {
+ if (checkbox && !checkbox.checked) {
+ src = Services.uriFixup.getFixupURIInfo(
+ src,
+ Ci.nsIURIFixup.FIXUP_FLAG_NONE
+ ).preferredURI.spec;
+ }
+ } catch (e) {}
+
+ globalElement.setAttribute("src", src);
+ }
+
+ let title = gDialog.titleInput.value.trim();
+ if (title) {
+ globalElement.setAttribute("title", title);
+ } else {
+ globalElement.removeAttribute("title");
+ }
+
+ // Force user to enter Alt text only if "Alternate text" radio is checked
+ // Don't allow just spaces in alt text
+ var alt = "";
+ var useAlt = gDialog.altTextRadioGroup.selectedItem == gDialog.altTextRadio;
+ if (useAlt) {
+ alt = TrimString(gDialog.altTextInput.value);
+ }
+
+ if (alt || !useAlt) {
+ globalElement.setAttribute("alt", alt);
+ } else if (!gDoAltTextError) {
+ globalElement.removeAttribute("alt");
+ } else {
+ Services.prompt.alert(window, GetString("Alert"), GetString("NoAltText"));
+ SwitchToValidatePanel();
+ gDialog.altTextInput.focus();
+ return false;
+ }
+
+ var width = "";
+ var height = "";
+
+ gValidateTab = gDialog.tabDimensions;
+ if (!gDialog.actualSizeRadio.selected) {
+ // Get user values for width and height
+ width = ValidateNumber(
+ gDialog.widthInput,
+ gDialog.widthUnitsMenulist,
+ 1,
+ gMaxPixels,
+ globalElement,
+ "width",
+ false,
+ true
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ height = ValidateNumber(
+ gDialog.heightInput,
+ gDialog.heightUnitsMenulist,
+ 1,
+ gMaxPixels,
+ globalElement,
+ "height",
+ false,
+ true
+ );
+ if (gValidationError) {
+ return false;
+ }
+ }
+
+ // We always set the width and height attributes, even if same as actual.
+ // This speeds up layout of pages since sizes are known before image is loaded
+ if (!width) {
+ width = gActualWidth;
+ }
+ if (!height) {
+ height = gActualHeight;
+ }
+
+ // Remove existing width and height only if source changed
+ // and we couldn't obtain actual dimensions
+ var srcChanged = src != gOriginalSrc;
+ if (width) {
+ editor.setAttributeOrEquivalent(globalElement, "width", width, true);
+ } else if (srcChanged) {
+ editor.removeAttributeOrEquivalent(globalElement, "width", true);
+ }
+
+ if (height) {
+ editor.setAttributeOrEquivalent(globalElement, "height", height, true);
+ } else if (srcChanged) {
+ editor.removeAttributeOrEquivalent(globalElement, "height", true);
+ }
+
+ // spacing attributes
+ gValidateTab = gDialog.tabBorder;
+ ValidateNumber(
+ gDialog.imagelrInput,
+ null,
+ 0,
+ gMaxPixels,
+ globalElement,
+ "hspace",
+ false,
+ true,
+ true
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ ValidateNumber(
+ gDialog.imagetbInput,
+ null,
+ 0,
+ gMaxPixels,
+ globalElement,
+ "vspace",
+ false,
+ true
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ // note this is deprecated and should be converted to stylesheets
+ ValidateNumber(
+ gDialog.border,
+ null,
+ 0,
+ gMaxPixels,
+ globalElement,
+ "border",
+ false,
+ true
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ // Default or setting "bottom" means don't set the attribute
+ // Note that the attributes "left" and "right" are opposite
+ // of what we use in the UI, which describes where the TEXT wraps,
+ // not the image location (which is what the HTML describes)
+ switch (gDialog.alignTypeSelect.value) {
+ case "top":
+ case "middle":
+ case "right":
+ case "left":
+ editor.setAttributeOrEquivalent(
+ globalElement,
+ "align",
+ gDialog.alignTypeSelect.value,
+ true
+ );
+ break;
+ default:
+ try {
+ editor.removeAttributeOrEquivalent(globalElement, "align", true);
+ } catch (e) {}
+ }
+
+ return true;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdImageLinkLoader.js b/comm/suite/editor/components/dialogs/content/EdImageLinkLoader.js
new file mode 100755
index 0000000000..5b88a5703f
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdImageLinkLoader.js
@@ -0,0 +1,145 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var gMsgCompProcessLink = false;
+var gMsgCompInputElement = null;
+var gMsgCompPrevInputValue = null;
+var gMsgCompPrevMozDoNotSendAttribute;
+var gMsgCompAttachSourceElement = null;
+
+function OnLoadDialog() {
+ gMsgCompAttachSourceElement = document.getElementById("AttachSourceToMail");
+ var editor = GetCurrentEditor();
+ if (
+ gMsgCompAttachSourceElement &&
+ editor &&
+ editor.flags & Ci.nsIEditor.eEditorMailMask
+ ) {
+ SetRelativeCheckbox = function() {
+ SetAttachCheckbox();
+ };
+ // initialize the AttachSourceToMail checkbox
+ gMsgCompAttachSourceElement.hidden = false;
+
+ switch (document.documentElement.id) {
+ case "imageDlg":
+ gMsgCompInputElement = gDialog.srcInput;
+ gMsgCompProcessLink = false;
+ break;
+ case "linkDlg":
+ gMsgCompInputElement = gDialog.hrefInput;
+ gMsgCompProcessLink = true;
+ break;
+ }
+ if (gMsgCompInputElement) {
+ SetAttachCheckbox();
+ gMsgCompPrevMozDoNotSendAttribute = globalElement.getAttribute(
+ "moz-do-not-send"
+ );
+ }
+ }
+}
+addEventListener("load", OnLoadDialog, false);
+
+function OnAcceptDialog() {
+ // Auto-convert file URLs to data URLs. If we're in the link properties
+ // dialog convert only when requested - for the image dialog do it always.
+ if (gMsgCompInputElement &&
+ /^file:/i.test(gMsgCompInputElement.value.trim()) &&
+ (gMsgCompAttachSourceElement.checked || !gMsgCompProcessLink)) {
+ var dataURI = GenerateDataURL(gMsgCompInputElement.value.trim());
+ gMsgCompInputElement.value = dataURI;
+ gMsgCompAttachSourceElement.checked = true;
+ }
+ DoAttachSourceCheckbox();
+}
+document.addEventListener("dialogaccept", OnAcceptDialog, true);
+
+function SetAttachCheckbox() {
+ var resetCheckbox = false;
+ var mozDoNotSend = globalElement.getAttribute("moz-do-not-send");
+
+ // In case somebody played with the advanced property and changed the moz-do-not-send attribute
+ if (mozDoNotSend != gMsgCompPrevMozDoNotSendAttribute) {
+ gMsgCompPrevMozDoNotSendAttribute = mozDoNotSend;
+ resetCheckbox = true;
+ }
+
+ // Has the URL changed
+ if (
+ gMsgCompInputElement &&
+ gMsgCompInputElement.value != gMsgCompPrevInputValue
+ ) {
+ gMsgCompPrevInputValue = gMsgCompInputElement.value;
+ resetCheckbox = true;
+ }
+
+ if (gMsgCompInputElement && resetCheckbox) {
+ // Here is the rule about how to set the checkbox Attach Source To Message:
+ // If the attribute "moz-do-not-send" has not been set, we look at the scheme of the URL
+ // and at some preference to decide what is the best for the user.
+ // If it is set to "false", the checkbox is checked, otherwise unchecked.
+ var attach = false;
+ if (mozDoNotSend == null) {
+ // We haven't yet set the "moz-do-not-send" attribute.
+ var inputValue = gMsgCompInputElement.value.trim();
+ if (/^(file|data):/i.test(inputValue)) {
+ // For files or data URLs, default to attach them.
+ attach = true;
+ } else if (
+ !gMsgCompProcessLink && // Implies image dialogue.
+ /^https?:/i.test(inputValue)
+ ) {
+ // For images loaded via http(s) we default to the preference value.
+ attach = Services.prefs.getBoolPref("mail.compose.attach_http_images");
+ }
+ } else {
+ attach = mozDoNotSend == "false";
+ }
+
+ gMsgCompAttachSourceElement.checked = attach;
+ }
+}
+
+function DoAttachSourceCheckbox() {
+ gMsgCompPrevMozDoNotSendAttribute = (!gMsgCompAttachSourceElement.checked).toString();
+ globalElement.setAttribute(
+ "moz-do-not-send",
+ gMsgCompPrevMozDoNotSendAttribute
+ );
+}
+
+function GenerateDataURL(url) {
+ var file = Services.io.newURI(url).QueryInterface(Ci.nsIFileURL).file;
+ var contentType = Cc["@mozilla.org/mime;1"]
+ .getService(Ci.nsIMIMEService)
+ .getTypeFromFile(file);
+ var inputStream = Cc[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Ci.nsIFileInputStream);
+ inputStream.init(file, 0x01, 0o600, 0);
+ var stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
+ Ci.nsIBinaryInputStream
+ );
+ stream.setInputStream(inputStream);
+ let data = "";
+ while (stream.available() > 0) {
+ data += stream.readBytes(stream.available());
+ }
+ let encoded = btoa(data);
+ stream.close();
+ return (
+ "data:" +
+ contentType +
+ ";filename=" +
+ encodeURIComponent(file.leafName) +
+ ";base64," +
+ encoded
+ );
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdImageProps.js b/comm/suite/editor/components/dialogs/content/EdImageProps.js
new file mode 100644
index 0000000000..5b73488dcb
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdImageProps.js
@@ -0,0 +1,293 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+/* import-globals-from EdImageDialog.js */
+
+var gAnchorElement = null;
+var gLinkElement = null;
+var gOriginalHref = "";
+var gHNodeArray = {};
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ ImageStartup();
+ gDialog.hrefInput = document.getElementById("hrefInput");
+ gDialog.makeRelativeLink = document.getElementById("MakeRelativeLink");
+ gDialog.showLinkBorder = document.getElementById("showLinkBorder");
+ gDialog.linkTab = document.getElementById("imageLinkTab");
+ gDialog.linkAdvanced = document.getElementById("LinkAdvancedEditButton");
+
+ // Get a single selected image element
+ var tagName = "img";
+ if ("arguments" in window && window.arguments[0]) {
+ imageElement = window.arguments[0];
+ // We've been called from form field properties, so we can't insert a link
+ gDialog.linkTab.remove();
+ gDialog.linkTab = null;
+ } else {
+ // First check for <input type="image">
+ try {
+ imageElement = editor.getSelectedElement("input");
+
+ if (!imageElement || imageElement.getAttribute("type") != "image") {
+ // Get a single selected image element
+ imageElement = editor.getSelectedElement(tagName);
+ if (imageElement) {
+ gAnchorElement = editor.getElementOrParentByTagName(
+ "href",
+ imageElement
+ );
+ }
+ }
+ } catch (e) {}
+ }
+
+ if (imageElement) {
+ // We found an element and don't need to insert one
+ if (imageElement.hasAttribute("src")) {
+ gInsertNewImage = false;
+ gActualWidth = imageElement.naturalWidth;
+ gActualHeight = imageElement.naturalHeight;
+ }
+ } else {
+ gInsertNewImage = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+ try {
+ imageElement = editor.createElementWithDefaults(tagName);
+ } catch (e) {}
+
+ if (!imageElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+ try {
+ gAnchorElement = editor.getSelectedElement("href");
+ } catch (e) {}
+ }
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = imageElement.cloneNode(false);
+
+ // We only need to test for this once per dialog load
+ gHaveDocumentUrl = GetDocumentBaseUrl();
+
+ InitDialog();
+ if (gAnchorElement) {
+ gOriginalHref = gAnchorElement.getAttribute("href");
+ // Make a copy to use for AdvancedEdit
+ gLinkElement = gAnchorElement.cloneNode(false);
+ } else {
+ gLinkElement = editor.createElementWithDefaults("a");
+ }
+ gDialog.hrefInput.value = gOriginalHref;
+
+ FillLinkMenulist(gDialog.hrefInput, gHNodeArray);
+ ChangeLinkLocation();
+
+ // Save initial source URL
+ gOriginalSrc = gDialog.srcInput.value;
+
+ // By default turn constrain on, but both width and height must be in pixels
+ gDialog.constrainCheckbox.checked =
+ gDialog.widthUnitsMenulist.selectedIndex == 0 &&
+ gDialog.heightUnitsMenulist.selectedIndex == 0;
+
+ // Start in "Link" tab if 2nd argument is true
+ if (gDialog.linkTab && "arguments" in window && window.arguments[1]) {
+ document.getElementById("TabBox").selectedTab = gDialog.linkTab;
+ SetTextboxFocus(gDialog.hrefInput);
+ } else {
+ SetTextboxFocus(gDialog.srcInput);
+ }
+
+ SetWindowLocation();
+}
+
+// Set dialog widgets with attribute data
+// We get them from globalElement copy so this can be used
+// by AdvancedEdit(), which is shared by all property dialogs
+function InitDialog() {
+ InitImage();
+ var border = TrimString(gDialog.border.value);
+ gDialog.showLinkBorder.checked = border != "" && border > 0;
+}
+
+function ChangeLinkLocation() {
+ var href = TrimString(gDialog.hrefInput.value);
+ SetRelativeCheckbox(gDialog.makeRelativeLink);
+ gDialog.showLinkBorder.disabled = !href;
+ gDialog.linkAdvanced.disabled = !href;
+ gLinkElement.setAttribute("href", href);
+}
+
+function ToggleShowLinkBorder() {
+ if (gDialog.showLinkBorder.checked) {
+ var border = TrimString(gDialog.border.value);
+ if (!border || border == "0") {
+ gDialog.border.value = "2";
+ }
+ } else {
+ gDialog.border.value = "0";
+ }
+}
+
+// Get data from widgets, validate, and set for the global element
+// accessible to AdvancedEdit() [in EdDialogCommon.js]
+function ValidateData() {
+ return ValidateImage();
+}
+
+function onAccept(event) {
+ // Use this now (default = false) so Advanced Edit button dialog doesn't trigger error message
+ gDoAltTextError = true;
+
+ if (ValidateData()) {
+ if ("arguments" in window && window.arguments[0]) {
+ SaveWindowLocation();
+ return;
+ }
+
+ var editor = GetCurrentEditor();
+
+ editor.beginTransaction();
+
+ try {
+ if (gRemoveImageMap) {
+ globalElement.removeAttribute("usemap");
+ if (gImageMap) {
+ editor.deleteNode(gImageMap);
+ gInsertNewIMap = true;
+ gImageMap = null;
+ }
+ } else if (gImageMap) {
+ // un-comment to see that inserting image maps does not work!
+ /*
+ gImageMap = editor.createElementWithDefaults("map");
+ gImageMap.setAttribute("name", "testing");
+ var testArea = editor.createElementWithDefaults("area");
+ testArea.setAttribute("shape", "circle");
+ testArea.setAttribute("coords", "86,102,52");
+ testArea.setAttribute("href", "test");
+ gImageMap.appendChild(testArea);
+ */
+
+ // Assign to map if there is one
+ var mapName = gImageMap.getAttribute("name");
+ if (mapName != "") {
+ globalElement.setAttribute("usemap", "#" + mapName);
+ if (globalElement.getAttribute("border") == "") {
+ globalElement.setAttribute("border", 0);
+ }
+ }
+ }
+
+ // Create or remove the link as appropriate
+ var href = gDialog.hrefInput.value;
+ if (href != gOriginalHref) {
+ if (href && !gInsertNewImage) {
+ EditorSetTextProperty("a", "href", href);
+ // gAnchorElement is needed for cloning attributes later.
+ if (!gAnchorElement) {
+ gAnchorElement = editor.getElementOrParentByTagName(
+ "href",
+ imageElement
+ );
+ }
+ } else {
+ EditorRemoveTextProperty("href", "");
+ }
+ }
+
+ // If inside a link, always write the 'border' attribute
+ if (href) {
+ if (gDialog.showLinkBorder.checked) {
+ // Use default = 2 if border attribute is empty
+ if (!globalElement.hasAttribute("border")) {
+ globalElement.setAttribute("border", "2");
+ }
+ } else {
+ globalElement.setAttribute("border", "0");
+ }
+ }
+
+ if (gInsertNewImage) {
+ if (href) {
+ gLinkElement.appendChild(imageElement);
+ editor.insertElementAtSelection(gLinkElement, true);
+ } else {
+ // 'true' means delete the selection before inserting
+ editor.insertElementAtSelection(imageElement, true);
+ }
+ }
+
+ // Check to see if the link was to a heading
+ // Do this last because it moves the caret (BAD!)
+ if (href in gHNodeArray) {
+ var anchorNode = editor.createElementWithDefaults("a");
+ if (anchorNode) {
+ anchorNode.name = href.substr(1);
+ // Remember to use editor method so it is undoable!
+ editor.insertNode(anchorNode, gHNodeArray[href], 0);
+ }
+ }
+ // All values are valid - copy to actual element in doc or
+ // element we just inserted
+ editor.cloneAttributes(imageElement, globalElement);
+ if (gAnchorElement) {
+ editor.cloneAttributes(gAnchorElement, gLinkElement);
+ }
+
+ // If document is empty, the map element won't insert,
+ // so always insert the image first
+ if (gImageMap && gInsertNewIMap) {
+ // Insert the ImageMap element at beginning of document
+ var body = editor.rootElement;
+ editor.setShouldTxnSetSelection(false);
+ editor.insertNode(gImageMap, body, 0);
+ editor.setShouldTxnSetSelection(true);
+ }
+ } catch (e) {
+ dump(e);
+ }
+
+ editor.endTransaction();
+
+ SaveWindowLocation();
+ return;
+ }
+
+ gDoAltTextError = false;
+
+ event.preventDefault();
+}
+
+function onLinkAdvancedEdit() {
+ window.AdvancedEditOK = false;
+ window.openDialog(
+ "chrome://editor/content/EdAdvancedEdit.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal,resizable=yes",
+ "",
+ gLinkElement
+ );
+ window.focus();
+ if (window.AdvancedEditOK) {
+ gDialog.hrefInput.value = gLinkElement.getAttribute("href");
+ }
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdImageProps.xhtml b/comm/suite/editor/components/dialogs/content/EdImageProps.xhtml
new file mode 100644
index 0000000000..11d7d03026
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdImageProps.xhtml
@@ -0,0 +1,116 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edImageProperties SYSTEM "chrome://editor/locale/EditorImageProperties.dtd">
+%edImageProperties;
+<!ENTITY % composeEditorOverlayDTD SYSTEM "chrome://messenger/locale/messengercompose/mailComposeEditorOverlay.dtd">
+%composeEditorOverlayDTD;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<!-- dialog containing a control requiring initial setup -->
+<dialog id="imageDlg" title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()"
+ buttons="accept,cancel">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdImageProps.js"/>
+ <script src="chrome://editor/content/EdImageDialog.js"/>
+ <script src="chrome://editor/content/EdImageLinkLoader.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <tabbox id="TabBox">
+ <tabs flex="1">
+ <tab id="imageLocationTab" label="&imageLocationTab.label;"/>
+ <tab id="imageDimensionsTab" label="&imageDimensionsTab.label;"/>
+ <tab id="imageAppearanceTab" label="&imageAppearanceTab.label;"/>
+ <tab id="imageLinkTab" label="&imageLinkTab.label;"/>
+ </tabs>
+ <tabpanels>
+#include edImage.inc.xhtml
+ <vbox>
+ <spacer class="spacer"/>
+ <vbox id="LinkLocationBox">
+ <label control="hrefInput"
+ accesskey="&LinkURLEditField2.accessKey;"
+ width="1">&LinkURLEditField2.label;</label>
+ <textbox id="hrefInput" type="text"
+ class="uri-element padded" oninput="ChangeLinkLocation();"/>
+ <hbox align="center">
+ <checkbox id="MakeRelativeLink"
+ for="hrefInput"
+ label="&makeUrlRelative.label;"
+ accesskey="&makeUrlRelative.accessKey;"
+ oncommand="MakeInputValueRelativeOrAbsolute(this);"
+ tooltiptext="&makeUrlRelative.tooltip;"/>
+ <spacer flex="1"/>
+ <button label="&chooseFileLinkButton.label;" accesskey="&chooseFileLinkButton.accessKey;"
+ oncommand="chooseLinkFile();"/>
+ </hbox>
+ </vbox>
+ <spacer class="spacer"/>
+ <hbox>
+ <checkbox id="showLinkBorder"
+ label="&showImageLinkBorder.label;"
+ accesskey="&showImageLinkBorder.accessKey;"
+ oncommand="ToggleShowLinkBorder();"/>
+ <spacer flex="1"/>
+ <button id="LinkAdvancedEditButton"
+ label="&LinkAdvancedEditButton.label;"
+ accesskey="&LinkAdvancedEditButton.accessKey;"
+ tooltiptext="&LinkAdvancedEditButton.tooltip;"
+ oncommand="onLinkAdvancedEdit();"/>
+ </hbox>
+ </vbox>
+ </tabpanels>
+ </tabbox>
+
+ <hbox align="end">
+ <groupbox id="imagePreview" orient="horizontal" flex="1">
+ <hbox class="groupbox-title">
+ <label class="header">&previewBox.label;</label>
+ </hbox>
+ <hbox id="preview-image-box" align="center">
+ <spacer flex="1"/>
+ <description id="preview-image-holder"/>
+ <spacer flex="1"/>
+ </hbox>
+ <vbox id="PreviewSize" collapsed="true">
+ <spacer flex="1"/>
+ <label value="&actualSize.label;"/>
+ <hbox>
+ <label value="&widthEditField.label;"/>
+ <spacer flex="1"/>
+ <label id="PreviewWidth"/>
+ </hbox>
+ <hbox>
+ <label value="&heightEditField.label;"/>
+ <spacer flex="1"/>
+ <label id="PreviewHeight"/>
+ </hbox>
+ <spacer flex="1"/>
+ </vbox>
+ </groupbox>
+
+ <vbox id="AdvancedEdit">
+ <hbox flex="1" style="margin-top: 0.2em" align="center">
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ </vbox>
+ </hbox>
+ <separator class="groove"/>
+
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdInputImage.js b/comm/suite/editor/components/dialogs/content/EdInputImage.js
new file mode 100644
index 0000000000..556acc7b13
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInputImage.js
@@ -0,0 +1,189 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+/* import-globals-from EdImageDialog.js */
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ gDialog = {
+ inputName: document.getElementById("InputName"),
+ inputDisabled: document.getElementById("InputDisabled"),
+ inputTabIndex: document.getElementById("InputTabIndex"),
+ };
+
+ ImageStartup();
+
+ // Get a single selected input element
+ var tagName = "input";
+ try {
+ imageElement = editor.getSelectedElement(tagName);
+ } catch (e) {}
+
+ if (imageElement) {
+ // We found an element and don't need to insert one
+ gInsertNewImage = false;
+ } else {
+ gInsertNewImage = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+ try {
+ imageElement = editor.createElementWithDefaults(tagName);
+ } catch (e) {}
+
+ if (!imageElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+ var imgElement;
+ try {
+ imgElement = editor.getSelectedElement("img");
+ } catch (e) {}
+
+ if (imgElement) {
+ // We found an image element, convert it to an input type="image"
+ var attributes = [
+ "src",
+ "alt",
+ "width",
+ "height",
+ "hspace",
+ "vspace",
+ "border",
+ "align",
+ "usemap",
+ "ismap",
+ ];
+ for (let i in attributes) {
+ imageElement.setAttribute(
+ attributes[i],
+ imgElement.getAttribute(attributes[i])
+ );
+ }
+ }
+ }
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = imageElement.cloneNode(false);
+
+ // We only need to test for this once per dialog load
+ gHaveDocumentUrl = GetDocumentBaseUrl();
+
+ InitDialog();
+
+ // Save initial source URL
+ gOriginalSrc = gDialog.srcInput.value;
+
+ // By default turn constrain on, but both width and height must be in pixels
+ gDialog.constrainCheckbox.checked =
+ gDialog.widthUnitsMenulist.selectedIndex == 0 &&
+ gDialog.heightUnitsMenulist.selectedIndex == 0;
+
+ SetTextboxFocus(gDialog.inputName);
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ InitImage();
+ gDialog.inputName.value = globalElement.getAttribute("name");
+ gDialog.inputDisabled.setAttribute(
+ "checked",
+ globalElement.hasAttribute("disabled")
+ );
+ gDialog.inputTabIndex.value = globalElement.getAttribute("tabindex");
+}
+
+function ValidateData() {
+ if (!ValidateImage()) {
+ return false;
+ }
+ if (gDialog.inputName.value) {
+ globalElement.setAttribute("name", gDialog.inputName.value);
+ } else {
+ globalElement.removeAttribute("name");
+ }
+ if (gDialog.inputTabIndex.value) {
+ globalElement.setAttribute("tabindex", gDialog.inputTabIndex.value);
+ } else {
+ globalElement.removeAttribute("tabindex");
+ }
+ if (gDialog.inputDisabled.checked) {
+ globalElement.setAttribute("disabled", "");
+ } else {
+ globalElement.removeAttribute("disabled");
+ }
+ globalElement.setAttribute("type", "image");
+ return true;
+}
+
+function onAccept(event) {
+ // Show alt text error only once
+ // (we don't initialize doAltTextError=true
+ // so Advanced edit button dialog doesn't trigger that error message)
+ // Use this now (default = false) so Advanced Edit button dialog doesn't trigger error message
+ gDoAltTextError = true;
+
+ if (ValidateData()) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+
+ try {
+ if (gRemoveImageMap) {
+ globalElement.removeAttribute("usemap");
+ if (gImageMap) {
+ editor.deleteNode(gImageMap);
+ gInsertNewIMap = true;
+ gImageMap = null;
+ }
+ } else if (gImageMap) {
+ // Assign to map if there is one
+ var mapName = gImageMap.getAttribute("name");
+ if (mapName != "") {
+ globalElement.setAttribute("usemap", "#" + mapName);
+ if (globalElement.getAttribute("border") == "") {
+ globalElement.setAttribute("border", 0);
+ }
+ }
+ }
+
+ if (gInsertNewImage) {
+ // 'true' means delete the selection before inserting
+ // in case were are converting an image to an input type="image"
+ editor.insertElementAtSelection(imageElement, true);
+ }
+ editor.cloneAttributes(imageElement, globalElement);
+
+ // If document is empty, the map element won't insert,
+ // so always insert the image element first
+ if (gImageMap && gInsertNewIMap) {
+ // Insert the ImageMap element at beginning of document
+ var body = editor.rootElement;
+ editor.setShouldTxnSetSelection(false);
+ editor.insertNode(gImageMap, body, 0);
+ editor.setShouldTxnSetSelection(true);
+ }
+ } catch (e) {}
+
+ editor.endTransaction();
+
+ SaveWindowLocation();
+
+ return;
+ }
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdInputImage.xhtml b/comm/suite/editor/components/dialogs/content/EdInputImage.xhtml
new file mode 100644
index 0000000000..d3fc8c8270
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInputImage.xhtml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edInputProperties SYSTEM "chrome://editor/locale/EditorInputProperties.dtd">
+%edInputProperties;
+<!ENTITY % edImageProperties SYSTEM "chrome://editor/locale/EditorImageProperties.dtd">
+%edImageProperties;
+<!ENTITY % composeEditorOverlayDTD SYSTEM "chrome://messenger/locale/messengercompose/mailComposeEditorOverlay.dtd">
+%composeEditorOverlayDTD;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitleImage.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdInputImage.js"/>
+ <script src="chrome://editor/content/EdImageDialog.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <tabbox id="TabBox">
+ <tabs flex="1">
+ <tab id="imageInputTab" label="&imageInputTab.label;"/>
+ <tab id="imageLocationTab" label="&imageLocationTab.label;"/>
+ <tab id="imageDimensionsTab" label="&imageDimensionsTab.label;"/>
+ <tab id="imageAppearanceTab" label="&imageAppearanceTab.label;"/>
+ </tabs>
+ <tabpanels>
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&InputSettings.label;</label>
+ </hbox>
+ <grid><columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label value="&InputName.label;"/>
+ <textbox id="InputName"/>
+ </row>
+ <row>
+ <spacer/>
+ <checkbox id="InputDisabled" label="&InputDisabled.label;"/>
+ </row>
+ <row align="center">
+ <label value="&tabIndex.label;"/>
+ <hbox>
+ <textbox id="InputTabIndex" class="narrow" oninput="forceInteger(this.id);"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+#include edImage.inc.xhtml
+ </tabpanels>
+ </tabbox>
+
+ <hbox align="end">
+ <groupbox id="imagePreview" orient="horizontal" flex="1">
+ <hbox class="groupbox-title">
+ <label class="header">&previewBox.label;</label>
+ </hbox>
+ <hbox id="preview-image-box" align="center">
+ <spacer flex="1"/>
+ <description id="preview-image-holder"/>
+ <spacer flex="1"/>
+ </hbox>
+ <vbox id="PreviewSize" collapsed="true">
+ <spacer flex="1"/>
+ <label value="&actualSize.label;"/>
+ <hbox>
+ <label value="&widthEditField.label;"/>
+ <spacer flex="1"/>
+ <label id="PreviewWidth"/>
+ </hbox>
+ <hbox>
+ <label value="&heightEditField.label;"/>
+ <spacer flex="1"/>
+ <label id="PreviewHeight"/>
+ </hbox>
+ <spacer flex="1"/>
+ </vbox>
+ </groupbox>
+
+ <vbox id="AdvancedEdit">
+ <hbox flex="1" style="margin-top: 0.2em" align="center">
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator id="advancedSeparator" class="groove"/>
+ </vbox>
+ </hbox>
+
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdInputProps.js b/comm/suite/editor/components/dialogs/content/EdInputProps.js
new file mode 100644
index 0000000000..a737e263c7
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInputProps.js
@@ -0,0 +1,345 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var insertNew;
+var inputElement;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ dump("Failed to get active editor!\n");
+ window.close();
+ return;
+ }
+
+ gDialog = {
+ accept: document.documentElement.getButton("accept"),
+ inputType: document.getElementById("InputType"),
+ inputNameDeck: document.getElementById("InputNameDeck"),
+ inputName: document.getElementById("InputName"),
+ inputValueDeck: document.getElementById("InputValueDeck"),
+ inputValue: document.getElementById("InputValue"),
+ inputDeck: document.getElementById("InputDeck"),
+ inputChecked: document.getElementById("InputChecked"),
+ inputSelected: document.getElementById("InputSelected"),
+ inputReadOnly: document.getElementById("InputReadOnly"),
+ inputDisabled: document.getElementById("InputDisabled"),
+ inputTabIndex: document.getElementById("InputTabIndex"),
+ inputAccessKey: document.getElementById("InputAccessKey"),
+ inputSize: document.getElementById("InputSize"),
+ inputMaxLength: document.getElementById("InputMaxLength"),
+ inputAccept: document.getElementById("InputAccept"),
+ MoreSection: document.getElementById("MoreSection"),
+ MoreFewerButton: document.getElementById("MoreFewerButton"),
+ AdvancedEditButton: document.getElementById("AdvancedEditButton"),
+ AdvancedEditDeck: document.getElementById("AdvancedEditDeck"),
+ };
+
+ // Get a single selected input element
+ const kTagName = "input";
+ try {
+ inputElement = editor.getSelectedElement(kTagName);
+ } catch (e) {}
+
+ if (inputElement) {
+ // We found an element and don't need to insert one
+ insertNew = false;
+ } else {
+ insertNew = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+ try {
+ inputElement = editor.createElementWithDefaults(kTagName);
+ } catch (e) {}
+
+ if (!inputElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+
+ var imgElement = editor.getSelectedElement("img");
+ if (imgElement) {
+ // We found an image element, convert it to an input type="image"
+ inputElement.setAttribute("type", "image");
+
+ var attributes = [
+ "src",
+ "alt",
+ "width",
+ "height",
+ "hspace",
+ "vspace",
+ "border",
+ "align",
+ ];
+ for (let i in attributes) {
+ inputElement.setAttribute(
+ attributes[i],
+ imgElement.getAttribute(attributes[i])
+ );
+ }
+ } else {
+ inputElement.setAttribute("value", GetSelectionAsText());
+ }
+ }
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = inputElement.cloneNode(false);
+
+ InitDialog();
+
+ InitMoreFewer();
+
+ gDialog.inputType.focus();
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ var type = globalElement.getAttribute("type");
+ var index = 0;
+ switch (type) {
+ case "button":
+ index = 9;
+ break;
+ case "checkbox":
+ index = 2;
+ break;
+ case "file":
+ index = 6;
+ break;
+ case "hidden":
+ index = 7;
+ break;
+ case "image":
+ index = 8;
+ break;
+ case "password":
+ index = 1;
+ break;
+ case "radio":
+ index = 3;
+ break;
+ case "reset":
+ index = 5;
+ break;
+ case "submit":
+ index = 4;
+ break;
+ }
+ gDialog.inputType.selectedIndex = index;
+ gDialog.inputName.value = globalElement.getAttribute("name");
+ gDialog.inputValue.value = globalElement.getAttribute("value");
+ gDialog.inputChecked.setAttribute(
+ "checked",
+ globalElement.hasAttribute("checked")
+ );
+ gDialog.inputSelected.setAttribute(
+ "checked",
+ globalElement.hasAttribute("checked")
+ );
+ gDialog.inputReadOnly.setAttribute(
+ "checked",
+ globalElement.hasAttribute("readonly")
+ );
+ gDialog.inputDisabled.setAttribute(
+ "checked",
+ globalElement.hasAttribute("disabled")
+ );
+ gDialog.inputTabIndex.value = globalElement.getAttribute("tabindex");
+ gDialog.inputAccessKey.value = globalElement.getAttribute("accesskey");
+ gDialog.inputSize.value = globalElement.getAttribute("size");
+ gDialog.inputMaxLength.value = globalElement.getAttribute("maxlength");
+ gDialog.inputAccept.value = globalElement.getAttribute("accept");
+ SelectInputType();
+}
+
+function SelectInputType() {
+ var index = gDialog.inputType.selectedIndex;
+ gDialog.AdvancedEditDeck.setAttribute("selectedIndex", 0);
+ gDialog.inputNameDeck.setAttribute("selectedIndex", 0);
+ gDialog.inputValueDeck.setAttribute("selectedIndex", 0);
+ gDialog.inputValue.disabled = false;
+ gDialog.inputChecked.disabled = index != 2;
+ gDialog.inputSelected.disabled = index != 3;
+ gDialog.inputReadOnly.disabled = index > 1;
+ gDialog.inputTabIndex.disabled = index == 7;
+ gDialog.inputAccessKey.disabled = index == 7;
+ gDialog.inputSize.disabled = index > 1;
+ gDialog.inputMaxLength.disabled = index > 1;
+ gDialog.inputAccept.disabled = index != 6;
+ switch (index) {
+ case 0:
+ case 1:
+ gDialog.inputValueDeck.setAttribute("selectedIndex", 1);
+ gDialog.inputDeck.setAttribute("selectedIndex", 2);
+ break;
+ case 2:
+ gDialog.inputDeck.setAttribute("selectedIndex", 0);
+ break;
+ case 3:
+ gDialog.inputDeck.setAttribute("selectedIndex", 1);
+ gDialog.inputNameDeck.setAttribute("selectedIndex", 1);
+ break;
+ case 6:
+ gDialog.inputValue.disabled = true;
+ gDialog.inputAccept.disabled = false;
+ break;
+ case 8:
+ gDialog.inputValue.disabled = true;
+ gDialog.AdvancedEditDeck.setAttribute("selectedIndex", 1);
+ gDialog.inputName.removeEventListener("input", onInput);
+ break;
+ case 7:
+ gDialog.inputValueDeck.setAttribute("selectedIndex", 1);
+ break;
+ }
+ onInput();
+}
+
+function onInput() {
+ var disabled = false;
+ switch (gDialog.inputType.selectedIndex) {
+ case 3:
+ disabled = disabled || !gDialog.inputValue.value;
+ break;
+ case 4:
+ case 5:
+ break;
+ case 8:
+ disabled = !globalElement.hasAttribute("src");
+ break;
+ default:
+ disabled = !gDialog.inputName.value;
+ break;
+ }
+ if (gDialog.accept.disabled != disabled) {
+ gDialog.accept.disabled = disabled;
+ gDialog.AdvancedEditButton.disabled = disabled;
+ }
+}
+
+function doImageProperties() {
+ window.openDialog(
+ "chrome://editor/content/EdImageProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ globalElement
+ );
+ window.focus();
+ onInput();
+}
+
+function ValidateData() {
+ var attributes = {
+ type: "",
+ name: gDialog.inputName.value,
+ value: gDialog.inputValue.value,
+ tabindex: gDialog.inputTabIndex.value,
+ accesskey: "",
+ size: "",
+ maxlength: "",
+ accept: "",
+ };
+ var index = gDialog.inputType.selectedIndex;
+ var flags = {
+ checked: false,
+ readonly: false,
+ disabled: gDialog.inputDisabled.checked,
+ };
+ switch (index) {
+ case 1:
+ attributes.type = "password";
+ // Falls through
+ case 0:
+ flags.readonly = gDialog.inputReadOnly.checked;
+ attributes.size = gDialog.inputSize.value;
+ attributes.maxlength = gDialog.inputMaxLength.value;
+ break;
+ case 2:
+ attributes.type = "checkbox";
+ flags.checked = gDialog.inputChecked.checked;
+ break;
+ case 3:
+ attributes.type = "radio";
+ flags.checked = gDialog.inputSelected.checked;
+ break;
+ case 4:
+ attributes.type = "submit";
+ attributes.accesskey = gDialog.inputAccessKey.value;
+ break;
+ case 5:
+ attributes.type = "reset";
+ attributes.accesskey = gDialog.inputAccessKey.value;
+ break;
+ case 6:
+ attributes.type = "file";
+ attributes.accept = gDialog.inputAccept.value;
+ attributes.value = "";
+ break;
+ case 7:
+ attributes.type = "hidden";
+ attributes.tabindex = "";
+ break;
+ case 8:
+ attributes.type = "image";
+ attributes.value = "";
+ break;
+ case 9:
+ attributes.type = "button";
+ attributes.accesskey = gDialog.inputAccessKey.value;
+ break;
+ }
+ for (var a in attributes) {
+ if (attributes[a]) {
+ globalElement.setAttribute(a, attributes[a]);
+ } else {
+ globalElement.removeAttribute(a);
+ }
+ }
+ for (var f in flags) {
+ if (flags[f]) {
+ globalElement.setAttribute(f, "");
+ } else {
+ globalElement.removeAttribute(f);
+ }
+ }
+ return true;
+}
+
+function onAccept(event) {
+ if (ValidateData()) {
+ // All values are valid - copy to actual element in doc or
+ // element created to insert
+
+ var editor = GetCurrentEditor();
+
+ editor.cloneAttributes(inputElement, globalElement);
+
+ if (insertNew) {
+ try {
+ // 'true' means delete the selection before inserting
+ // in case were are converting an image to an input type="image"
+ editor.insertElementAtSelection(inputElement, true);
+ } catch (e) {
+ dump(e);
+ }
+ }
+
+ SaveWindowLocation();
+
+ return;
+ }
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdInputProps.xhtml b/comm/suite/editor/components/dialogs/content/EdInputProps.xhtml
new file mode 100644
index 0000000000..c6011ee896
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInputProps.xhtml
@@ -0,0 +1,135 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edInputProperties SYSTEM "chrome://editor/locale/EditorInputProperties.dtd">
+%edInputProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ buttons="accept,cancel">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdInputProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header" control="InputType" accesskey="&InputType.accesskey;">&InputType.label;</label>
+ </hbox>
+ <menulist id="InputType" oncommand="SelectInputType();">
+ <menupopup>
+ <menuitem label="&text.value;"/>
+ <menuitem label="&password.value;"/>
+ <menuitem label="&checkbox.value;"/>
+ <menuitem label="&radio.value;"/>
+ <menuitem label="&submit.value;"/>
+ <menuitem label="&reset.value;"/>
+ <menuitem label="&file.value;"/>
+ <menuitem label="&hidden.value;"/>
+ <menuitem label="&image.value;"/>
+ <menuitem label="&button.value;"/>
+ </menupopup>
+ </menulist>
+ </groupbox>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&InputSettings.label;</label>
+ </hbox>
+ <grid><columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <deck id="InputNameDeck">
+ <label control="InputName" value="&InputName.label;" accesskey="&InputName.accesskey;"/>
+ <label control="InputName" value="&GroupName.label;" accesskey="&GroupName.accesskey;"/>
+ </deck>
+ <textbox id="InputName" oninput="onInput();"/>
+ </row>
+ <row align="center">
+ <deck id="InputValueDeck">
+ <label control="InputValue" value="&InputValue.label;" accesskey="&InputValue.accesskey;"/>
+ <label control="InputValue" value="&InitialValue.label;" accesskey="&InitialValue.accesskey;"/>
+ </deck>
+ <textbox id="InputValue" oninput="onInput();"/>
+ </row>
+ <row>
+ <spacer/>
+ <deck id="InputDeck" persist="index">
+ <checkbox id="InputChecked" label="&InputChecked.label;" accesskey="&InputChecked.accesskey;"/>
+ <checkbox id="InputSelected" label="&InputSelected.label;" accesskey="&InputSelected.accesskey;"/>
+ <checkbox id="InputReadOnly" label="&InputReadOnly.label;" accesskey="&InputReadOnly.accesskey;"/>
+ </deck>
+ </row>
+ </rows>
+ </grid>
+ <hbox>
+ <button id="MoreFewerButton" oncommand="onMoreFewer();" persist="more"/>
+ </hbox>
+ <grid id="MoreSection" align="start">
+ <columns><column/><column/></columns>
+ <rows>
+ <row>
+ <spacer/>
+ <checkbox id="InputDisabled" label="&InputDisabled.label;" accesskey="&InputDisabled.accesskey;"/>
+ </row>
+ <row align="center">
+ <label control="InputTabIndex" value="&tabIndex.label;" accesskey="&tabIndex.accesskey;"/>
+ <hbox>
+ <textbox id="InputTabIndex" class="narrow" oninput="forceInteger(this.id);"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="InputAccessKey" value="&AccessKey.label;" accesskey="&AccessKey.accesskey;"/>
+ <hbox>
+ <textbox id="InputAccessKey" class="narrow"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="InputSize" value="&TextSize.label;" accesskey="&TextSize.accesskey;"/>
+ <hbox>
+ <textbox id="InputSize" class="narrow" oninput="forceInteger(this.id);"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="InputMaxLength" value="&TextLength.label;" accesskey="&TextLength.accesskey;"/>
+ <hbox>
+ <textbox id="InputMaxLength" class="narrow" oninput="forceInteger(this.id);"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="InputAccept" value="&Accept.label;" accesskey="&Accept.accesskey;"/>
+ <textbox id="InputAccept"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <!-- from EdDialogOverlay -->
+ <hbox flex="1" style="margin-top: 0.2em">
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <deck id="AdvancedEditDeck">
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ <button label="&ImageProperties.label;" accesskey="&ImageProperties.accesskey;" oncommand="doImageProperties();"/>
+ </deck>
+ </hbox>
+ <separator class="groove"/>
+
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdInsSrc.js b/comm/suite/editor/components/dialogs/content/EdInsSrc.js
new file mode 100644
index 0000000000..0f0304ef1e
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsSrc.js
@@ -0,0 +1,160 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Insert Source HTML dialog */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var gFullDataStrings = new Map();
+var gShortDataStrings = new Map();
+var gListenerAttached = false;
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ let editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ document.documentElement.getButton("accept").removeAttribute("default");
+
+ // Create dialog object to store controls for easy access
+ gDialog.srcInput = document.getElementById("srcInput");
+
+ // Attach a paste listener so we can detect pasted data URIs we need to shorten.
+ gDialog.srcInput.addEventListener("paste", onPaste);
+
+ let selection;
+ try {
+ selection = editor.outputToString(
+ "text/html",
+ kOutputFormatted | kOutputSelectionOnly | kOutputWrap
+ );
+ } catch (e) {}
+ if (selection) {
+ selection = selection.replace(/<body[^>]*>/, "").replace(/<\/body>/, "");
+
+ // Shorten data URIs for display.
+ selection = replaceDataURIs(selection);
+
+ if (selection) {
+ gDialog.srcInput.value = selection;
+ }
+ }
+ // Set initial focus
+ gDialog.srcInput.focus();
+ SetWindowLocation();
+}
+
+function replaceDataURIs(input) {
+ return input.replace(/(data:.+;base64,)([^"' >]+)/gi, function(
+ match,
+ nonDataPart,
+ dataPart
+ ) {
+ if (gShortDataStrings.has(dataPart)) {
+ // We found the exact same data URI, just return the shortened URI.
+ return nonDataPart + gShortDataStrings.get(dataPart);
+ }
+
+ let l = 5;
+ let key;
+ // Normally we insert the ellipsis after five characters but if it's not unique
+ // we include more data.
+ do {
+ key = dataPart.substr(0, l) + "…" + dataPart.substr(dataPart.length - 10);
+ l++;
+ } while (gFullDataStrings.has(key) && l < dataPart.length - 10);
+ gFullDataStrings.set(key, dataPart);
+ gShortDataStrings.set(dataPart, key);
+
+ // Attach listeners. In case anyone copies/cuts from the HTML window,
+ // we want to restore the data URI on the clipboard.
+ if (!gListenerAttached) {
+ gDialog.srcInput.addEventListener("copy", onCopyOrCut);
+ gDialog.srcInput.addEventListener("cut", onCopyOrCut);
+ gListenerAttached = true;
+ }
+
+ return nonDataPart + key;
+ });
+}
+
+function onCopyOrCut(event) {
+ let startPos = gDialog.srcInput.selectionStart;
+ if (startPos == undefined) {
+ return;
+ }
+ let endPos = gDialog.srcInput.selectionEnd;
+ let clipboard = gDialog.srcInput.value.substring(startPos, endPos);
+
+ // Add back the original data URIs we stashed away earlier.
+ clipboard = clipboard.replace(/(data:.+;base64,)([^"' >]+)/gi, function(
+ match,
+ nonDataPart,
+ key
+ ) {
+ if (!gFullDataStrings.has(key)) {
+ // User changed data URI.
+ return match;
+ }
+ return nonDataPart + gFullDataStrings.get(key);
+ });
+ event.clipboardData.setData("text/plain", clipboard);
+ if (event.type == "cut") {
+ // We have to cut the selection manually.
+ gDialog.srcInput.value =
+ gDialog.srcInput.value.substr(0, startPos) +
+ gDialog.srcInput.value.substr(endPos);
+ }
+ event.preventDefault();
+}
+
+function onPaste(event) {
+ let startPos = gDialog.srcInput.selectionStart;
+ if (startPos == undefined) {
+ return;
+ }
+ let endPos = gDialog.srcInput.selectionEnd;
+ let clipboard = event.clipboardData.getData("text/plain");
+
+ // We do out own paste by replacing the selection with the pre-processed
+ // clipboard data.
+ gDialog.srcInput.value =
+ gDialog.srcInput.value.substr(0, startPos) +
+ replaceDataURIs(clipboard) +
+ gDialog.srcInput.value.substr(endPos);
+ event.preventDefault();
+}
+
+function onAccept(event) {
+ let html = gDialog.srcInput.value;
+ if (!html) {
+ event.preventDefault();
+ return;
+ }
+
+ // Add back the original data URIs we stashed away earlier.
+ html = html.replace(/(data:.+;base64,)([^"' >]+)/gi, function(
+ match,
+ nonDataPart,
+ key
+ ) {
+ if (!gFullDataStrings.has(key)) {
+ // User changed data URI.
+ return match;
+ }
+ return nonDataPart + gFullDataStrings.get(key);
+ });
+
+ try {
+ GetCurrentEditor().insertHTML(html);
+ } catch (e) {}
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdInsSrc.xhtml b/comm/suite/editor/components/dialogs/content/EdInsSrc.xhtml
new file mode 100644
index 0000000000..32b89ebefd
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsSrc.xhtml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorInsertSource.dtd">
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload = "Startup()"
+ buttonlabelaccept="&insertButton.label;"
+ buttonaccesskeyaccept="&insertButton.accesskey;">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://global/content/globalOverlay.js"/>
+ <script src="chrome://global/content/editMenuOverlay.js"/>
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdInsSrc.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <label id="srcMessage" value="&sourceEditField.label;"/>
+ <vbox flex="1" style="width: 30em; height: 20em;">
+ <html:textarea id="srcInput" rows="18" flex="1"/>
+ </vbox>
+ <!-- Will this accept the embedded HTML tags? -->
+ <hbox>
+ <spacer class="bigspacer"/>
+ <label value="&example.label;"/>
+ <label class="bold" value="&exampleOpenTag.label;"/>
+ <label class="bold italic" value="&exampleText.label;"/>
+ <label class="bold" value="&exampleCloseTag.label;"/>
+ </hbox>
+ <spacer class="spacer"/>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdInsertChars.js b/comm/suite/editor/components/dialogs/content/EdInsertChars.js
new file mode 100644
index 0000000000..6ee88afcdd
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsertChars.js
@@ -0,0 +1,409 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// ------------------------------------------------------------------
+// From Unicode 3.0 Page 54. 3.11 Conjoining Jamo Behavior
+var SBase = 0xac00;
+var LBase = 0x1100;
+var VBase = 0x1161;
+var TBase = 0x11a7;
+var LCount = 19;
+var VCount = 21;
+var TCount = 28;
+var NCount = VCount * TCount;
+// End of Unicode 3.0
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onClose);
+
+// dialog initialization code
+function Startup() {
+ if (!GetCurrentEditor()) {
+ window.close();
+ return;
+ }
+
+ StartupLatin();
+
+ // Set a variable on the opener window so we
+ // can track ownership of close this window with it
+ window.opener.InsertCharWindow = window;
+ window.sizeToContent();
+
+ SetWindowLocation();
+}
+
+function onAccept(event) {
+ // Insert the character
+ try {
+ GetCurrentEditor().insertText(LatinM.label);
+ } catch (e) {}
+
+ // Set persistent attributes to save
+ // which category, letter, and character modifier was used
+ CategoryGroup.setAttribute("category", category);
+ CategoryGroup.setAttribute("letter_index", indexL);
+ CategoryGroup.setAttribute("char_index", indexM);
+
+ // Don't close the dialog
+ event.preventDefault();
+}
+
+// Don't allow inserting in HTML Source Mode
+function onFocus() {
+ var enable = true;
+ if ("gEditorDisplayMode" in window.opener) {
+ enable = !window.opener.IsInHTMLSourceMode();
+ }
+
+ SetElementEnabled(document.documentElement.getButton("accept"), enable);
+}
+
+function onClose() {
+ window.opener.InsertCharWindow = null;
+ SaveWindowLocation();
+}
+
+// ------------------------------------------------------------------
+var LatinL;
+var LatinM;
+var LatinL_Label;
+var LatinM_Label;
+var indexL = 0;
+var indexM = 0;
+var indexM_AU = 0;
+var indexM_AL = 0;
+var indexM_U = 0;
+var indexM_L = 0;
+var indexM_S = 0;
+var LItems = 0;
+var category;
+var CategoryGroup;
+var initialize = true;
+
+function StartupLatin() {
+ LatinL = document.getElementById("LatinL");
+ LatinM = document.getElementById("LatinM");
+ LatinL_Label = document.getElementById("LatinL_Label");
+ LatinM_Label = document.getElementById("LatinM_Label");
+
+ var Symbol = document.getElementById("Symbol");
+ var AccentUpper = document.getElementById("AccentUpper");
+ var AccentLower = document.getElementById("AccentLower");
+ var Upper = document.getElementById("Upper");
+ var Lower = document.getElementById("Lower");
+ CategoryGroup = document.getElementById("CatGrp");
+
+ // Initialize which radio button is set from persistent attribute...
+ var category = CategoryGroup.getAttribute("category");
+
+ // ...as well as indexes into the letter and character lists
+ var index = Number(CategoryGroup.getAttribute("letter_index"));
+ if (index && index >= 0) {
+ indexL = index;
+ }
+ index = Number(CategoryGroup.getAttribute("char_index"));
+ if (index && index >= 0) {
+ indexM = index;
+ }
+
+ switch (category) {
+ case "AccentUpper": // Uppercase Diacritical
+ CategoryGroup.selectedItem = AccentUpper;
+ indexM_AU = indexM;
+ break;
+ case "AccentLower": // Lowercase Diacritical
+ CategoryGroup.selectedItem = AccentLower;
+ indexM_AL = indexM;
+ break;
+ case "Upper": // Uppercase w/o Diacritical
+ CategoryGroup.selectedItem = Upper;
+ indexM_U = indexM;
+ break;
+ case "Lower": // Lowercase w/o Diacritical
+ CategoryGroup.selectedItem = Lower;
+ indexM_L = indexM;
+ break;
+ default:
+ category = "Symbol";
+ CategoryGroup.selectedItem = Symbol;
+ indexM_S = indexM;
+ break;
+ }
+
+ ChangeCategory(category);
+ initialize = false;
+}
+
+function ChangeCategory(newCategory) {
+ if (category != newCategory || initialize) {
+ category = newCategory;
+ // Note: Must do L before M to set LatinL.selectedIndex
+ UpdateLatinL();
+ UpdateLatinM();
+ UpdateCharacter();
+ }
+}
+
+function SelectLatinLetter() {
+ if (LatinL.selectedIndex != indexL) {
+ indexL = LatinL.selectedIndex;
+ UpdateLatinM();
+ UpdateCharacter();
+ }
+}
+
+function SelectLatinModifier() {
+ if (LatinM.selectedIndex != indexM) {
+ indexM = LatinM.selectedIndex;
+ UpdateCharacter();
+ }
+}
+function DisableLatinL(disable) {
+ if (disable) {
+ LatinL_Label.setAttribute("disabled", "true");
+ LatinL.setAttribute("disabled", "true");
+ } else {
+ LatinL_Label.removeAttribute("disabled");
+ LatinL.removeAttribute("disabled");
+ }
+}
+
+function UpdateLatinL() {
+ LatinL.removeAllItems();
+ if (category == "AccentUpper" || category == "AccentLower") {
+ DisableLatinL(false);
+ // No Q or q
+ var alphabet =
+ category == "AccentUpper"
+ ? "ABCDEFGHIJKLMNOPRSTUVWXYZ"
+ : "abcdefghijklmnoprstuvwxyz";
+ for (var letter = 0; letter < alphabet.length; letter++) {
+ LatinL.appendItem(alphabet.charAt(letter));
+ }
+
+ LatinL.selectedIndex = indexL;
+ } else {
+ // Other categories don't hinge on a "letter"
+ DisableLatinL(true);
+ // Note: don't change the indexL so it can be used next time
+ }
+}
+
+function UpdateLatinM() {
+ LatinM.removeAllItems();
+ var i, accent;
+ switch (category) {
+ case "AccentUpper": // Uppercase Diacritical
+ accent = upper[indexL];
+ for (i = 0; i < accent.length; i++) {
+ LatinM.appendItem(accent.charAt(i));
+ }
+
+ if (indexM_AU < accent.length) {
+ indexM = indexM_AU;
+ } else {
+ indexM = accent.length - 1;
+ }
+ indexM_AU = indexM;
+ break;
+
+ case "AccentLower": // Lowercase Diacritical
+ accent = lower[indexL];
+ for (i = 0; i < accent.length; i++) {
+ LatinM.appendItem(accent.charAt(i));
+ }
+
+ if (indexM_AL < accent.length) {
+ indexM = indexM_AL;
+ } else {
+ indexM = lower[indexL].length - 1;
+ }
+ indexM_AL = indexM;
+ break;
+
+ case "Upper": // Uppercase w/o Diacritical
+ for (i = 0; i < otherupper.length; i++) {
+ LatinM.appendItem(otherupper.charAt(i));
+ }
+
+ if (indexM_U < otherupper.length) {
+ indexM = indexM_U;
+ } else {
+ indexM = otherupper.length - 1;
+ }
+ indexM_U = indexM;
+ break;
+
+ case "Lower": // Lowercase w/o Diacritical
+ for (i = 0; i < otherlower.length; i++) {
+ LatinM.appendItem(otherlower.charAt(i));
+ }
+
+ if (indexM_L < otherlower.length) {
+ indexM = indexM_L;
+ } else {
+ indexM = otherlower.length - 1;
+ }
+ indexM_L = indexM;
+ break;
+
+ case "Symbol": // Symbol
+ for (i = 0; i < symbol.length; i++) {
+ LatinM.appendItem(symbol.charAt(i));
+ }
+
+ if (indexM_S < symbol.length) {
+ indexM = indexM_S;
+ } else {
+ indexM = symbol.length - 1;
+ }
+ indexM_S = indexM;
+ break;
+ }
+ LatinM.selectedIndex = indexM;
+}
+
+function UpdateCharacter() {
+ indexM = LatinM.selectedIndex;
+
+ switch (category) {
+ case "AccentUpper": // Uppercase Diacritical
+ indexM_AU = indexM;
+ break;
+ case "AccentLower": // Lowercase Diacritical
+ indexM_AL = indexM;
+ break;
+ case "Upper": // Uppercase w/o Diacritical
+ indexM_U = indexM;
+ break;
+ case "Lower": // Lowercase w/o Diacritical
+ indexM_L = indexM;
+ break;
+ case "Symbol":
+ indexM_S = indexM;
+ break;
+ }
+ // dump("Letter Index="+indexL+", Character Index="+indexM+", Character = "+LatinM.label+"\n");
+}
+
+const upper = [
+ // A
+ "\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u0100\u0102\u0104\u01cd\u01de\u01de\u01e0\u01fa\u0200\u0202\u0226\u1e00\u1ea0\u1ea2\u1ea4\u1ea6\u1ea8\u1eaa\u1eac\u1eae\u1eb0\u1eb2\u1eb4\u1eb6",
+ // B
+ "\u0181\u0182\u0184\u1e02\u1e04\u1e06",
+ // C
+ "\u00c7\u0106\u0108\u010a\u010c\u0187\u1e08",
+ // D
+ "\u010e\u0110\u0189\u018a\u1e0a\u1e0c\u1e0e\u1e10\u1e12",
+ // E
+ "\u00C8\u00C9\u00CA\u00CB\u0112\u0114\u0116\u0118\u011A\u0204\u0206\u0228\u1e14\u1e16\u1e18\u1e1a\u1e1c\u1eb8\u1eba\u1ebc\u1ebe\u1ec0\u1ec2\u1ec4\u1ec6",
+ // F
+ "\u1e1e",
+ // G
+ "\u011c\u011E\u0120\u0122\u01e4\u01e6\u01f4\u1e20",
+ // H
+ "\u0124\u0126\u021e\u1e22\u1e24\u1e26\u1e28\u1e2a",
+ // I
+ "\u00CC\u00CD\u00CE\u00CF\u0128\u012a\u012C\u012e\u0130\u0208\u020a\u1e2c\u1e2e\u1ec8\u1eca",
+ // J
+ "\u0134\u01f0",
+ // K
+ "\u0136\u0198\u01e8\u1e30\u1e32\u1e34",
+ // L
+ "\u0139\u013B\u013D\u013F\u0141\u1e36\u1e38\u1e3a\u1e3c",
+ // M
+ "\u1e3e\u1e40\u1e42",
+ // N
+ "\u00D1\u0143\u0145\u0147\u014A\u01F8\u1e44\u1e46\u1e48\u1e4a",
+ // O
+ "\u00D2\u00D3\u00D4\u00D5\u00D6\u014C\u014E\u0150\u01ea\u01ec\u020c\u020e\u022A\u022C\u022E\u0230\u1e4c\u1e4e\u1e50\u1e52\u1ecc\u1ece\u1ed0\u1ed2\u1ed4\u1ed6\u1ed8\u1eda\u1edc\u1ede\u1ee0\u1ee2",
+ // P
+ "\u1e54\u1e56",
+ // No Q
+ // R
+ "\u0154\u0156\u0158\u0210\u0212\u1e58\u1e5a\u1e5c\u1e5e",
+ // S
+ "\u015A\u015C\u015E\u0160\u0218\u1e60\u1e62\u1e64\u1e66\u1e68",
+ // T
+ "\u0162\u0164\u0166\u021A\u1e6a\u1e6c\u1e6e\u1e70",
+ // U
+ "\u00D9\u00DA\u00DB\u00DC\u0168\u016A\u016C\u016E\u0170\u0172\u0214\u0216\u1e72\u1e74\u1e76\u1e78\u1e7a\u1ee4\u1ee6\u1ee8\u1eea\u1eec\u1eee\u1ef0",
+ // V
+ "\u1e7c\u1e7e",
+ // W
+ "\u0174\u1e80\u1e82\u1e84\u1e86\u1e88",
+ // X
+ "\u1e8a\u1e8c",
+ // Y
+ "\u00DD\u0176\u0178\u0232\u1e8e\u1ef2\u1ef4\u1ef6\u1ef8",
+ // Z
+ "\u0179\u017B\u017D\u0224\u1e90\u1e92\u1e94",
+];
+
+const lower = [
+ // a
+ "\u00e0\u00e1\u00e2\u00e3\u00e4\u00e5\u0101\u0103\u0105\u01ce\u01df\u01e1\u01fb\u0201\u0203\u0227\u1e01\u1e9a\u1ea1\u1ea3\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7",
+ // b
+ "\u0180\u0183\u0185\u1e03\u1e05\u1e07",
+ // c
+ "\u00e7\u0107\u0109\u010b\u010d\u0188\u1e09",
+ // d
+ "\u010f\u0111\u1e0b\u1e0d\u1e0f\u1e11\u1e13",
+ // e
+ "\u00e8\u00e9\u00ea\u00eb\u0113\u0115\u0117\u0119\u011b\u0205\u0207\u0229\u1e15\u1e17\u1e19\u1e1b\u1e1d\u1eb9\u1ebb\u1ebd\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7",
+ // f
+ "\u1e1f",
+ // g
+ "\u011d\u011f\u0121\u0123\u01e5\u01e7\u01f5\u1e21",
+ // h
+ "\u0125\u0127\u021f\u1e23\u1e25\u1e27\u1e29\u1e2b\u1e96",
+ // i
+ "\u00ec\u00ed\u00ee\u00ef\u0129\u012b\u012d\u012f\u0131\u01d0\u0209\u020b\u1e2d\u1e2f\u1ec9\u1ecb",
+ // j
+ "\u0135",
+ // k
+ "\u0137\u0138\u01e9\u1e31\u1e33\u1e35",
+ // l
+ "\u013a\u013c\u013e\u0140\u0142\u1e37\u1e39\u1e3b\u1e3d",
+ // m
+ "\u1e3f\u1e41\u1e43",
+ // n
+ "\u00f1\u0144\u0146\u0148\u0149\u014b\u01f9\u1e45\u1e47\u1e49\u1e4b",
+ // o
+ "\u00f2\u00f3\u00f4\u00f5\u00f6\u014d\u014f\u0151\u01d2\u01eb\u01ed\u020d\u020e\u022b\u022d\u022f\u0231\u1e4d\u1e4f\u1e51\u1e53\u1ecd\u1ecf\u1ed1\u1ed3\u1ed5\u1ed7\u1ed9\u1edb\u1edd\u1edf\u1ee1\u1ee3",
+ // p
+ "\u1e55\u1e57",
+ // No q
+ // r
+ "\u0155\u0157\u0159\u0211\u0213\u1e59\u1e5b\u1e5d\u1e5f",
+ // s
+ "\u015b\u015d\u015f\u0161\u0219\u1e61\u1e63\u1e65\u1e67\u1e69",
+ // t
+ "\u0162\u0163\u0165\u0167\u021b\u1e6b\u1e6d\u1e6f\u1e71\u1e97",
+ // u
+ "\u00f9\u00fa\u00fb\u00fc\u0169\u016b\u016d\u016f\u0171\u0173\u01d4\u01d6\u01d8\u01da\u01dc\u0215\u0217\u1e73\u1e75\u1e77\u1e79\u1e7b\u1ee5\u1ee7\u1ee9\u1eeb\u1eed\u1eef\u1ef1",
+ // v
+ "\u1e7d\u1e7f",
+ // w
+ "\u0175\u1e81\u1e83\u1e85\u1e87\u1e89\u1e98",
+ // x
+ "\u1e8b\u1e8d",
+ // y
+ "\u00fd\u00ff\u0177\u0233\u1e8f\u1e99\u1ef3\u1ef5\u1ef7\u1ef9",
+ // z
+ "\u017a\u017c\u017e\u0225\u1e91\u1e93\u1e95",
+];
+
+const symbol =
+ "\u00a1\u00a2\u00a3\u00a4\u00a5\u20ac\u00a6\u00a7\u00a8\u00a9\u00aa\u00ab\u00ac\u00ae\u00af\u00b0\u00b1\u00b2\u00b3\u00b4\u00b5\u00b6\u00b7\u00b8\u00b9\u00ba\u00bb\u00bc\u00bd\u00be\u00bf\u00d7\u00f7";
+
+const otherupper =
+ "\u00c6\u00d0\u00d8\u00de\u0132\u0152\u0186\u01c4\u01c5\u01c7\u01c8\u01ca\u01cb\u01F1\u01f2";
+
+const otherlower =
+ "\u00e6\u00f0\u00f8\u00fe\u00df\u0133\u0153\u01c6\u01c9\u01cc\u01f3";
diff --git a/comm/suite/editor/components/dialogs/content/EdInsertChars.xhtml b/comm/suite/editor/components/dialogs/content/EdInsertChars.xhtml
new file mode 100644
index 0000000000..4e6c1020fa
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsertChars.xhtml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorInsertChars.dtd">
+
+<dialog id="insertCharsDlg" title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload = "Startup()"
+ onfocus = "onFocus()"
+ buttonlabelaccept="&insertButton.label;"
+ buttonlabelcancel="&closeButton.label;"
+ style = "width: 20em">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdInsertChars.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&category.label;</label>
+ </hbox>
+ <radiogroup id="CatGrp" persist="category letter_index char_index">
+ <radio id="AccentUpper" label="&accentUpper.label;" oncommand="ChangeCategory(this.id)"/>
+ <radio id="AccentLower" label="&accentLower.label;" oncommand="ChangeCategory(this.id)"/>
+ <radio id="Upper" label="&otherUpper.label;" oncommand="ChangeCategory(this.id)"/>
+ <radio id="Lower" label="&otherLower.label;" oncommand="ChangeCategory(this.id)"/>
+ <radio id="Symbol" label="&commonSymbols.label;" oncommand="ChangeCategory(this.id)"/>
+ </radiogroup>
+ <spacer class="spacer"/>
+ </groupbox>
+ <hbox equalsize="always">
+ <vbox flex="1">
+ <!-- value is set in JS from editor.properties strings -->
+ <label id="LatinL_Label" control="LatinL" value="&letter.label;" accesskey="&letter.accessKey;"/>
+ <menulist class="larger" flex="1" id="LatinL" oncommand="SelectLatinLetter()">
+ <menupopup/>
+ </menulist>
+ </vbox>
+ <vbox flex="1">
+ <label id="LatinM_Label" control="LatinM" value="&character.label;" accesskey="&character.accessKey;"/>
+ <menulist class="larger" flex="1" id="LatinM" oncommand="SelectLatinModifier()">
+ <menupopup/>
+ </menulist>
+ </vbox>
+ </hbox>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdInsertMath.js b/comm/suite/editor/components/dialogs/content/EdInsertMath.js
new file mode 100644
index 0000000000..c99bf8edac
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsertMath.js
@@ -0,0 +1,330 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Insert MathML dialog */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ // Create dialog object for easy access
+ gDialog.accept = document.documentElement.getButton("accept");
+ gDialog.mode = document.getElementById("optionMode");
+ gDialog.direction = document.getElementById("optionDirection");
+ gDialog.input = document.getElementById("input");
+ gDialog.output = document.getElementById("output");
+ gDialog.tabbox = document.getElementById("tabboxInsertLaTeXCommand");
+
+ // Set initial focus
+ gDialog.input.focus();
+
+ // Load TeXZilla
+ // TeXZilla.js contains non-ASCII characters and explicitly sets
+ // window.TeXZilla, so we have to specify the charset parameter but don't
+ // need to worry about the targetObj parameter.
+ /* globals TeXZilla */
+ Services.scriptloader.loadSubScript(
+ "chrome://editor/content/TeXZilla.js",
+ {},
+ "UTF-8"
+ );
+
+ // Verify if the selection is on a <math> and initialize the dialog.
+ gDialog.oldMath = editor.getElementOrParentByTagName("math", null);
+ if (gDialog.oldMath) {
+ // When these attributes are absent or invalid, they default to "inline" and "ltr" respectively.
+ gDialog.mode.selectedIndex =
+ gDialog.oldMath.getAttribute("display") == "block" ? 1 : 0;
+ gDialog.direction.selectedIndex =
+ gDialog.oldMath.getAttribute("dir") == "rtl" ? 1 : 0;
+ gDialog.input.value = TeXZilla.getTeXSource(gDialog.oldMath);
+ }
+
+ // Create the tabbox with LaTeX commands.
+ createCommandPanel({
+ "√⅗²": [
+ "{⋯}^{⋯}",
+ "{⋯}_{⋯}",
+ "{⋯}_{⋯}^{⋯}",
+ "\\underset{⋯}{⋯}",
+ "\\overset{⋯}{⋯}",
+ "\\underoverset{⋯}{⋯}{⋯}",
+ "\\left(⋯\\right)",
+ "\\left[⋯\\right]",
+ "\\frac{⋯}{⋯}",
+ "\\binom{⋯}{⋯}",
+ "\\sqrt{⋯}",
+ "\\sqrt[⋯]{⋯}",
+ "\\cos\\left({⋯}\\right)",
+ "\\sin\\left({⋯}\\right)",
+ "\\tan\\left({⋯}\\right)",
+ "\\exp\\left({⋯}\\right)",
+ "\\ln\\left({⋯}\\right)",
+ "\\underbrace{⋯}",
+ "\\underline{⋯}",
+ "\\overbrace{⋯}",
+ "\\widevec{⋯}",
+ "\\widetilde{⋯}",
+ "\\widehat{⋯}",
+ "\\widecheck{⋯}",
+ "\\widebar{⋯}",
+ "\\dot{⋯}",
+ "\\ddot{⋯}",
+ "\\boxed{⋯}",
+ "\\slash{⋯}",
+ ],
+ "(▦)": [
+ "\\begin{matrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{matrix}",
+ "\\begin{pmatrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{pmatrix}",
+ "\\begin{bmatrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{bmatrix}",
+ "\\begin{Bmatrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{Bmatrix}",
+ "\\begin{vmatrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{vmatrix}",
+ "\\begin{Vmatrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{Vmatrix}",
+ "\\begin{cases} ⋯ \\\\ ⋯ \\end{cases}",
+ "\\begin{aligned} ⋯ &= ⋯ \\\\ ⋯ &= ⋯ \\end{aligned}",
+ ],
+ });
+ createSymbolPanels([
+ "∏∐∑∫∬∭⨌∮⊎⊕⊖⊗⊘⊙⋀⋁⋂⋃⌈⌉⌊⌋⎰⎱⟨⟩⟪⟫∥⫼⨀⨁⨂⨄⨅⨆ðıȷℏℑℓ℘ℜℵℶ",
+ "∀∃∄∅∉∊∋∌⊂⊃⊄⊅⊆⊇⊈⊈⊉⊊⊊⊋⊋⊏⊐⊑⊒⊓⊔⊥⋐⋑⋔⫅⫆⫋⫋⫌⫌…⋮⋯⋰⋱♭♮♯∂∇",
+ "±×÷†‡•∓∔∗∘∝∠∡∢∧∨∴∵∼∽≁≃≅≇≈≈≊≍≎≏≐≑≒≓≖≗≜≡≢≬⊚⊛⊞⊡⊢⊣⊤⊥",
+ "⊨⊩⊪⊫⊬⊭⊯⊲⊲⊳⊴⊵⊸⊻⋄⋅⋇⋈⋉⋊⋋⋌⋍⋎⋏⋒⋓⌅⌆⌣△▴▵▸▹▽▾▿◂◃◊○★♠♡♢♣⧫",
+ "≦≧≨≩≩≪≫≮≯≰≱≲≳≶≷≺≻≼≽≾≿⊀⊁⋖⋗⋘⋙⋚⋛⋞⋟⋦⋧⋨⋩⩽⩾⪅⪆⪇⪈⪉⪊⪋⪌⪕⪯⪰⪷⪸⪹⪺",
+ "←↑→↓↔↕↖↗↘↙↜↝↞↠↢↣↦↩↪↫↬↭↭↰↱↼↽↾↿⇀⇁⇂⇃⇄⇆⇇⇈⇉⇊⇋⇌⇐⇑⇒⇓⇕⇖⇗⇘⇙⟺",
+ "αβγδϵ϶εζηθϑικϰλμνξℴπϖρϱσςτυϕφχψωΓΔΘΛΞΠΣϒΦΨΩϝ℧",
+ "𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ",
+ "𝒶𝒷𝒸𝒹ℯ𝒻ℊ𝒽𝒾𝒿𝓀𝓁𝓂𝓃ℴ𝓅𝓆𝓇𝓈𝓉𝓊𝓋𝓌𝓍𝓎𝓏𝒜ℬ𝒞𝒟ℰℱ𝒢ℋℐ𝒥𝒦ℒℳ𝒩𝒪𝒫𝒬ℛ𝒮𝒯𝒰𝒱𝒲𝒳𝒴𝒵",
+ "𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ",
+ ]);
+ gDialog.tabbox.selectedIndex = 0;
+
+ updateMath();
+
+ SetWindowLocation();
+}
+
+function insertLaTeXCommand(aButton) {
+ gDialog.input.focus();
+
+ // For a single math symbol, just use the insertText command.
+ if (aButton.label) {
+ gDialog.input.editor.insertText(aButton.label);
+ return;
+ }
+
+ // Otherwise, it's a LaTeX command with at least one argument...
+ var latex = TeXZilla.getTeXSource(aButton.firstChild);
+ var selectionStart = gDialog.input.selectionStart;
+ var selectionEnd = gDialog.input.selectionEnd;
+
+ // If the selection is not empty, we replace the first argument of the LaTeX
+ // command with the current selection.
+ var selection = gDialog.input.value.substring(selectionStart, selectionEnd);
+ if (selection != "") {
+ latex = latex.replace("⋯", selection);
+ }
+
+ // Try and move to the next position.
+ var latexNewStart = latex.indexOf("⋯"),
+ latexNewEnd;
+ if (latexNewStart == -1) {
+ // This is a unary function and the selection was used as an argument above.
+ // We select the expression again so that one can choose to apply further
+ // command to it or just move the caret after that text.
+ latexNewStart = 0;
+ latexNewEnd = latex.length;
+ } else {
+ // Otherwise, select the dots representing the next argument.
+ latexNewEnd = latexNewStart + 1;
+ }
+
+ // Update the input text and selection.
+ gDialog.input.editor.insertText(latex);
+ gDialog.input.setSelectionRange(
+ selectionStart + latexNewStart,
+ selectionStart + latexNewEnd
+ );
+
+ updateMath();
+}
+
+function createCommandPanel(aCommandPanelList) {
+ const columnCount = 10;
+
+ for (var label in aCommandPanelList) {
+ var commands = aCommandPanelList[label];
+
+ // Create a <rows> element with some LaTeX commands.
+ var rows = document.createXULElement("rows");
+
+ var i = 0,
+ row;
+ for (var command of commands) {
+ if (i % columnCount == 0) {
+ // Create a new row.
+ row = document.createXULElement("row");
+ rows.appendChild(row);
+ }
+
+ // Create a new button to insert the symbol.
+ var button = document.createXULElement("toolbarbutton");
+ button.setAttribute("class", "tabbable");
+ button.appendChild(TeXZilla.toMathML(command));
+ row.appendChild(button);
+
+ i++;
+ }
+
+ // Create a <columns> element with the desired number of columns.
+ var columns = document.createXULElement("columns");
+ for (i = 0; i < columnCount; i++) {
+ var column = document.createXULElement("column");
+ column.setAttribute("flex", "1");
+ columns.appendChild(column);
+ }
+
+ // Create the <grid> element with the <rows> and <columns> children.
+ var grid = document.createXULElement("grid");
+ grid.appendChild(columns);
+ grid.appendChild(rows);
+
+ // Create a new <tab> element.
+ var tab = document.createXULElement("tab");
+ tab.setAttribute("label", label);
+ gDialog.tabbox.tabs.appendChild(tab);
+
+ // Append the new tab panel.
+ gDialog.tabbox.tabpanels.appendChild(grid);
+ }
+}
+
+function createSymbolPanels(aSymbolPanelList) {
+ const columnCount = 13,
+ tabLabelLength = 3;
+
+ for (var symbols of aSymbolPanelList) {
+ // Create a <rows> element with the symbols of the i-th panel.
+ var rows = document.createXULElement("rows");
+ var i = 0,
+ tabLabel = "",
+ row;
+ for (var symbol of symbols) {
+ if (i % columnCount == 0) {
+ // Create a new row.
+ row = document.createXULElement("row");
+ rows.appendChild(row);
+ }
+
+ // Build the tab label from the first symbols of this tab.
+ if (i < tabLabelLength) {
+ tabLabel += symbol;
+ }
+
+ // Create a new button to insert the symbol.
+ var button = document.createXULElement("toolbarbutton");
+ button.setAttribute("label", symbol);
+ button.setAttribute("class", "tabbable");
+ row.appendChild(button);
+
+ i++;
+ }
+
+ // Create a <columns> element with the desired number of columns.
+ var columns = document.createXULElement("columns");
+ for (i = 0; i < columnCount; i++) {
+ var column = document.createXULElement("column");
+ column.setAttribute("flex", "1");
+ columns.appendChild(column);
+ }
+
+ // Create the <grid> element with the <rows> and <columns> children.
+ var grid = document.createXULElement("grid");
+ grid.appendChild(columns);
+ grid.appendChild(rows);
+
+ // Create a new <tab> element with the label determined above.
+ var tab = document.createXULElement("tab");
+ tab.setAttribute("label", tabLabel);
+ gDialog.tabbox.tabs.appendChild(tab);
+
+ // Append the new tab panel.
+ gDialog.tabbox.tabpanels.appendChild(grid);
+ }
+}
+
+function onAccept(event) {
+ if (gDialog.output.firstChild) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+
+ try {
+ var newMath = editor.document.importNode(gDialog.output.firstChild, true);
+ if (gDialog.oldMath) {
+ // Replace the old <math> element with the new one.
+ editor.selectElement(gDialog.oldMath);
+ editor.insertElementAtSelection(newMath, true);
+ } else {
+ // Insert the new <math> element.
+ editor.insertElementAtSelection(newMath, false);
+ }
+ } catch (e) {}
+
+ editor.endTransaction();
+ } else {
+ dump("Null value -- not inserting in MathML Source dialog\n");
+ event.preventDefault();
+ }
+ SaveWindowLocation();
+}
+
+function updateMath() {
+ // Remove the preview, if any.
+ if (gDialog.output.firstChild) {
+ gDialog.output.firstChild.remove();
+ }
+
+ // Try to convert the LaTeX source into MathML using TeXZilla.
+ // We use the placeholder text if no input is provided.
+ try {
+ var input = gDialog.input.value || gDialog.input.placeholder;
+ var newMath = TeXZilla.toMathML(
+ input,
+ gDialog.mode.selectedIndex,
+ gDialog.direction.selectedIndex,
+ true
+ );
+ gDialog.output.appendChild(document.importNode(newMath, true));
+ gDialog.output.style.opacity = gDialog.input.value ? 1 : 0.5;
+ } catch (e) {}
+ // Disable the accept button if parsing fails or when the placeholder is used.
+ gDialog.accept.disabled = !gDialog.input.value || !gDialog.output.firstChild;
+}
+
+function updateMode() {
+ if (gDialog.output.firstChild) {
+ gDialog.output.firstChild.setAttribute(
+ "display",
+ gDialog.mode.selectedIndex ? "block" : "inline"
+ );
+ }
+}
+
+function updateDirection() {
+ if (gDialog.output.firstChild) {
+ gDialog.output.firstChild.setAttribute(
+ "dir",
+ gDialog.direction.selectedIndex ? "rtl" : "ltr"
+ );
+ }
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdInsertMath.xhtml b/comm/suite/editor/components/dialogs/content/EdInsertMath.xhtml
new file mode 100644
index 0000000000..9138d00846
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsertMath.xhtml
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+- License, v. 2.0. If a copy of the MPL was not distributed with this
+- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorInsertMath.dtd">
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ buttonlabelaccept="&insertButton.label;"
+ buttonaccesskeyaccept="&insertButton.accesskey;">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://global/content/globalOverlay.js"/>
+ <script src="chrome://global/content/editMenuOverlay.js"/>
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdInsertMath.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <label id="srcMessage" value="&sourceEditField.label;"/>
+ <html:textarea id="input" rows="5" oninput="updateMath();"
+ placeholder="\sqrt{x_1} + \frac{π^3}{2}"/>
+ <vbox flex="1" style="overflow: auto; width: 30em; height: 5em;">
+ <description id="output"/>
+ </vbox>
+ <tabbox id="tabboxInsertLaTeXCommand">
+ <tabs/>
+ <tabpanels oncommand="insertLaTeXCommand(event.target);"/>
+ </tabbox>
+ <spacer class="spacer"/>
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&options.label;</label>
+ </hbox>
+ <hbox>
+ <radiogroup id="optionMode" oncommand="updateMode();">
+ <radio label="&optionInline.label;"
+ accesskey="&optionInline.accesskey;"/>
+ <radio label="&optionDisplay.label;"
+ accesskey="&optionDisplay.accesskey;"/>
+ </radiogroup>
+ <radiogroup id="optionDirection" oncommand="updateDirection();">
+ <radio label="&optionLTR.label;"
+ accesskey="&optionLTR.accesskey;"/>
+ <radio label="&optionRTL.label;"
+ accesskey="&optionRTL.accesskey;"/>
+ </radiogroup>
+ </hbox>
+ </groupbox>
+ <spacer class="spacer"/>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdInsertTOC.js b/comm/suite/editor/components/dialogs/content/EdInsertTOC.js
new file mode 100644
index 0000000000..3ec386f7c9
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsertTOC.js
@@ -0,0 +1,378 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// tocHeadersArray is the array containing the pairs tag/class
+// defining TOC entries
+var tocHeadersArray = new Array(6);
+
+// a global used when building the TOC
+var currentHeaderLevel = 0;
+
+// a global set to true if the TOC is to be readonly
+var readonly = false;
+
+// a global set to true if user wants indexes in the TOC
+var orderedList = true;
+
+// constants
+const kMozToc = "mozToc";
+const kMozTocLength = 6;
+const kMozTocIdPrefix = "mozTocId";
+const kMozTocIdPrefixLength = 8;
+const kMozTocClassPrefix = "mozToc";
+const kMozTocClassPrefixLength = 6;
+
+document.addEventListener("dialogaccept", () => BuildTOC(true));
+
+// Startup() is called when EdInsertTOC.xhtml is opened
+function Startup() {
+ // early way out if if we have no editor
+ if (!GetCurrentEditor()) {
+ window.close();
+ return;
+ }
+
+ var i;
+ // clean the table of tag/class pairs we look for
+ for (i = 0; i < 6; ++i) {
+ tocHeadersArray[i] = ["", ""];
+ }
+
+ // reset all settings
+ for (i = 1; i < 7; ++i) {
+ var menulist = document.getElementById("header" + i + "Menulist");
+ var menuitem = document.getElementById("header" + i + "none");
+ var textbox = document.getElementById("header" + i + "Class");
+ menulist.selectedItem = menuitem;
+ textbox.setAttribute("disabled", "true");
+ }
+
+ var theDocument = GetCurrentEditor().document;
+
+ // do we already have a TOC in the document ? It should have "mozToc" ID
+ var toc = theDocument.getElementById(kMozToc);
+
+ // default TOC definition, use h1-h6 for TOC entry levels 1-6
+ var headers = "h1 1 h2 2 h3 3 h4 4 h5 5 h6 6";
+
+ var orderedListCheckbox = document.getElementById("orderedListCheckbox");
+ orderedListCheckbox.checked = true;
+
+ if (toc) {
+ // man, there is already a TOC here
+
+ if (toc.getAttribute("class") == "readonly") {
+ // and it's readonly
+ var checkbox = document.getElementById("readOnlyCheckbox");
+ checkbox.checked = true;
+ readonly = true;
+ }
+
+ // let's see if it's an OL or an UL
+ orderedList = toc.nodeName.toLowerCase() == "ol";
+ orderedListCheckbox.checked = orderedList;
+
+ var nodeList = toc.childNodes;
+ // let's look at the children of the TOC ; if we find a comment beginning
+ // with "mozToc", it contains the TOC definition
+ for (i = 0; i < nodeList.length; ++i) {
+ if (
+ nodeList.item(i).nodeType == Node.COMMENT_NODE &&
+ nodeList.item(i).data.startsWith(kMozToc)
+ ) {
+ // yep, there is already a definition here; parse it !
+ headers = nodeList
+ .item(i)
+ .data.substr(
+ kMozTocLength + 1,
+ nodeList.item(i).length - kMozTocLength - 1
+ );
+ break;
+ }
+ }
+ }
+
+ // let's get an array filled with the (tag.class, index level) pairs
+ var headersArray = headers.split(" ");
+
+ for (i = 0; i < headersArray.length; i += 2) {
+ var tag = headersArray[i],
+ className = "";
+ var index = headersArray[i + 1];
+ menulist = document.getElementById("header" + index + "Menulist");
+ if (menulist) {
+ var sep = tag.indexOf(".");
+ if (sep != -1) {
+ // the tag variable contains in fact "tag.className", let's parse
+ // the class and get the real tag name
+ var tmp = tag.substr(0, sep);
+ className = tag.substr(sep + 1, tag.length - sep - 1);
+ tag = tmp;
+ }
+
+ // update the dialog
+ menuitem = document.getElementById("header" + index + tag.toUpperCase());
+ textbox = document.getElementById("header" + index + "Class");
+ menulist.selectedItem = menuitem;
+ if (tag != "") {
+ textbox.removeAttribute("disabled");
+ }
+ if (className != "") {
+ textbox.value = className;
+ }
+ tocHeadersArray[index - 1] = [tag, className];
+ }
+ }
+}
+
+function BuildTOC(update) {
+ // controlClass() is a node filter that accepts a node if
+ // (a) we don't look for a class (b) we look for a class and
+ // node has it
+ function controlClass(node, index) {
+ currentHeaderLevel = index + 1;
+ if (tocHeadersArray[index][1] == "") {
+ // we are not looking for a specific class, this node is ok
+ return NodeFilter.FILTER_ACCEPT;
+ }
+ if (node.getAttribute("class")) {
+ // yep, we look for a class, let's look at all the classes
+ // the node has
+ var classArray = node.getAttribute("class").split(" ");
+ for (var j = 0; j < classArray.length; j++) {
+ if (classArray[j] == tocHeadersArray[index][1]) {
+ // hehe, we found it...
+ return NodeFilter.FILTER_ACCEPT;
+ }
+ }
+ }
+ return NodeFilter.FILTER_SKIP;
+ }
+
+ // the main node filter for our node iterator
+ // it selects the tag names as specified in the dialog
+ // then calls the controlClass filter above
+ function acceptNode(node) {
+ switch (node.nodeName.toLowerCase()) {
+ case tocHeadersArray[0][0]:
+ return controlClass(node, 0);
+ case tocHeadersArray[1][0]:
+ return controlClass(node, 1);
+ case tocHeadersArray[2][0]:
+ return controlClass(node, 2);
+ case tocHeadersArray[3][0]:
+ return controlClass(node, 3);
+ case tocHeadersArray[4][0]:
+ return controlClass(node, 4);
+ case tocHeadersArray[5][0]:
+ return controlClass(node, 5);
+ default:
+ return NodeFilter.FILTER_SKIP;
+ }
+ }
+
+ var editor = GetCurrentEditor();
+ var theDocument = editor.document;
+ // let's create a TreeWalker to look for our nodes
+ var treeWalker = theDocument.createTreeWalker(
+ theDocument.documentElement,
+ NodeFilter.SHOW_ELEMENT,
+ acceptNode,
+ true
+ );
+ // we need an array to store all TOC entries we find in the document
+ var tocArray = [];
+ if (treeWalker) {
+ var tocSourceNode = treeWalker.nextNode();
+ while (tocSourceNode) {
+ var headerIndex = currentHeaderLevel;
+
+ // we have a node, we need to get all its textual contents
+ var textTreeWalker = theDocument.createTreeWalker(
+ tocSourceNode,
+ NodeFilter.SHOW_TEXT,
+ null,
+ true
+ );
+ var textNode = textTreeWalker.nextNode(),
+ headerText = "";
+ while (textNode) {
+ headerText += textNode.data;
+ textNode = textTreeWalker.nextNode();
+ }
+
+ var anchor = tocSourceNode.firstChild,
+ id;
+ // do we have a named anchor as 1st child of our node ?
+ if (
+ anchor.nodeName.toLowerCase() == "a" &&
+ anchor.hasAttribute("name") &&
+ anchor.getAttribute("name").startsWith(kMozTocIdPrefix)
+ ) {
+ // yep, get its name
+ id = anchor.getAttribute("name");
+ } else {
+ // no we don't and we need to create one
+ anchor = theDocument.createElement("a");
+ tocSourceNode.insertBefore(anchor, tocSourceNode.firstChild);
+ // let's give it a random ID
+ var c = 1000000 * Math.random();
+ id = kMozTocIdPrefix + Math.round(c);
+ anchor.setAttribute("name", id);
+ anchor.setAttribute(
+ "class",
+ kMozTocClassPrefix + tocSourceNode.nodeName.toUpperCase()
+ );
+ }
+ // and store that new entry in our array
+ tocArray.push(headerIndex, headerText, id);
+ tocSourceNode = treeWalker.nextNode();
+ }
+ }
+
+ /* generate the TOC itself */
+ headerIndex = 0;
+ var item, toc;
+ for (var i = 0; i < tocArray.length; i += 3) {
+ if (!headerIndex) {
+ // do we need to create an ol/ul container for the first entry ?
+ ++headerIndex;
+ toc = theDocument.getElementById(kMozToc);
+ if (!toc || !update) {
+ // we need to create a list container for the table of contents
+ toc = GetCurrentEditor().createElementWithDefaults(
+ orderedList ? "ol" : "ul"
+ );
+ // grrr, we need to create a LI inside the list otherwise
+ // Composer will refuse an empty list and will remove it !
+ var pit = theDocument.createElement("li");
+ toc.appendChild(pit);
+ GetCurrentEditor().insertElementAtSelection(toc, true);
+ // ah, now it's inserted so let's remove the useless list item...
+ toc.removeChild(pit);
+ // we need to recognize later that this list is our TOC
+ toc.setAttribute("id", kMozToc);
+ } else if (orderedList != (toc.nodeName.toLowerCase() == "ol")) {
+ // we have to update an existing TOC, is the existing TOC of the
+ // desired type (ordered or not) ?
+
+ // nope, we have to recreate the list
+ var newToc = GetCurrentEditor().createElementWithDefaults(
+ orderedList ? "ol" : "ul"
+ );
+ toc.parentNode.insertBefore(newToc, toc);
+ // and remove the old one
+ toc.remove();
+ toc = newToc;
+ toc.setAttribute("id", kMozToc);
+ } else {
+ // we can keep the list itself but let's get rid of the TOC entries
+ while (toc.hasChildNodes()) {
+ toc.lastChild.remove();
+ }
+ }
+
+ var commentText = "mozToc ";
+ for (var j = 0; j < 6; j++) {
+ if (tocHeadersArray[j][0] != "") {
+ commentText += tocHeadersArray[j][0];
+ if (tocHeadersArray[j][1] != "") {
+ commentText += "." + tocHeadersArray[j][1];
+ }
+ commentText += " " + (j + 1) + " ";
+ }
+ }
+ // important, we have to remove trailing spaces
+ commentText = TrimStringRight(commentText);
+
+ // forge a comment we'll insert in the TOC ; that comment will hold
+ // the TOC definition for us
+ var ct = theDocument.createComment(commentText);
+ toc.appendChild(ct);
+
+ // assign a special class to the TOC top element if the TOC is readonly
+ // the definition of this class is in EditorOverride.css
+ if (readonly) {
+ toc.setAttribute("class", "readonly");
+ } else {
+ toc.removeAttribute("class");
+ }
+
+ // We need a new variable to hold the local ul/ol container
+ // The toplevel TOC element is not the parent element of a
+ // TOC entry if its depth is > 1...
+ var tocList = toc;
+ // create a list item
+ var tocItem = theDocument.createElement("li");
+ // and an anchor in this list item
+ var tocAnchor = theDocument.createElement("a");
+ // make it target the source of the TOC entry
+ tocAnchor.setAttribute("href", "#" + tocArray[i + 2]);
+ // and put the textual contents of the TOC entry in that anchor
+ var tocEntry = theDocument.createTextNode(tocArray[i + 1]);
+ // now, insert everything where it has to be inserted
+ tocAnchor.appendChild(tocEntry);
+ tocItem.appendChild(tocAnchor);
+ tocList.appendChild(tocItem);
+ item = tocList;
+ } else {
+ if (tocArray[i] < headerIndex) {
+ // if the depth of the new TOC entry is less than the depth of the
+ // last entry we created, find the good ul/ol ancestor
+ for (j = headerIndex - tocArray[i]; j > 0; --j) {
+ if (item != toc) {
+ item = item.parentNode.parentNode;
+ }
+ }
+ tocItem = theDocument.createElement("li");
+ } else if (tocArray[i] > headerIndex) {
+ // to the contrary, it's deeper than the last one
+ // we need to create sub ul/ol's and li's
+ for (j = tocArray[i] - headerIndex; j > 0; --j) {
+ tocList = theDocument.createElement(orderedList ? "ol" : "ul");
+ item.lastChild.appendChild(tocList);
+ tocItem = theDocument.createElement("li");
+ tocList.appendChild(tocItem);
+ item = tocList;
+ }
+ } else {
+ tocItem = theDocument.createElement("li");
+ }
+ tocAnchor = theDocument.createElement("a");
+ tocAnchor.setAttribute("href", "#" + tocArray[i + 2]);
+ tocEntry = theDocument.createTextNode(tocArray[i + 1]);
+ tocAnchor.appendChild(tocEntry);
+ tocItem.appendChild(tocAnchor);
+ item.appendChild(tocItem);
+ headerIndex = tocArray[i];
+ }
+ }
+ SaveWindowLocation();
+}
+
+function selectHeader(elt, index) {
+ var tag = elt.value;
+ tocHeadersArray[index - 1][0] = tag;
+ var textbox = document.getElementById("header" + index + "Class");
+ if (tag == "") {
+ textbox.setAttribute("disabled", "true");
+ } else {
+ textbox.removeAttribute("disabled");
+ }
+}
+
+function changeClass(elt, index) {
+ tocHeadersArray[index - 1][1] = elt.value;
+}
+
+function ToggleReadOnlyToc(elt) {
+ readonly = elt.checked;
+}
+
+function ToggleOrderedList(elt) {
+ orderedList = elt.checked;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdInsertTOC.xhtml b/comm/suite/editor/components/dialogs/content/EdInsertTOC.xhtml
new file mode 100644
index 0000000000..8d82bac046
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsertTOC.xhtml
@@ -0,0 +1,225 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorInsertTOC.dtd">
+
+<dialog title="&Window.title;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ oncancel="window.close(); return true;">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdInsertTOC.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+ <spacer id="dummy" style="display:none"/>
+ <vbox flex="1">
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&buildToc.label;</label>
+ </hbox>
+ <grid>
+ <columns><column/><column style="min-width: 6em"/><column/></columns>
+ <rows>
+ <row align="center">
+ <spacer/>
+ <label value="&tag.label;"/>
+ <label value="&class.label;"/>
+ </row>
+ <row align="center">
+ <label value="&header1.label;"/>
+ <menulist id="header1Menulist">
+ <menupopup>
+ <menuitem id="header1none" label="--" value=""
+ oncommand="selectHeader(this, 1)"/>
+ <menuseparator/>
+ <menuitem id="header1H1" label="h1" value="h1"
+ oncommand="selectHeader(this, 1)"/>
+ <menuitem id="header1H2" label="h2" value="h2"
+ oncommand="selectHeader(this, 1)"/>
+ <menuitem id="header1H3" label="h3" value="h3"
+ oncommand="selectHeader(this, 1)"/>
+ <menuitem id="header1H4" label="h4" value="h4"
+ oncommand="selectHeader(this, 1)"/>
+ <menuitem id="header1H5" label="h5" value="h5"
+ oncommand="selectHeader(this, 1)"/>
+ <menuitem id="header1H6" label="h6" value="h6"
+ oncommand="selectHeader(this, 1)"/>
+ <menuitem id="header1DIV" label="div" value="div"
+ oncommand="selectHeader(this, 1)"/>
+ <menuitem id="header1P" label="p" value="p"
+ oncommand="selectHeader(this, 1)"/>
+ </menupopup>
+ </menulist>
+ <textbox id="header1Class" size="10"
+ oninput="changeClass(this, 1)"/>
+ </row>
+
+ <row align="center">
+ <label value="&header2.label;"/>
+ <menulist id="header2Menulist">
+ <menupopup>
+ <menuitem id="header2none" label="--" value=""
+ oncommand="selectHeader(this, 2)"/>
+ <menuseparator/>
+ <menuitem id="header2H1" label="h1" value="h1"
+ oncommand="selectHeader(this, 2)"/>
+ <menuitem id="header2H2" label="h2" value="h2"
+ oncommand="selectHeader(this, 2)"/>
+ <menuitem id="header2H3" label="h3" value="h3"
+ oncommand="selectHeader(this, 2)"/>
+ <menuitem id="header2H4" label="h4" value="h4"
+ oncommand="selectHeader(this, 2)"/>
+ <menuitem id="header2H5" label="h5" value="h5"
+ oncommand="selectHeader(this, 2)"/>
+ <menuitem id="header2H6" label="h6" value="h6"
+ oncommand="selectHeader(this, 2)"/>
+ <menuitem id="header2DIV" label="div" value="div"
+ oncommand="selectHeader(this, 2)"/>
+ <menuitem id="header2P" label="p" value="p"
+ oncommand="selectHeader(this, 2)"/>
+ </menupopup>
+ </menulist>
+ <textbox id="header2Class" size="10"
+ oninput="changeClass(this, 2)"/>
+ </row>
+
+ <row align="center">
+ <label value="&header3.label;"/>
+ <menulist id="header3Menulist">
+ <menupopup>
+ <menuitem id="header3none" label="--" value=""
+ oncommand="selectHeader(this, 3)"/>
+ <menuseparator/>
+ <menuitem id="header3H1" label="h1" value="h1"
+ oncommand="selectHeader(this, 3)"/>
+ <menuitem id="header3H2" label="h2" value="h2"
+ oncommand="selectHeader(this, 3)"/>
+ <menuitem id="header3H3" label="h3" value="h3"
+ oncommand="selectHeader(this, 3)"/>
+ <menuitem id="header3H4" label="h4" value="h4"
+ oncommand="selectHeader(this, 3)"/>
+ <menuitem id="header3H5" label="h5" value="h5"
+ oncommand="selectHeader(this, 3)"/>
+ <menuitem id="header3H6" label="h6" value="h6"
+ oncommand="selectHeader(this, 3)"/>
+ <menuitem id="header3DIV" label="div" value="div"
+ oncommand="selectHeader(this, 3)"/>
+ <menuitem id="header3P" label="p" value="p"
+ oncommand="selectHeader(this, 3)"/>
+ </menupopup>
+ </menulist>
+ <textbox id="header3Class" size="10"
+ oninput="changeClass(this, 3)"/>
+ </row>
+
+ <row align="center">
+ <label value="&header4.label;"/>
+ <menulist id="header4Menulist">
+ <menupopup>
+ <menuitem id="header4none" label="--" value=""
+ oncommand="selectHeader(this, 4)"/>
+ <menuseparator/>
+ <menuitem id="header4H1" label="h1" value="h1"
+ oncommand="selectHeader(this, 4)"/>
+ <menuitem id="header4H2" label="h2" value="h2"
+ oncommand="selectHeader(this, 4)"/>
+ <menuitem id="header4H3" label="h3" value="h3"
+ oncommand="selectHeader(this, 4)"/>
+ <menuitem id="header4H4" label="h4" value="h4"
+ oncommand="selectHeader(this, 4)"/>
+ <menuitem id="header4H5" label="h5" value="h5"
+ oncommand="selectHeader(this, 4)"/>
+ <menuitem id="header4H6" label="h6" value="h6"
+ oncommand="selectHeader(this, 4)"/>
+ <menuitem id="header4DIV" label="div" value="div"
+ oncommand="selectHeader(this, 4)"/>
+ <menuitem id="header4P" label="p" value="p"
+ oncommand="selectHeader(this, 4)"/>
+ </menupopup>
+ </menulist>
+ <textbox id="header4Class" size="10"
+ oninput="changeClass(this, 4)"/>
+ </row>
+
+ <row align="center">
+ <label value="&header5.label;"/>
+ <menulist id="header5Menulist">
+ <menupopup>
+ <menuitem id="header5none" label="--" value=""
+ oncommand="selectHeader(this, 5)"/>
+ <menuseparator/>
+ <menuitem id="header5H1" label="h1" value="h1"
+ oncommand="selectHeader(this, 5)"/>
+ <menuitem id="header5H2" label="h2" value="h2"
+ oncommand="selectHeader(this, 5)"/>
+ <menuitem id="header5H3" label="h3" value="h3"
+ oncommand="selectHeader(this, 5)"/>
+ <menuitem id="header5H4" label="h4" value="h4"
+ oncommand="selectHeader(this, 5)"/>
+ <menuitem id="header5H5" label="h5" value="h5"
+ oncommand="selectHeader(this, 5)"/>
+ <menuitem id="header5H6" label="h6" value="h6"
+ oncommand="selectHeader(this, 5)"/>
+ <menuitem id="header5DIV" label="div" value="div"
+ oncommand="selectHeader(this, 5)"/>
+ <menuitem id="header5P" label="p" value="p"
+ oncommand="selectHeader(this, 5)"/>
+ </menupopup>
+ </menulist>
+ <textbox id="header5Class" size="10"
+ oninput="changeClass(this, 5)"/>
+ </row>
+
+ <row align="center">
+ <label value="&header6.label;"/>
+ <menulist id="header6Menulist">
+ <menupopup>
+ <menuitem id="header6none" label="--" value=""
+ oncommand="selectHeader(this, 6)"/>
+ <menuseparator/>
+ <menuitem id="header6H1" label="h1" value="h1"
+ oncommand="selectHeader(this, 6)"/>
+ <menuitem id="header6H2" label="h2" value="h2"
+ oncommand="selectHeader(this, 6)"/>
+ <menuitem id="header6H3" label="h3" value="h3"
+ oncommand="selectHeader(this, 6)"/>
+ <menuitem id="header6H4" label="h4" value="h4"
+ oncommand="selectHeader(this, 6)"/>
+ <menuitem id="header6H5" label="h5" value="h5"
+ oncommand="selectHeader(this, 6)"/>
+ <menuitem id="header6H6" label="h6" value="h6"
+ oncommand="selectHeader(this, 6)"/>
+ <menuitem id="header6DIV" label="div" value="div"
+ oncommand="selectHeader(this, 6)"/>
+ <menuitem id="header6P" label="p" value="p"
+ oncommand="selectHeader(this, 6)"/>
+ </menupopup>
+ </menulist>
+ <textbox id="header6Class" size="10"
+ oninput="changeClass(this, 6)"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+ <vbox>
+ <checkbox id="orderedListCheckbox"
+ label="&orderedList.label;"
+ oncommand="ToggleOrderedList(this)"/>
+ <checkbox id="readOnlyCheckbox"
+ label="&makeReadOnly.label;"
+ oncommand="ToggleReadOnlyToc(this)"/>
+ </vbox>
+ <separator class="groove"/>
+ </vbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdInsertTable.js b/comm/suite/editor/components/dialogs/content/EdInsertTable.js
new file mode 100644
index 0000000000..0053f9fd94
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsertTable.js
@@ -0,0 +1,254 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// Cancel() is in EdDialogCommon.js
+
+var gTableElement = null;
+var gRows;
+var gColumns;
+var gActiveEditor;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ gActiveEditor = GetCurrentTableEditor();
+ if (!gActiveEditor) {
+ dump("Failed to get active editor!\n");
+ window.close();
+ return;
+ }
+
+ try {
+ gTableElement = gActiveEditor.createElementWithDefaults("table");
+ } catch (e) {}
+
+ if (!gTableElement) {
+ dump("Failed to create a new table!\n");
+ window.close();
+ return;
+ }
+ gDialog.rowsInput = document.getElementById("rowsInput");
+ gDialog.columnsInput = document.getElementById("columnsInput");
+ gDialog.widthInput = document.getElementById("widthInput");
+ gDialog.borderInput = document.getElementById("borderInput");
+ gDialog.widthPixelOrPercentMenulist = document.getElementById(
+ "widthPixelOrPercentMenulist"
+ );
+ gDialog.OkButton = document.documentElement.getButton("accept");
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = gTableElement.cloneNode(false);
+ try {
+ if (
+ Services.prefs.getBoolPref("editor.use_css") &&
+ IsHTMLEditor() &&
+ !(gActiveEditor.flags & Ci.nsIEditor.eEditorMailMask)
+ ) {
+ // only for Composer and not for htmlmail
+ globalElement.setAttribute("style", "text-align: left;");
+ }
+ } catch (e) {}
+
+ // Initialize all widgets with image attributes
+ InitDialog();
+
+ // Set initial number to 2 rows, 2 columns:
+ // Note, these are not attributes on the table,
+ // so don't put them in InitDialog(),
+ // else the user's values will be trashed when they use
+ // the Advanced Edit dialog
+ gDialog.rowsInput.value = 2;
+ gDialog.columnsInput.value = 2;
+
+ // If no default value on the width, set to 100%
+ if (gDialog.widthInput.value.length == 0) {
+ gDialog.widthInput.value = "100";
+ gDialog.widthPixelOrPercentMenulist.selectedIndex = 1;
+ }
+
+ SetTextboxFocusById("rowsInput");
+
+ SetWindowLocation();
+}
+
+// Set dialog widgets with attribute data
+// We get them from globalElement copy so this can be used
+// by AdvancedEdit(), which is shared by all property dialogs
+function InitDialog() {
+ // Get default attributes set on the created table:
+ // Get the width attribute of the element, stripping out "%"
+ // This sets contents of menu combobox list
+ // 2nd param = null: Use current selection to find if parent is table cell or window
+ gDialog.widthInput.value = InitPixelOrPercentMenulist(
+ globalElement,
+ null,
+ "width",
+ "widthPixelOrPercentMenulist",
+ gPercent
+ );
+ gDialog.borderInput.value = globalElement.getAttribute("border");
+}
+
+function ChangeRowOrColumn(id) {
+ // Allow only integers
+ forceInteger(id);
+
+ // Enable OK only if both rows and columns have a value > 0
+ var enable =
+ gDialog.rowsInput.value.length > 0 &&
+ gDialog.rowsInput.value > 0 &&
+ gDialog.columnsInput.value.length > 0 &&
+ gDialog.columnsInput.value > 0;
+
+ SetElementEnabled(gDialog.OkButton, enable);
+ SetElementEnabledById("AdvancedEditButton1", enable);
+}
+
+// Get and validate data from widgets.
+// Set attributes on globalElement so they can be accessed by AdvancedEdit()
+function ValidateData() {
+ gRows = ValidateNumber(
+ gDialog.rowsInput,
+ null,
+ 1,
+ gMaxRows,
+ null,
+ null,
+ true
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ gColumns = ValidateNumber(
+ gDialog.columnsInput,
+ null,
+ 1,
+ gMaxColumns,
+ null,
+ null,
+ true
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ // Set attributes: NOTE: These may be empty strings (last param = false)
+ ValidateNumber(
+ gDialog.borderInput,
+ null,
+ 0,
+ gMaxPixels,
+ globalElement,
+ "border",
+ false
+ );
+ // TODO: Deal with "BORDER" without value issue
+ if (gValidationError) {
+ return false;
+ }
+
+ ValidateNumber(
+ gDialog.widthInput,
+ gDialog.widthPixelOrPercentMenulist,
+ 1,
+ gMaxTableSize,
+ globalElement,
+ "width",
+ false
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ return true;
+}
+
+function onAccept(event) {
+ if (ValidateData()) {
+ gActiveEditor.beginTransaction();
+ try {
+ gActiveEditor.cloneAttributes(gTableElement, globalElement);
+
+ // Create necessary rows and cells for the table
+ var tableBody = gActiveEditor.createElementWithDefaults("tbody");
+ if (tableBody) {
+ gTableElement.appendChild(tableBody);
+
+ // Create necessary rows and cells for the table
+ for (var i = 0; i < gRows; i++) {
+ var newRow = gActiveEditor.createElementWithDefaults("tr");
+ if (newRow) {
+ tableBody.appendChild(newRow);
+ for (var j = 0; j < gColumns; j++) {
+ var newCell = gActiveEditor.createElementWithDefaults("td");
+ if (newCell) {
+ newRow.appendChild(newCell);
+ }
+ }
+ }
+ }
+ }
+ // Detect when entire cells are selected:
+ // Get number of cells selected
+ var tagNameObj = { value: "" };
+ var countObj = { value: 0 };
+ var element = gActiveEditor.getSelectedOrParentTableElement(
+ tagNameObj,
+ countObj
+ );
+ var deletePlaceholder = false;
+
+ if (tagNameObj.value == "table") {
+ // Replace entire selected table with new table, so delete the table
+ gActiveEditor.deleteTable();
+ } else if (tagNameObj.value == "td") {
+ if (countObj.value >= 1) {
+ if (countObj.value > 1) {
+ // Assume user wants to replace a block of
+ // contiguous cells with a table, so
+ // join the selected cells
+ gActiveEditor.joinTableCells(false);
+
+ // Get the cell everything was merged into
+ element = gActiveEditor.getFirstSelectedCell();
+
+ // Collapse selection into just that cell
+ gActiveEditor.selection.collapse(element, 0);
+ }
+
+ if (element) {
+ // Empty just the contents of the cell
+ gActiveEditor.deleteTableCellContents();
+
+ // Collapse selection to start of empty cell...
+ gActiveEditor.selection.collapse(element, 0);
+ // ...but it will contain a <br> placeholder
+ deletePlaceholder = true;
+ }
+ }
+ }
+
+ // true means delete selection when inserting
+ gActiveEditor.insertElementAtSelection(gTableElement, true);
+
+ if (deletePlaceholder && gTableElement && gTableElement.nextSibling) {
+ // Delete the placeholder <br>
+ gActiveEditor.deleteNode(gTableElement.nextSibling);
+ }
+ } catch (e) {}
+
+ gActiveEditor.endTransaction();
+
+ SaveWindowLocation();
+ return;
+ }
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdInsertTable.xhtml b/comm/suite/editor/components/dialogs/content/EdInsertTable.xhtml
new file mode 100644
index 0000000000..b376d5f0be
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsertTable.xhtml
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edInsertTable SYSTEM "chrome://editor/locale/EditorInsertTable.dtd">
+%edInsertTable;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload = "Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdInsertTable.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&size.label;</label>
+ </hbox>
+ <grid>
+ <columns>
+ <column flex="1"/>
+ <column flex="1"/>
+ <column flex="6"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label control="rowsInput" class="align-right"
+ value="&numRowsEditField.label;"
+ accesskey="&numRowsEditField.accessKey;"/>
+ <textbox class="narrow" id="rowsInput" oninput="ChangeRowOrColumn(this.id)" />
+ <spacer/>
+ </row>
+ <row align="center">
+ <label control="columnsInput" class="align-right"
+ value="&numColumnsEditField.label;"
+ accesskey="&numColumnsEditField.accessKey;"/>
+ <textbox class="narrow" id="columnsInput" oninput="ChangeRowOrColumn(this.id)" />
+ <spacer/>
+ </row>
+ <row align="center">
+ <label control="widthInput" class="align-right"
+ value="&widthEditField.label;"
+ accesskey="&widthEditField.accessKey;"/>
+ <textbox class="narrow" id="widthInput" oninput="forceInteger(this.id)" />
+ <menulist id="widthPixelOrPercentMenulist" flex="1"/>
+ <!-- child elements are appended by JS -->
+ </row>
+ </rows>
+ </grid>
+ <spacer class="spacer"/>
+ </groupbox>
+ <spacer class="spacer"/>
+ <hbox align="center">
+ <label control="borderInput" class="align-right"
+ value="&borderEditField.label;"
+ accesskey="&borderEditField.accessKey;"
+ tooltiptext="&borderEditField.tooltip;" />
+ <textbox class="narrow" id="borderInput" oninput="forceInteger(this.id)" />
+ <label value="&pixels.label;"/>
+ </hbox>
+ <vbox id="AdvancedEdit">
+ <hbox flex="1" style="margin-top: 0.2em" align="center">
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator id="advancedSeparator" class="groove"/>
+ </vbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdLabelProps.js b/comm/suite/editor/components/dialogs/content/EdLabelProps.js
new file mode 100644
index 0000000000..ec96878ea6
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdLabelProps.js
@@ -0,0 +1,118 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var labelElement;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ dump("Failed to get active editor!\n");
+ window.close();
+ return;
+ }
+
+ gDialog.editText = document.getElementById("EditText");
+ gDialog.labelText = document.getElementById("LabelText");
+ gDialog.labelFor = document.getElementById("LabelFor");
+ gDialog.labelAccessKey = document.getElementById("LabelAccessKey");
+
+ labelElement = window.arguments[0];
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = labelElement.cloneNode(false);
+
+ InitDialog();
+
+ var range = editor.document.createRange();
+ range.selectNode(labelElement);
+ gDialog.labelText.value = range.toString();
+
+ if (labelElement.innerHTML.includes("<")) {
+ gDialog.editText.checked = false;
+ gDialog.editText.disabled = false;
+ gDialog.labelText.disabled = true;
+ gDialog.editText.addEventListener(
+ "command",
+ () =>
+ Services.prompt.alert(
+ window,
+ GetString("Alert"),
+ GetString("EditTextWarning")
+ ),
+ { capture: false, once: true }
+ );
+ SetTextboxFocus(gDialog.labelFor);
+ } else {
+ SetTextboxFocus(gDialog.labelText);
+ }
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ gDialog.labelFor.value = globalElement.getAttribute("for");
+ gDialog.labelAccessKey.value = globalElement.getAttribute("accesskey");
+}
+
+function RemoveLabel() {
+ RemoveContainer(labelElement);
+ SaveWindowLocation();
+ window.close();
+}
+
+function ValidateData() {
+ if (gDialog.labelFor.value) {
+ globalElement.setAttribute("for", gDialog.labelFor.value);
+ } else {
+ globalElement.removeAttribute("for");
+ }
+ if (gDialog.labelAccessKey.value) {
+ globalElement.setAttribute("accesskey", gDialog.labelAccessKey.value);
+ } else {
+ globalElement.removeAttribute("accesskey");
+ }
+ return true;
+}
+
+function onAccept() {
+ // All values are valid - copy to actual element in doc
+ ValidateData();
+
+ var editor = GetCurrentEditor();
+
+ editor.beginTransaction();
+
+ try {
+ if (gDialog.editText.checked) {
+ editor.setShouldTxnSetSelection(false);
+
+ while (labelElement.firstChild) {
+ editor.deleteNode(labelElement.firstChild);
+ }
+ if (gDialog.labelText.value) {
+ editor.insertNode(
+ editor.document.createTextNode(gDialog.labelText.value),
+ labelElement,
+ 0
+ );
+ }
+
+ editor.setShouldTxnSetSelection(true);
+ }
+
+ editor.cloneAttributes(labelElement, globalElement);
+ } catch (e) {}
+
+ editor.endTransaction();
+
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdLabelProps.xhtml b/comm/suite/editor/components/dialogs/content/EdLabelProps.xhtml
new file mode 100644
index 0000000000..21d964f1fd
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdLabelProps.xhtml
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edLabelProperties SYSTEM "chrome://editor/locale/EditorLabelProperties.dtd">
+%edLabelProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ buttons="accept,cancel">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdLabelProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header" accesskey="&Settings.accesskey;">&Settings.label;</label>
+ </hbox>
+ <grid><columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <checkbox id="EditText" label="&EditLabelText.label;" accesskey="&EditLabelText.accesskey;" checked="true" disabled="true"
+ oncommand="gDialog.labelText.disabled = !gDialog.editText.checked;"/>
+ <textbox id="LabelText" accesskey="&Settings.accesskey;"/>
+ </row>
+ <row align="center">
+ <label control="LabelFor" value="&LabelFor.label;" accesskey="&LabelFor.accesskey;"/>
+ <textbox id="LabelFor"/>
+ </row>
+ <row align="center">
+ <label control="LabelAccessKey" value="&AccessKey.label;" accesskey="&AccessKey.accesskey;"/>
+ <hbox>
+ <textbox id="LabelAccessKey" class="narrow"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <!-- from EdDialogOverlay -->
+ <hbox flex="1" style="margin-top: 0.2em">
+ <button id="RemoveLabel" label="&RemoveLabel.label;" accesskey="&RemoveLabel.accesskey;" oncommand="RemoveLabel();"/>
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator class="groove"/>
+
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdLinkProps.js b/comm/suite/editor/components/dialogs/content/EdLinkProps.js
new file mode 100644
index 0000000000..6504496a51
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdLinkProps.js
@@ -0,0 +1,331 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var gActiveEditor;
+var anchorElement = null;
+var imageElement = null;
+var insertNew = false;
+var replaceExistingLink = false;
+var insertLinkAtCaret;
+var needLinkText = false;
+var href;
+var newLinkText;
+var gHNodeArray = {};
+var gHaveNamedAnchors = false;
+var gHaveHeadings = false;
+var gCanChangeHeadingSelected = true;
+var gCanChangeAnchorSelected = true;
+
+// NOTE: Use "href" instead of "a" to distinguish from Named Anchor
+// The returned node is has an "a" tagName
+var tagName = "href";
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ gActiveEditor = GetCurrentEditor();
+ if (!gActiveEditor) {
+ dump("Failed to get active editor!\n");
+ window.close();
+ return;
+ }
+ // Message was wrapped in a <label> or <div>, so actual text is a child text node
+ gDialog.linkTextCaption = document.getElementById("linkTextCaption");
+ gDialog.linkTextMessage = document.getElementById("linkTextMessage");
+ gDialog.linkTextInput = document.getElementById("linkTextInput");
+ gDialog.hrefInput = document.getElementById("hrefInput");
+ gDialog.makeRelativeLink = document.getElementById("MakeRelativeLink");
+ gDialog.AdvancedEditSection = document.getElementById("AdvancedEdit");
+
+ // See if we have a single selected image
+ imageElement = gActiveEditor.getSelectedElement("img");
+
+ if (imageElement) {
+ // Get the parent link if it exists -- more efficient than GetSelectedElement()
+ anchorElement = gActiveEditor.getElementOrParentByTagName(
+ "href",
+ imageElement
+ );
+ if (anchorElement) {
+ if (anchorElement.childNodes.length > 1) {
+ // If there are other children, then we want to break
+ // this image away by inserting a new link around it,
+ // so make a new node and copy existing attributes
+ anchorElement = anchorElement.cloneNode(false);
+ // insertNew = true;
+ replaceExistingLink = true;
+ }
+ }
+ } else {
+ // Get an anchor element if caret or
+ // entire selection is within the link.
+ anchorElement = gActiveEditor.getSelectedElement(tagName);
+
+ if (anchorElement) {
+ // Select the entire link
+ gActiveEditor.selectElement(anchorElement);
+ } else {
+ // If selection starts in a link, but extends beyond it,
+ // the user probably wants to extend existing link to new selection,
+ // so check if either end of selection is within a link
+ // POTENTIAL PROBLEM: This prevents user from selecting text in an existing
+ // link and making 2 links.
+ // Note that this isn't a problem with images, handled above
+
+ anchorElement = gActiveEditor.getElementOrParentByTagName(
+ "href",
+ gActiveEditor.selection.anchorNode
+ );
+ if (!anchorElement) {
+ anchorElement = gActiveEditor.getElementOrParentByTagName(
+ "href",
+ gActiveEditor.selection.focusNode
+ );
+ }
+
+ if (anchorElement) {
+ // But clone it for reinserting/merging around existing
+ // link that only partially overlaps the selection
+ anchorElement = anchorElement.cloneNode(false);
+ // insertNew = true;
+ replaceExistingLink = true;
+ }
+ }
+ }
+
+ if (!anchorElement) {
+ // No existing link -- create a new one
+ anchorElement = gActiveEditor.createElementWithDefaults(tagName);
+ insertNew = true;
+ // Hide message about removing existing link
+ // document.getElementById("RemoveLinkMsg").hidden = true;
+ }
+ if (!anchorElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+
+ // We insert at caret only when nothing is selected
+ insertLinkAtCaret = gActiveEditor.selection.isCollapsed;
+
+ var selectedText;
+ if (insertLinkAtCaret) {
+ // Groupbox caption:
+ gDialog.linkTextCaption.setAttribute("label", GetString("LinkText"));
+
+ // Message above input field:
+ gDialog.linkTextMessage.setAttribute("value", GetString("EnterLinkText"));
+ gDialog.linkTextMessage.setAttribute(
+ "accesskey",
+ GetString("EnterLinkTextAccessKey")
+ );
+ } else {
+ if (!imageElement) {
+ // We get here if selection is exactly around a link node
+ // Check if selection has some text - use that first
+ selectedText = GetSelectionAsText();
+ if (!selectedText) {
+ // No text, look for first image in the selection
+ var children = anchorElement.childNodes;
+ if (children) {
+ for (var i = 0; i < children.length; i++) {
+ var nodeName = children.item(i).nodeName.toLowerCase();
+ if (nodeName == "img") {
+ imageElement = children.item(i);
+ break;
+ }
+ }
+ }
+ }
+ }
+ // Set "caption" for link source and the source text or image URL
+ if (imageElement) {
+ gDialog.linkTextCaption.setAttribute("label", GetString("LinkImage"));
+ // Link source string is the source URL of image
+ // TODO: THIS DOESN'T HANDLE MULTIPLE SELECTED IMAGES!
+ gDialog.linkTextMessage.setAttribute("value", imageElement.src);
+ } else {
+ gDialog.linkTextCaption.setAttribute("label", GetString("LinkText"));
+ if (selectedText) {
+ // Use just the first 60 characters and add "..."
+ gDialog.linkTextMessage.setAttribute(
+ "value",
+ TruncateStringAtWordEnd(
+ ReplaceWhitespace(selectedText, " "),
+ 60,
+ true
+ )
+ );
+ } else {
+ gDialog.linkTextMessage.setAttribute(
+ "value",
+ GetString("MixedSelection")
+ );
+ }
+ }
+ }
+
+ // Make a copy to use for AdvancedEdit and onSaveDefault
+ globalElement = anchorElement.cloneNode(false);
+
+ // Get the list of existing named anchors and headings
+ FillLinkMenulist(gDialog.hrefInput, gHNodeArray);
+
+ // We only need to test for this once per dialog load
+ gHaveDocumentUrl = GetDocumentBaseUrl();
+
+ // Set data for the dialog controls
+ InitDialog();
+
+ // Search for a URI pattern in the selected text
+ // as candidate href
+ selectedText = TrimString(selectedText);
+ if (!gDialog.hrefInput.value && TextIsURI(selectedText)) {
+ gDialog.hrefInput.value = selectedText;
+ }
+
+ // Set initial focus
+ if (insertLinkAtCaret) {
+ // We will be using the HREF inputbox, so text message
+ SetTextboxFocus(gDialog.linkTextInput);
+ } else {
+ SetTextboxFocus(gDialog.hrefInput);
+
+ // We will not insert a new link at caret, so remove link text input field
+ gDialog.linkTextInput.hidden = true;
+ gDialog.linkTextInput = null;
+ }
+
+ // This sets enable state on OK button
+ doEnabling();
+
+ SetWindowLocation();
+}
+
+// Set dialog widgets with attribute data
+// We get them from globalElement copy so this can be used
+// by AdvancedEdit(), which is shared by all property dialogs
+function InitDialog() {
+ // Must use getAttribute, not "globalElement.href",
+ // or foreign chars aren't converted correctly!
+ gDialog.hrefInput.value = globalElement.getAttribute("href");
+
+ // Set "Relativize" checkbox according to current URL state
+ SetRelativeCheckbox(gDialog.makeRelativeLink);
+}
+
+function doEnabling() {
+ // We disable Ok button when there's no href text only if inserting a new link
+ var enable = insertNew
+ ? TrimString(gDialog.hrefInput.value).length > 0
+ : true;
+
+ // anon. content, so can't use SetElementEnabledById here
+ var dialogNode = document.getElementById("linkDlg");
+ dialogNode.getButton("accept").disabled = !enable;
+
+ SetElementEnabledById("AdvancedEditButton1", enable);
+}
+
+function ChangeLinkLocation() {
+ SetRelativeCheckbox(gDialog.makeRelativeLink);
+ // Set OK button enable state
+ doEnabling();
+}
+
+// Get and validate data from widgets.
+// Set attributes on globalElement so they can be accessed by AdvancedEdit()
+function ValidateData() {
+ href = TrimString(gDialog.hrefInput.value);
+ if (href) {
+ // Set the HREF directly on the editor document's anchor node
+ // or on the newly-created node if insertNew is true
+ globalElement.setAttribute("href", href);
+ } else if (insertNew) {
+ // We must have a URL to insert a new link
+ // NOTE: We accept an empty HREF on existing link to indicate removing the link
+ ShowInputErrorMessage(GetString("EmptyHREFError"));
+ return false;
+ }
+ if (gDialog.linkTextInput) {
+ // The text we will insert isn't really an attribute,
+ // but it makes sense to validate it
+ newLinkText = TrimString(gDialog.linkTextInput.value);
+ if (!newLinkText) {
+ if (href) {
+ newLinkText = href;
+ } else {
+ ShowInputErrorMessage(GetString("EmptyLinkTextError"));
+ SetTextboxFocus(gDialog.linkTextInput);
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+function onAccept(event) {
+ if (ValidateData()) {
+ if (href.length > 0) {
+ // Copy attributes to element we are changing or inserting
+ gActiveEditor.cloneAttributes(anchorElement, globalElement);
+
+ // Coalesce into one undo transaction
+ gActiveEditor.beginTransaction();
+
+ // Get text to use for a new link
+ if (insertLinkAtCaret) {
+ // Append the link text as the last child node
+ // of the anchor node
+ var textNode = gActiveEditor.document.createTextNode(newLinkText);
+ if (textNode) {
+ anchorElement.appendChild(textNode);
+ }
+ try {
+ gActiveEditor.insertElementAtSelection(anchorElement, false);
+ } catch (e) {
+ dump("Exception occurred in InsertElementAtSelection\n");
+ return;
+ }
+ } else if (insertNew || replaceExistingLink) {
+ // Link source was supplied by the selection,
+ // so insert a link node as parent of this
+ // (may be text, image, or other inline content)
+ try {
+ gActiveEditor.insertLinkAroundSelection(anchorElement);
+ } catch (e) {
+ dump("Exception occurred in InsertElementAtSelection\n");
+ return;
+ }
+ }
+ // Check if the link was to a heading
+ if (href in gHNodeArray) {
+ var anchorNode = gActiveEditor.createElementWithDefaults("a");
+ if (anchorNode) {
+ anchorNode.name = href.substr(1);
+
+ // Insert the anchor into the document,
+ // but don't let the transaction change the selection
+ gActiveEditor.setShouldTxnSetSelection(false);
+ gActiveEditor.insertNode(anchorNode, gHNodeArray[href], 0);
+ gActiveEditor.setShouldTxnSetSelection(true);
+ }
+ }
+ gActiveEditor.endTransaction();
+ } else if (!insertNew) {
+ // We already had a link, but empty HREF means remove it
+ EditorRemoveTextProperty("href", "");
+ }
+ SaveWindowLocation();
+ return;
+ }
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdLinkProps.xhtml b/comm/suite/editor/components/dialogs/content/EdLinkProps.xhtml
new file mode 100644
index 0000000000..04e147db4c
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdLinkProps.xhtml
@@ -0,0 +1,79 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % linkPropertiesDTD SYSTEM "chrome://editor/locale/EditorLinkProperties.dtd">
+%linkPropertiesDTD;
+<!ENTITY % composeEditorOverlayDTD SYSTEM "chrome://messenger/locale/messengercompose/mailComposeEditorOverlay.dtd">
+%composeEditorOverlayDTD;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog id="linkDlg" title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload = "Startup()">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdLinkProps.js"/>
+ <script src="chrome://editor/content/EdImageLinkLoader.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <vbox style="min-width: 20em">
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label id="linkTextCaption" class="header"/>
+ </hbox>
+ <vbox>
+ <label id="linkTextMessage" control="linkTextInput"/>
+ <textbox id="linkTextInput"/>
+ </vbox>
+ </groupbox>
+
+ <groupbox id="LinkURLBox">
+ <hbox class="groupbox-title">
+ <label class="header">&LinkURLBox.label;</label>
+ </hbox>
+ <vbox id="LinkLocationBox">
+ <label control="hrefInput"
+ accesskey="&LinkURLEditField2.accessKey;"
+ width="1">&LinkURLEditField2.label;</label>
+ <textbox id="hrefInput" type="text"
+ class="uri-element padded" oninput="ChangeLinkLocation();"/>
+ <hbox align="center">
+ <checkbox id="MakeRelativeLink"
+ for="hrefInput"
+ label="&makeUrlRelative.label;"
+ accesskey="&makeUrlRelative.accessKey;"
+ oncommand="MakeInputValueRelativeOrAbsolute(this);"
+ tooltiptext="&makeUrlRelative.tooltip;"/>
+ <spacer flex="1"/>
+ <button label="&chooseFileLinkButton.label;" accesskey="&chooseFileLinkButton.accessKey;"
+ oncommand="chooseLinkFile();"/>
+ </hbox>
+ </vbox>
+ <checkbox id="AttachSourceToMail"
+ hidden="true"
+ label="&attachLinkSource.label;"
+ accesskey="&attachLinkSource.accesskey;"
+ oncommand="DoAttachSourceCheckbox()"/>
+ </groupbox>
+ </vbox>
+ <vbox id="AdvancedEdit">
+ <hbox flex="1" style="margin-top: 0.2em" align="center">
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator id="advancedSeparator" class="groove"/>
+ </vbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdListProps.js b/comm/suite/editor/components/dialogs/content/EdListProps.js
new file mode 100644
index 0000000000..8d4b78536b
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdListProps.js
@@ -0,0 +1,455 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// Cancel() is in EdDialogCommon.js
+var gBulletStyleType = "";
+var gNumberStyleType = "";
+var gListElement;
+var gOriginalListType = "";
+var gListType = "";
+var gMixedListSelection = false;
+var gStyleType = "";
+var gOriginalStyleType = "";
+const gOnesArray = ["", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"];
+const gTensArray = ["", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"];
+const gHundredsArray = [
+ "",
+ "C",
+ "CC",
+ "CCC",
+ "CD",
+ "D",
+ "DC",
+ "DCC",
+ "DCCC",
+ "CM",
+];
+const gThousandsArray = [
+ "",
+ "M",
+ "MM",
+ "MMM",
+ "MMMM",
+ "MMMMM",
+ "MMMMMM",
+ "MMMMMMM",
+ "MMMMMMMM",
+ "MMMMMMMMM",
+];
+const gRomanDigits = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 };
+const A = "A".charCodeAt(0);
+const gArabic = "1";
+const gUpperRoman = "I";
+const gLowerRoman = "i";
+const gUpperLetters = "A";
+const gLowerLetters = "a";
+const gDecimalCSS = "decimal";
+const gUpperRomanCSS = "upper-roman";
+const gLowerRomanCSS = "lower-roman";
+const gUpperAlphaCSS = "upper-alpha";
+const gLowerAlphaCSS = "lower-alpha";
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+ gDialog.ListTypeList = document.getElementById("ListType");
+ gDialog.BulletStyleList = document.getElementById("BulletStyle");
+ gDialog.BulletStyleLabel = document.getElementById("BulletStyleLabel");
+ gDialog.StartingNumberInput = document.getElementById("StartingNumber");
+ gDialog.StartingNumberLabel = document.getElementById("StartingNumberLabel");
+ gDialog.AdvancedEditButton = document.getElementById("AdvancedEditButton1");
+ gDialog.RadioGroup = document.getElementById("RadioGroup");
+ gDialog.ChangeAllRadio = document.getElementById("ChangeAll");
+ gDialog.ChangeSelectedRadio = document.getElementById("ChangeSelected");
+
+ // Try to get an existing list(s)
+ var mixedObj = { value: null };
+ try {
+ gListType = editor.getListState(mixedObj, {}, {}, {});
+
+ // We may have mixed list and non-list, or > 1 list type in selection
+ gMixedListSelection = mixedObj.value;
+
+ // Get the list element at the anchor node
+ gListElement = editor.getElementOrParentByTagName("list", null);
+ } catch (e) {}
+
+ // The copy to use in AdvancedEdit
+ if (gListElement) {
+ globalElement = gListElement.cloneNode(false);
+ }
+
+ // Show extra options for changing entire list if we have one already.
+ gDialog.RadioGroup.collapsed = !gListElement;
+ if (gListElement) {
+ // Radio button index is persistent
+ if (gDialog.RadioGroup.getAttribute("index") == "1") {
+ gDialog.RadioGroup.selectedItem = gDialog.ChangeSelectedRadio;
+ } else {
+ gDialog.RadioGroup.selectedItem = gDialog.ChangeAllRadio;
+ }
+ }
+
+ InitDialog();
+
+ gOriginalListType = gListType;
+
+ gDialog.ListTypeList.focus();
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ // Note that if mixed, we we pay attention
+ // only to the anchor node's list type
+ // (i.e., don't confuse user with "mixed" designation)
+ if (gListElement) {
+ gListType = gListElement.nodeName.toLowerCase();
+ } else {
+ gListType = "";
+ }
+
+ gDialog.ListTypeList.value = gListType;
+ gDialog.StartingNumberInput.value = "";
+
+ // Last param = true means attribute value is case-sensitive
+ var type = globalElement
+ ? GetHTMLOrCSSStyleValue(globalElement, "type", "list-style-type")
+ : null;
+
+ if (gListType == "ul") {
+ if (type) {
+ type = type.toLowerCase();
+ gBulletStyleType = type;
+ gOriginalStyleType = type;
+ }
+ } else if (gListType == "ol") {
+ // Translate CSS property strings
+ switch (type.toLowerCase()) {
+ case gDecimalCSS:
+ type = gArabic;
+ break;
+ case gUpperRomanCSS:
+ type = gUpperRoman;
+ break;
+ case gLowerRomanCSS:
+ type = gLowerRoman;
+ break;
+ case gUpperAlphaCSS:
+ type = gUpperLetters;
+ break;
+ case gLowerAlphaCSS:
+ type = gLowerLetters;
+ break;
+ }
+ if (type) {
+ gNumberStyleType = type;
+ gOriginalStyleType = type;
+ }
+
+ // Convert attribute number to appropriate letter or roman numeral
+ gDialog.StartingNumberInput.value = ConvertStartAttrToUserString(
+ globalElement.getAttribute("start"),
+ type
+ );
+ }
+ BuildBulletStyleList();
+}
+
+// Convert attribute number to appropriate letter or roman numeral
+function ConvertStartAttrToUserString(startAttr, type) {
+ switch (type) {
+ case gUpperRoman:
+ startAttr = ConvertArabicToRoman(startAttr);
+ break;
+ case gLowerRoman:
+ startAttr = ConvertArabicToRoman(startAttr).toLowerCase();
+ break;
+ case gUpperLetters:
+ startAttr = ConvertArabicToLetters(startAttr);
+ break;
+ case gLowerLetters:
+ startAttr = ConvertArabicToLetters(startAttr).toLowerCase();
+ break;
+ }
+ return startAttr;
+}
+
+function BuildBulletStyleList() {
+ gDialog.BulletStyleList.removeAllItems();
+ var label;
+
+ if (gListType == "ul") {
+ gDialog.BulletStyleList.removeAttribute("disabled");
+ gDialog.BulletStyleLabel.removeAttribute("disabled");
+ gDialog.StartingNumberInput.setAttribute("disabled", "true");
+ gDialog.StartingNumberLabel.setAttribute("disabled", "true");
+
+ label = GetString("BulletStyle");
+
+ gDialog.BulletStyleList.appendItem(GetString("Automatic"), "");
+ gDialog.BulletStyleList.appendItem(GetString("SolidCircle"), "disc");
+ gDialog.BulletStyleList.appendItem(GetString("OpenCircle"), "circle");
+ gDialog.BulletStyleList.appendItem(GetString("SolidSquare"), "square");
+
+ gDialog.BulletStyleList.value = gBulletStyleType;
+ } else if (gListType == "ol") {
+ gDialog.BulletStyleList.removeAttribute("disabled");
+ gDialog.BulletStyleLabel.removeAttribute("disabled");
+ gDialog.StartingNumberInput.removeAttribute("disabled");
+ gDialog.StartingNumberLabel.removeAttribute("disabled");
+ label = GetString("NumberStyle");
+
+ gDialog.BulletStyleList.appendItem(GetString("Automatic"), "");
+ gDialog.BulletStyleList.appendItem(GetString("Style_1"), gArabic);
+ gDialog.BulletStyleList.appendItem(GetString("Style_I"), gUpperRoman);
+ gDialog.BulletStyleList.appendItem(GetString("Style_i"), gLowerRoman);
+ gDialog.BulletStyleList.appendItem(GetString("Style_A"), gUpperLetters);
+ gDialog.BulletStyleList.appendItem(GetString("Style_a"), gLowerLetters);
+
+ gDialog.BulletStyleList.value = gNumberStyleType;
+ } else {
+ gDialog.BulletStyleList.setAttribute("disabled", "true");
+ gDialog.BulletStyleLabel.setAttribute("disabled", "true");
+ gDialog.StartingNumberInput.setAttribute("disabled", "true");
+ gDialog.StartingNumberLabel.setAttribute("disabled", "true");
+ }
+
+ // Disable advanced edit button if changing to "normal"
+ if (gListType) {
+ gDialog.AdvancedEditButton.removeAttribute("disabled");
+ } else {
+ gDialog.AdvancedEditButton.setAttribute("disabled", "true");
+ }
+
+ if (label) {
+ gDialog.BulletStyleLabel.setAttribute("label", label);
+ }
+}
+
+function SelectListType() {
+ // Each list type is stored in the "value" of each menuitem
+ var NewType = gDialog.ListTypeList.value;
+
+ if (NewType == "ol") {
+ SetTextboxFocus(gDialog.StartingNumberInput);
+ }
+
+ if (gListType != NewType) {
+ gListType = NewType;
+
+ // Create a newlist object for Advanced Editing
+ try {
+ if (gListType) {
+ globalElement = GetCurrentEditor().createElementWithDefaults(gListType);
+ }
+ } catch (e) {}
+
+ BuildBulletStyleList();
+ }
+}
+
+function SelectBulletStyle() {
+ // Save the selected index so when user changes
+ // list style, restore index to associated list
+ // Each bullet or number type is stored in the "value" of each menuitem
+ if (gListType == "ul") {
+ gBulletStyleType = gDialog.BulletStyleList.value;
+ } else if (gListType == "ol") {
+ var type = gDialog.BulletStyleList.value;
+ if (gNumberStyleType != type) {
+ // Convert existing input value to attr number first,
+ // then convert to the appropriate format for the newly-selected
+ gDialog.StartingNumberInput.value = ConvertStartAttrToUserString(
+ ConvertUserStringToStartAttr(gNumberStyleType),
+ type
+ );
+
+ gNumberStyleType = type;
+ SetTextboxFocus(gDialog.StartingNumberInput);
+ }
+ }
+}
+
+function ValidateData() {
+ gBulletStyleType = gDialog.BulletStyleList.value;
+ // globalElement should already be of the correct type
+
+ if (globalElement) {
+ var editor = GetCurrentEditor();
+ if (gListType == "ul") {
+ if (gBulletStyleType && gDialog.ChangeAllRadio.selected) {
+ globalElement.setAttribute("type", gBulletStyleType);
+ } else {
+ try {
+ editor.removeAttributeOrEquivalent(globalElement, "type", true);
+ } catch (e) {}
+ }
+ } else if (gListType == "ol") {
+ if (gBulletStyleType) {
+ globalElement.setAttribute("type", gBulletStyleType);
+ } else {
+ try {
+ editor.removeAttributeOrEquivalent(globalElement, "type", true);
+ } catch (e) {}
+ }
+
+ var startingNumber = ConvertUserStringToStartAttr(gBulletStyleType);
+ if (startingNumber) {
+ globalElement.setAttribute("start", startingNumber);
+ } else {
+ globalElement.removeAttribute("start");
+ }
+ }
+ }
+ return true;
+}
+
+function ConvertUserStringToStartAttr(type) {
+ var startingNumber = TrimString(gDialog.StartingNumberInput.value);
+
+ switch (type) {
+ case gUpperRoman:
+ case gLowerRoman:
+ // If the input isn't an integer, assume it's a roman numeral. Convert it.
+ if (!Number(startingNumber)) {
+ startingNumber = ConvertRomanToArabic(startingNumber);
+ }
+ break;
+ case gUpperLetters:
+ case gLowerLetters:
+ // Get the number equivalent of the letters
+ if (!Number(startingNumber)) {
+ startingNumber = ConvertLettersToArabic(startingNumber);
+ }
+ break;
+ }
+ return startingNumber;
+}
+
+function ConvertRomanToArabic(num) {
+ num = num.toUpperCase();
+ if (num && !/[^MDCLXVI]/i.test(num)) {
+ var Arabic = 0;
+ var last_digit = 1000;
+ for (var i = 0; i < num.length; i++) {
+ var digit = gRomanDigits[num.charAt(i)];
+ if (last_digit < digit) {
+ Arabic -= 2 * last_digit;
+ }
+
+ last_digit = digit;
+ Arabic += last_digit;
+ }
+ return Arabic;
+ }
+
+ return "";
+}
+
+function ConvertArabicToRoman(num) {
+ if (/^\d{1,4}$/.test(num)) {
+ var digits = ("000" + num).substr(-4);
+ return (
+ gThousandsArray[digits.charAt(0)] +
+ gHundredsArray[digits.charAt(1)] +
+ gTensArray[digits.charAt(2)] +
+ gOnesArray[digits.charAt(3)]
+ );
+ }
+ return "";
+}
+
+function ConvertLettersToArabic(letters) {
+ letters = letters.toUpperCase();
+ if (!letters || /[^A-Z]/.test(letters)) {
+ return "";
+ }
+
+ var num = 0;
+ for (var i = 0; i < letters.length; i++) {
+ num = num * 26 + letters.charCodeAt(i) - A + 1;
+ }
+ return num;
+}
+
+function ConvertArabicToLetters(num) {
+ var letters = "";
+ while (num) {
+ num--;
+ letters = String.fromCharCode(A + (num % 26)) + letters;
+ num = Math.floor(num / 26);
+ }
+ return letters;
+}
+
+function onAccept(event) {
+ if (ValidateData()) {
+ // Coalesce into one undo transaction
+ var editor = GetCurrentEditor();
+
+ editor.beginTransaction();
+
+ var changeEntireList =
+ gDialog.RadioGroup.selectedItem == gDialog.ChangeAllRadio;
+
+ // Remember which radio button was selected
+ if (gListElement) {
+ gDialog.RadioGroup.setAttribute("index", changeEntireList ? "0" : "1");
+ }
+
+ var changeList;
+ if (gListElement && gDialog.ChangeAllRadio.selected) {
+ changeList = true;
+ } else {
+ changeList =
+ gMixedListSelection ||
+ gListType != gOriginalListType ||
+ gBulletStyleType != gOriginalStyleType;
+ }
+ if (changeList) {
+ try {
+ if (gListType) {
+ editor.makeOrChangeList(
+ gListType,
+ changeEntireList,
+ gBulletStyleType != gOriginalStyleType ? gBulletStyleType : null
+ );
+
+ // Get the new list created:
+ gListElement = editor.getElementOrParentByTagName(gListType, null);
+
+ editor.cloneAttributes(gListElement, globalElement);
+ } else {
+ // Remove all existing lists
+ if (gListElement && changeEntireList) {
+ editor.selectElement(gListElement);
+ }
+
+ editor.removeList("ol");
+ editor.removeList("ul");
+ editor.removeList("dl");
+ }
+ } catch (e) {}
+ }
+
+ editor.endTransaction();
+
+ SaveWindowLocation();
+
+ return;
+ }
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdListProps.xhtml b/comm/suite/editor/components/dialogs/content/EdListProps.xhtml
new file mode 100644
index 0000000000..cae9829c97
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdListProps.xhtml
@@ -0,0 +1,73 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edListProperties SYSTEM "chrome://editor/locale/EditorListProperties.dtd">
+%edListProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdListProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox flex="1">
+ <hbox class="groupbox-title">
+ <label class="header">&ListType.label;</label>
+ </hbox>
+ <menulist id="ListType" oncommand="SelectListType()">
+ <menupopup>
+ <menuitem label="&none.value;"/>
+ <menuitem value="ul" label="&bulletList.value;"/>
+ <menuitem value="ol" label="&numberList.value;"/>
+ <menuitem value="dl" label="&definitionList.value;"/>
+ </menupopup>
+ </menulist>
+ </groupbox>
+ <spacer class="spacer"/>
+
+ <!-- message text and list items are set in JS
+ text value should be identical to string with id=BulletStyle in editor.properties
+ -->
+ <groupbox flex="1">
+ <hbox class="groupbox-title">
+ <label id="BulletStyleLabel" class="header">&bulletStyle.label;</label>
+ </hbox>
+ <menulist class="MinWidth10em" id="BulletStyle" oncommand="SelectBulletStyle()">
+ <menupopup/>
+ </menulist>
+ <spacer class="spacer"/>
+ <hbox>
+ <label id="StartingNumberLabel" control="StartingNumber"
+ value="&startingNumber.label;" accesskey="&startingNumber.accessKey;"/>
+ <textbox class="narrow" id="StartingNumber"/>
+ <spacer/>
+ </hbox>
+ </groupbox>
+ <radiogroup id="RadioGroup" index="0" persist="index">
+ <radio id="ChangeAll" label="&changeEntireListRadio.label;" accesskey="&changeEntireListRadio.accessKey;"/>
+ <radio id="ChangeSelected" label="&changeSelectedRadio.label;" accesskey="&changeSelectedRadio.accessKey;"/>
+ </radiogroup>
+ <vbox id="AdvancedEdit">
+ <hbox flex="1" style="margin-top: 0.2em" align="center">
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator id="advancedSeparator" class="groove"/>
+ </vbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.js b/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.js
new file mode 100644
index 0000000000..0433e58872
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.js
@@ -0,0 +1,159 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var gInsertNew = true;
+var gAnchorElement = null;
+var gOriginalName = "";
+const kTagName = "anchor";
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ gDialog.OkButton = document.documentElement.getButton("accept");
+ gDialog.NameInput = document.getElementById("nameInput");
+
+ // Get a single selected element of the desired type
+ gAnchorElement = editor.getSelectedElement(kTagName);
+
+ if (gAnchorElement) {
+ // We found an element and don't need to insert one
+ gInsertNew = false;
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = gAnchorElement.cloneNode(false);
+ gOriginalName = ConvertToCDATAString(gAnchorElement.name);
+ } else {
+ gInsertNew = true;
+ // We don't have an element selected,
+ // so create one with default attributes
+ gAnchorElement = editor.createElementWithDefaults(kTagName);
+ if (gAnchorElement) {
+ // Use the current selection as suggested name
+ var name = GetSelectionAsText();
+ // Get 40 characters of the selected text and don't add "...",
+ // replace whitespace with "_" and strip non-word characters
+ name = ConvertToCDATAString(TruncateStringAtWordEnd(name, 40, false));
+ // Be sure the name is unique to the document
+ if (AnchorNameExists(name)) {
+ name += "_";
+ }
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = gAnchorElement.cloneNode(false);
+ globalElement.setAttribute("name", name);
+ }
+ }
+ if (!gAnchorElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+
+ InitDialog();
+
+ DoEnabling();
+ SetTextboxFocus(gDialog.NameInput);
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ gDialog.NameInput.value = globalElement.getAttribute("name");
+}
+
+function ChangeName() {
+ if (gDialog.NameInput.value.length > 0) {
+ // Replace spaces with "_" and strip other non-URL characters
+ // Note: we could use ConvertAndEscape, but then we'd
+ // have to UnEscapeAndConvert beforehand - too messy!
+ gDialog.NameInput.value = ConvertToCDATAString(gDialog.NameInput.value);
+ }
+ DoEnabling();
+}
+
+function DoEnabling() {
+ var enable = gDialog.NameInput.value.length > 0;
+ SetElementEnabled(gDialog.OkButton, enable);
+ SetElementEnabledById("AdvancedEditButton1", enable);
+}
+
+function AnchorNameExists(name) {
+ var anchorList;
+ try {
+ anchorList = GetCurrentEditor().document.anchors;
+ } catch (e) {}
+
+ if (anchorList) {
+ for (var i = 0; i < anchorList.length; i++) {
+ if (anchorList[i].name == name) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// Get and validate data from widgets.
+// Set attributes on globalElement so they can be accessed by AdvancedEdit()
+function ValidateData() {
+ var name = TrimString(gDialog.NameInput.value);
+ if (!name) {
+ ShowInputErrorMessage(GetString("MissingAnchorNameError"));
+ SetTextboxFocus(gDialog.NameInput);
+ return false;
+ }
+ // Replace spaces with "_" and strip other characters
+ // Note: we could use ConvertAndEscape, but then we'd
+ // have to UnConverAndEscape beforehand - too messy!
+ name = ConvertToCDATAString(name);
+
+ if (gOriginalName != name && AnchorNameExists(name)) {
+ ShowInputErrorMessage(
+ GetString("DuplicateAnchorNameError").replace(/%name%/, name)
+ );
+ SetTextboxFocus(gDialog.NameInput);
+ return false;
+ }
+ globalElement.name = name;
+
+ return true;
+}
+
+function onAccept(event) {
+ if (ValidateData()) {
+ if (gOriginalName != globalElement.name) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+
+ try {
+ // "false" = don't delete selected text when inserting
+ if (gInsertNew) {
+ // We must insert element before copying CSS style attribute,
+ // but we must set the name else it won't insert at all
+ gAnchorElement.name = globalElement.name;
+ editor.insertElementAtSelection(gAnchorElement, false);
+ }
+
+ // Copy attributes to element we are changing or inserting
+ editor.cloneAttributes(gAnchorElement, globalElement);
+ } catch (e) {}
+
+ editor.endTransaction();
+ }
+ SaveWindowLocation();
+ return;
+ }
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.xhtml b/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.xhtml
new file mode 100644
index 0000000000..3a38256222
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.xhtml
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edNamedAnchorProperties SYSTEM "chrome://editor/locale/EdNamedAnchorProperties.dtd">
+%edNamedAnchorProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdNamedAnchorProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <label control="nameInput"
+ value="&anchorNameEditField.label;"
+ accesskey="&anchorNameEditField.accessKey;"/>
+ <textbox class="MinWidth20em" id="nameInput" oninput="ChangeName()"
+ tooltiptext="&nameInput.tooltip;"/>
+ <spacer class="spacer"/>
+ <vbox id="AdvancedEdit">
+ <hbox flex="1" style="margin-top: 0.2em" align="center">
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator id="advancedSeparator" class="groove"/>
+ </vbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdPageProps.js b/comm/suite/editor/components/dialogs/content/EdPageProps.js
new file mode 100644
index 0000000000..568eff66ec
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdPageProps.js
@@ -0,0 +1,159 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var gNewTitle = "";
+var gAuthor = "";
+var gDescription = "";
+var gAuthorElement;
+var gDescriptionElement;
+var gInsertNewAuthor = false;
+var gInsertNewDescription = false;
+var gTitleWasEdited = false;
+var gAuthorWasEdited = false;
+var gDescWasEdited = false;
+
+// Cancel() is in EdDialogCommon.js
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ gDialog.PageLocation = document.getElementById("PageLocation");
+ gDialog.PageModDate = document.getElementById("PageModDate");
+ gDialog.TitleInput = document.getElementById("TitleInput");
+ gDialog.AuthorInput = document.getElementById("AuthorInput");
+ gDialog.DescriptionInput = document.getElementById("DescriptionInput");
+
+ // Default string for new page is set from DTD string in XUL,
+ // so set only if not new doc URL
+ var location = GetDocumentUrl();
+ var lastmodString = GetString("Unknown");
+
+ if (!IsUrlAboutBlank(location)) {
+ // NEVER show username and password in clear text
+ gDialog.PageLocation.setAttribute("value", StripPassword(location));
+
+ // Get last-modified file date+time
+ // TODO: Convert this to local time?
+ var lastmod;
+ try {
+ lastmod = editor.document.lastModified; // get string of last modified date
+ } catch (e) {}
+ // Convert modified string to date (0 = unknown date or January 1, 1970 GMT)
+ if (Date.parse(lastmod)) {
+ try {
+ const dateTimeFormatter = new Services.intl.DateTimeFormat(undefined, {
+ dateStyle: "long",
+ timeStyle: "short",
+ });
+
+ var lastModDate = new Date();
+ lastModDate.setTime(Date.parse(lastmod));
+ lastmodString = dateTimeFormatter.format(lastModDate);
+ } catch (e) {}
+ }
+ }
+ gDialog.PageModDate.value = lastmodString;
+
+ gAuthorElement = GetMetaElementByAttribute("name", "author");
+ if (!gAuthorElement) {
+ gAuthorElement = CreateMetaElementWithAttribute("name", "author");
+ if (!gAuthorElement) {
+ window.close();
+ return;
+ }
+ gInsertNewAuthor = true;
+ }
+
+ gDescriptionElement = GetMetaElementByAttribute("name", "description");
+ if (!gDescriptionElement) {
+ gDescriptionElement = CreateMetaElementWithAttribute("name", "description");
+ if (!gDescriptionElement) {
+ window.close();
+ }
+
+ gInsertNewDescription = true;
+ }
+
+ InitDialog();
+
+ SetTextboxFocus(gDialog.TitleInput);
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ gDialog.TitleInput.value = GetDocumentTitle();
+
+ var gAuthor = TrimString(gAuthorElement.getAttribute("content"));
+ if (!gAuthor) {
+ // Fill in with value from editor prefs
+ gAuthor = Services.prefs.getCharPref("editor.author");
+ }
+ gDialog.AuthorInput.value = gAuthor;
+ gDialog.DescriptionInput.value = gDescriptionElement.getAttribute("content");
+}
+
+function TextboxChanged(ID) {
+ switch (ID) {
+ case "TitleInput":
+ gTitleWasEdited = true;
+ break;
+ case "AuthorInput":
+ gAuthorWasEdited = true;
+ break;
+ case "DescriptionInput":
+ gDescWasEdited = true;
+ break;
+ }
+}
+
+function ValidateData() {
+ gNewTitle = TrimString(gDialog.TitleInput.value);
+ gAuthor = TrimString(gDialog.AuthorInput.value);
+ gDescription = TrimString(gDialog.DescriptionInput.value);
+ return true;
+}
+
+function onAccept(event) {
+ if (ValidateData()) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+
+ // Set title contents even if string is empty
+ // because TITLE is a required HTML element
+ if (gTitleWasEdited) {
+ SetDocumentTitle(gNewTitle);
+ }
+
+ if (gAuthorWasEdited) {
+ SetMetaElementContent(gAuthorElement, gAuthor, gInsertNewAuthor, false);
+ }
+
+ if (gDescWasEdited) {
+ SetMetaElementContent(
+ gDescriptionElement,
+ gDescription,
+ gInsertNewDescription,
+ false
+ );
+ }
+
+ editor.endTransaction();
+
+ SaveWindowLocation();
+ return; // do close the window
+ }
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdPageProps.xhtml b/comm/suite/editor/components/dialogs/content/EdPageProps.xhtml
new file mode 100644
index 0000000000..4891baa04a
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdPageProps.xhtml
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorPageProperties.dtd">
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdPageProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+ <grid>
+ <columns><column flex="1"/><column flex="2"/></columns>
+ <rows>
+ <row>
+ <label value="&location.label;"/>
+ <label value="&locationNewPage.label;" id="PageLocation"/>
+ </row>
+ <row>
+ <label value="&lastModified.label;"/>
+ <label id="PageModDate"/>
+ </row>
+ <spacer class="spacer"/>
+ <row align="center">
+ <label value="&titleInput.label;" accesskey="&titleInput.accessKey;" control="TitleInput"/>
+ <textbox class="MinWidth20em" id="TitleInput" oninput="TextboxChanged(this.id)"/>
+ </row>
+ <row align="center">
+ <label value="&authorInput.label;" accesskey="&authorInput.accessKey;" control="AuthorInput"/>
+ <textbox class="MinWidth20em" id="AuthorInput" oninput="TextboxChanged(this.id)"/>
+ </row>
+ <row align="center">
+ <label value="&descriptionInput.label;" accesskey="&descriptionInput.accessKey;" control="DescriptionInput"/>
+ <textbox class="MinWidth20em" id="DescriptionInput" oninput="TextboxChanged(this.id)"/>
+ </row>
+ </rows>
+ </grid>
+ <spacer class="bigspacer"/>
+ <label value="&EditHEADSource1.label;"/>
+ <description class="wrap" flex="1">&EditHEADSource2.label;</description>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdReplace.js b/comm/suite/editor/components/dialogs/content/EdReplace.js
new file mode 100644
index 0000000000..c0daea29de
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdReplace.js
@@ -0,0 +1,382 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var gReplaceDialog; // Quick access to document/form elements.
+var gFindInst; // nsIWebBrowserFind that we're going to use
+var gFindService; // Global service which remembers find params
+var gEditor; // the editor we're using
+
+document.addEventListener("dialogaccept", event => {
+ onFindNext();
+ event.preventDefault();
+});
+
+function initDialogObject() {
+ // Create gReplaceDialog object and initialize.
+ gReplaceDialog = {};
+ gReplaceDialog.findInput = document.getElementById("dialog.findInput");
+ gReplaceDialog.replaceInput = document.getElementById("dialog.replaceInput");
+ gReplaceDialog.caseSensitive = document.getElementById(
+ "dialog.caseSensitive"
+ );
+ gReplaceDialog.wrap = document.getElementById("dialog.wrap");
+ gReplaceDialog.searchBackwards = document.getElementById(
+ "dialog.searchBackwards"
+ );
+ gReplaceDialog.findNext = document.getElementById("findNext");
+ gReplaceDialog.replace = document.getElementById("replace");
+ gReplaceDialog.replaceAndFind = document.getElementById("replaceAndFind");
+ gReplaceDialog.replaceAll = document.getElementById("replaceAll");
+}
+
+function loadDialog() {
+ // Set initial dialog field contents.
+ // Set initial dialog field contents. Use the gFindInst attributes first,
+ // this is necessary for window.find()
+ gReplaceDialog.findInput.value = gFindInst.searchString
+ ? gFindInst.searchString
+ : gFindService.searchString;
+ gReplaceDialog.replaceInput.value = gFindService.replaceString;
+ gReplaceDialog.caseSensitive.checked = gFindInst.matchCase
+ ? gFindInst.matchCase
+ : gFindService.matchCase;
+ gReplaceDialog.wrap.checked = gFindInst.wrapFind
+ ? gFindInst.wrapFind
+ : gFindService.wrapFind;
+ gReplaceDialog.searchBackwards.checked = gFindInst.findBackwards
+ ? gFindInst.findBackwards
+ : gFindService.findBackwards;
+
+ doEnabling();
+}
+
+function onLoad() {
+ // Get the xul <editor> element:
+ var editorElement = window.arguments[0];
+
+ // If we don't get the editor, then we won't allow replacing.
+ gEditor = editorElement.getEditor(editorElement.contentWindow);
+ if (!gEditor) {
+ window.close();
+ return;
+ }
+
+ // Get the nsIWebBrowserFind service:
+ gFindInst = editorElement.webBrowserFind;
+
+ try {
+ // get the find service, which stores global find state
+ gFindService = Cc["@mozilla.org/find/find_service;1"].getService(
+ Ci.nsIFindService
+ );
+ } catch (e) {
+ dump("No find service!\n");
+ gFindService = 0;
+ }
+
+ // Init gReplaceDialog.
+ initDialogObject();
+
+ // Change "OK" to "Find".
+ // dialog.find.label = document.getElementById("fBLT").getAttribute("label");
+
+ // Fill dialog.
+ loadDialog();
+
+ if (gReplaceDialog.findInput.value) {
+ gReplaceDialog.findInput.select();
+ } else {
+ gReplaceDialog.findInput.focus();
+ }
+}
+
+function saveFindData() {
+ // Set data attributes per user input.
+ if (gFindService) {
+ gFindService.searchString = gReplaceDialog.findInput.value;
+ gFindService.matchCase = gReplaceDialog.caseSensitive.checked;
+ gFindService.wrapFind = gReplaceDialog.wrap.checked;
+ gFindService.findBackwards = gReplaceDialog.searchBackwards.checked;
+ }
+}
+
+function setUpFindInst() {
+ gFindInst.searchString = gReplaceDialog.findInput.value;
+ gFindInst.matchCase = gReplaceDialog.caseSensitive.checked;
+ gFindInst.wrapFind = gReplaceDialog.wrap.checked;
+ gFindInst.findBackwards = gReplaceDialog.searchBackwards.checked;
+}
+
+function onFindNext() {
+ // Transfer dialog contents to the find service.
+ saveFindData();
+ // set up the find instance
+ setUpFindInst();
+
+ // Search.
+ var result = gFindInst.findNext();
+
+ if (!result) {
+ var bundle = document.getElementById("findBundle");
+ Services.prompt.alert(
+ window,
+ GetString("Alert"),
+ bundle.getString("notFoundWarning")
+ );
+ SetTextboxFocus(gReplaceDialog.findInput);
+ gReplaceDialog.findInput.select();
+ gReplaceDialog.findInput.focus();
+ return false;
+ }
+ return true;
+}
+
+function onReplace() {
+ if (!gEditor) {
+ return false;
+ }
+
+ // Does the current selection match the find string?
+ var selection = gEditor.selection;
+
+ var selStr = selection.toString();
+ var specStr = gReplaceDialog.findInput.value;
+ if (!gReplaceDialog.caseSensitive.checked) {
+ selStr = selStr.toLowerCase();
+ specStr = specStr.toLowerCase();
+ }
+ // Unfortunately, because of whitespace we can't just check
+ // whether (selStr == specStr), but have to loop ourselves.
+ // N chars of whitespace in specStr can match any M >= N in selStr.
+ var matches = true;
+ var specLen = specStr.length;
+ var selLen = selStr.length;
+ if (selLen < specLen) {
+ matches = false;
+ } else {
+ var specArray = specStr.match(/\S+|\s+/g);
+ var selArray = selStr.match(/\S+|\s+/g);
+ if (specArray.length != selArray.length) {
+ matches = false;
+ } else {
+ for (var i = 0; i < selArray.length; i++) {
+ if (selArray[i] != specArray[i]) {
+ if (/\S/.test(selArray[i][0]) || /\S/.test(specArray[i][0])) {
+ // not a space chunk -- match fails
+ matches = false;
+ break;
+ } else if (selArray[i].length < specArray[i].length) {
+ // if it's a space chunk then we only care that sel be
+ // at least as long as spec
+ matches = false;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // If the current selection doesn't match the pattern,
+ // then we want to find the next match, but not do the replace.
+ // That's what most other apps seem to do.
+ // So here, just return.
+ if (!matches) {
+ return false;
+ }
+
+ // Transfer dialog contents to the find service.
+ saveFindData();
+
+ // For reverse finds, need to remember the caret position
+ // before current selection
+ var newRange;
+ if (gReplaceDialog.searchBackwards.checked && selection.rangeCount > 0) {
+ newRange = selection.getRangeAt(0).cloneRange();
+ newRange.collapse(true);
+ }
+
+ // nsPlaintextEditor::InsertText fails if the string is empty,
+ // so make that a special case:
+ var replStr = gReplaceDialog.replaceInput.value;
+ if (replStr == "") {
+ gEditor.deleteSelection(gEditor.eNone, gEditor.eStrip);
+ } else {
+ gEditor.insertText(replStr);
+ }
+
+ // For reverse finds, need to move caret just before the replaced text
+ if (gReplaceDialog.searchBackwards.checked && newRange) {
+ gEditor.selection.removeAllRanges();
+ gEditor.selection.addRange(newRange);
+ }
+
+ return true;
+}
+
+function onReplaceAll() {
+ if (!gEditor) {
+ return;
+ }
+
+ var findStr = gReplaceDialog.findInput.value;
+ var repStr = gReplaceDialog.replaceInput.value;
+
+ // Transfer dialog contents to the find service.
+ saveFindData();
+
+ var finder = Cc["@mozilla.org/embedcomp/rangefind;1"]
+ .createInstance()
+ .QueryInterface(Ci.nsIFind);
+
+ finder.caseSensitive = gReplaceDialog.caseSensitive.checked;
+ finder.findBackwards = gReplaceDialog.searchBackwards.checked;
+
+ // We want the whole operation to be undoable in one swell foop,
+ // so start a transaction:
+ gEditor.beginTransaction();
+
+ // and to make sure we close the transaction, guard against exceptions:
+ try {
+ // Make a range containing the current selection,
+ // so we don't go past it when we wrap.
+ var selection = gEditor.selection;
+ var selecRange;
+ if (selection.rangeCount > 0) {
+ selecRange = selection.getRangeAt(0);
+ }
+ var origRange = selecRange.cloneRange();
+
+ // We'll need a range for the whole document:
+ var wholeDocRange = gEditor.document.createRange();
+ var rootNode = gEditor.rootElement;
+ wholeDocRange.selectNodeContents(rootNode);
+
+ // And start and end points:
+ var endPt = gEditor.document.createRange();
+
+ if (gReplaceDialog.searchBackwards.checked) {
+ endPt.setStart(wholeDocRange.startContainer, wholeDocRange.startOffset);
+ endPt.setEnd(wholeDocRange.startContainer, wholeDocRange.startOffset);
+ } else {
+ endPt.setStart(wholeDocRange.endContainer, wholeDocRange.endOffset);
+ endPt.setEnd(wholeDocRange.endContainer, wholeDocRange.endOffset);
+ }
+
+ // Find and replace from here to end (start) of document:
+ var foundRange;
+ var searchRange = wholeDocRange.cloneRange();
+ while (
+ (foundRange = finder.Find(findStr, searchRange, selecRange, endPt)) !=
+ null
+ ) {
+ gEditor.selection.removeAllRanges();
+ gEditor.selection.addRange(foundRange);
+
+ // The editor will leave the caret at the end of the replaced text.
+ // For reverse finds, we need it at the beginning,
+ // so save the next position now.
+ if (gReplaceDialog.searchBackwards.checked) {
+ selecRange = foundRange.cloneRange();
+ selecRange.setEnd(selecRange.startContainer, selecRange.startOffset);
+ }
+
+ // nsPlaintextEditor::InsertText fails if the string is empty,
+ // so make that a special case:
+ if (repStr == "") {
+ gEditor.deleteSelection(gEditor.eNone, gEditor.eStrip);
+ } else {
+ gEditor.insertText(repStr);
+ }
+
+ // If we're going forward, we didn't save selecRange before, so do it now:
+ if (!gReplaceDialog.searchBackwards.checked) {
+ selection = gEditor.selection;
+ if (selection.rangeCount <= 0) {
+ gEditor.endTransaction();
+ return;
+ }
+ selecRange = selection.getRangeAt(0).cloneRange();
+ }
+ }
+
+ // If no wrapping, then we're done
+ if (!gReplaceDialog.wrap.checked) {
+ gEditor.endTransaction();
+ return;
+ }
+
+ // If wrapping, find from start/end of document back to start point.
+ if (gReplaceDialog.searchBackwards.checked) {
+ // Collapse origRange to end
+ origRange.setStart(origRange.endContainer, origRange.endOffset);
+ // Set current position to document end
+ selecRange.setEnd(wholeDocRange.endContainer, wholeDocRange.endOffset);
+ selecRange.setStart(wholeDocRange.endContainer, wholeDocRange.endOffset);
+ } else {
+ // Collapse origRange to start
+ origRange.setEnd(origRange.startContainer, origRange.startOffset);
+ // Set current position to document start
+ selecRange.setStart(
+ wholeDocRange.startContainer,
+ wholeDocRange.startOffset
+ );
+ selecRange.setEnd(
+ wholeDocRange.startContainer,
+ wholeDocRange.startOffset
+ );
+ }
+
+ while (
+ (foundRange = finder.Find(
+ findStr,
+ wholeDocRange,
+ selecRange,
+ origRange
+ )) != null
+ ) {
+ gEditor.selection.removeAllRanges();
+ gEditor.selection.addRange(foundRange);
+
+ // Save insert point for backward case
+ if (gReplaceDialog.searchBackwards.checked) {
+ selecRange = foundRange.cloneRange();
+ selecRange.setEnd(selecRange.startContainer, selecRange.startOffset);
+ }
+
+ // nsPlaintextEditor::InsertText fails if the string is empty,
+ // so make that a special case:
+ if (repStr == "") {
+ gEditor.deleteSelection(gEditor.eNone, gEditor.eStrip);
+ } else {
+ gEditor.insertText(repStr);
+ }
+
+ // Get insert point for forward case
+ if (!gReplaceDialog.searchBackwards.checked) {
+ selection = gEditor.selection;
+ if (selection.rangeCount <= 0) {
+ gEditor.endTransaction();
+ return;
+ }
+ selecRange = selection.getRangeAt(0);
+ }
+ }
+ } catch (e) {}
+
+ gEditor.endTransaction();
+}
+
+function doEnabling() {
+ var findStr = gReplaceDialog.findInput.value;
+ gReplaceDialog.enabled = findStr;
+ gReplaceDialog.findNext.disabled = !findStr;
+ gReplaceDialog.replace.disabled = !findStr;
+ gReplaceDialog.replaceAndFind.disabled = !findStr;
+ gReplaceDialog.replaceAll.disabled = !findStr;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdReplace.xhtml b/comm/suite/editor/components/dialogs/content/EdReplace.xhtml
new file mode 100644
index 0000000000..54a9d81ba3
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdReplace.xhtml
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorReplace.dtd">
+
+<dialog id="replaceDlg" title="&replaceDialog.title;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ persist="screenX screenY"
+ buttons="cancel"
+ onload="onLoad()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdReplace.js"/>
+ <stringbundle id="findBundle" src="chrome://global/locale/finddialog.properties"/>
+
+ <hbox>
+ <vbox>
+ <spacer class="spacer"/>
+ <grid align="start">
+ <columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label value="&findField.label;" accesskey="&findField.accesskey;" control="dialog.findInput"/>
+ <textbox id="dialog.findInput" oninput="doEnabling();"/>
+ </row>
+ <row align="center">
+ <label value="&replaceField.label;" accesskey="&replaceField.accesskey;" control="dialog.replaceInput"/>
+ <textbox id="dialog.replaceInput" oninput="doEnabling();"/>
+ </row>
+ <row align="start">
+ <spacer/>
+ <vbox align="start">
+ <spacer class="bigspacer"/>
+ <checkbox id="dialog.caseSensitive" label="&caseSensitiveCheckbox.label;"
+ accesskey="&caseSensitiveCheckbox.accesskey;"/>
+ <checkbox id="dialog.wrap" label="&wrapCheckbox.label;"
+ accesskey="&wrapCheckbox.accesskey;"/>
+ <checkbox id="dialog.searchBackwards" label="&backwardsCheckbox.label;"
+ accesskey="&backwardsCheckbox.accesskey;"/>
+ </vbox>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+ <vbox>
+ <button id="findNext" label="&findNextButton.label;" accesskey="&findNextButton.accesskey;"
+ oncommand="onFindNext();" default="true"/>
+ <button id="replace" label="&replaceButton.label;" accesskey="&replaceButton.accesskey;"
+ oncommand="onReplace();"/>
+ <button id="replaceAndFind" label="&replaceAndFindButton.label;"
+ accesskey="&replaceAndFindButton.accesskey;" oncommand="onReplace(); onFindNext();"/>
+ <button id="replaceAll" label="&replaceAllButton.label;"
+ accesskey="&replaceAllButton.accesskey;" oncommand="onReplaceAll();"/>
+ <button dlgtype="cancel" label="&closeButton.label;" accesskey="&closeButton.accesskey;"/>
+ </vbox>
+ </hbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdSelectProps.js b/comm/suite/editor/components/dialogs/content/EdSelectProps.js
new file mode 100644
index 0000000000..c03fd73a67
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdSelectProps.js
@@ -0,0 +1,770 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// Global variables
+
+var hasValue;
+var oldValue;
+var insertNew;
+var itemArray;
+var theTree;
+var treeSelection;
+var selectElement;
+var currentItem = null;
+var selectedOption = null;
+var selectedOptionCount = 0;
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+// Utility functions
+
+function getParentIndex(index) {
+ switch (itemArray[index].level) {
+ case 0:
+ return -1;
+ case 1:
+ return 0;
+ }
+ // eslint-disable-next-line curly
+ while (itemArray[--index].level > 1);
+ return index;
+}
+
+function UpdateSelectMultiple() {
+ if (selectedOptionCount > 1) {
+ gDialog.selectMultiple.checked = true;
+ gDialog.selectMultiple.disabled = true;
+ } else {
+ gDialog.selectMultiple.disabled = false;
+ }
+}
+
+/* wrapper objects:
+ * readonly attribute Node element; // DOM node (select/optgroup/option)
+ * readonly attribute int level; // tree depth
+ * readonly attribute boolean container; // can contain options
+ * string getCellText(string col); // tree view helper
+ * string cycleCell(int currentIndex); // tree view helper
+ * void onFocus(); // load data into deck
+ * void onBlur(); // save data from deck
+ * boolean canDestroy(boolean prompt); // NB prompt not used
+ * void destroy(); // post remove callback
+ * void moveUp();
+ * boolean canMoveDown();
+ * void moveDown();
+ * void appendOption(newElement, currentIndex);
+ */
+
+// OPTION element wrapper object
+
+// Create a wrapper for the given element at the given level
+function optionObject(option, level) {
+ // select an added option (when loading from document)
+ if (option.hasAttribute("selected")) {
+ selectedOptionCount++;
+ }
+ this.level = level;
+ this.element = option;
+}
+
+optionObject.prototype.container = false;
+
+optionObject.prototype.getCellText = function(column) {
+ if (column.id == "SelectSelCol") {
+ return "";
+ }
+ if (column.id == "SelectValCol" && this.element.hasAttribute("value")) {
+ return this.element.getAttribute("value");
+ }
+ return this.element.text;
+};
+
+optionObject.prototype.cycleCell = function(index) {
+ if (this.element.hasAttribute("selected")) {
+ this.element.removeAttribute("selected");
+ selectedOptionCount--;
+ selectedOption = null;
+ } else {
+ // Different handling for multiselect lists
+ if (gDialog.selectMultiple.checked || !selectedOption) {
+ selectedOptionCount++;
+ } else if (selectedOption) {
+ selectedOption.removeAttribute("selected");
+ let column = theTree.columns.SelectSelCol;
+ theTree.invalidateColumn(column);
+ selectedOption = null;
+ }
+ this.element.setAttribute("selected", "");
+ selectedOption = this.element;
+ let column = theTree.columns.SelectSelCol;
+ theTree.invalidateCell(index, column);
+ }
+ if (currentItem == this) {
+ // Also update the deck
+ gDialog.optionSelected.setAttribute(
+ "checked",
+ this.element.hasAttribute("selected")
+ );
+ }
+ UpdateSelectMultiple();
+};
+
+optionObject.prototype.onFocus = function() {
+ gDialog.optionText.value = this.element.text;
+ hasValue = this.element.hasAttribute("value");
+ oldValue = this.element.value;
+ gDialog.optionHasValue.checked = hasValue;
+ gDialog.optionValue.value = hasValue ? this.element.value : this.element.text;
+ gDialog.optionSelected.checked = this.element.hasAttribute("selected");
+ gDialog.optionDisabled.checked = this.element.hasAttribute("disabled");
+ gDialog.selectDeck.setAttribute("selectedIndex", "2");
+};
+
+optionObject.prototype.onBlur = function() {
+ this.element.text = gDialog.optionText.value;
+ if (gDialog.optionHasValue.checked) {
+ this.element.value = gDialog.optionValue.value;
+ } else {
+ this.element.removeAttribute("value");
+ }
+ if (gDialog.optionSelected.checked) {
+ this.element.setAttribute("selected", "");
+ } else {
+ this.element.removeAttribute("selected");
+ }
+ if (gDialog.optionDisabled.checked) {
+ this.element.setAttribute("disabled", "");
+ } else {
+ this.element.removeAttribute("disabled");
+ }
+};
+
+optionObject.prototype.canDestroy = function(prompt) {
+ return true;
+ /* return !prompt ||
+ ConfirmWithTitle(GetString("DeleteOption"),
+ GetString("DeleteOptionMsg"),
+ GetString("DeleteOption"));*/
+};
+
+optionObject.prototype.destroy = function() {
+ // Deselect a removed option
+ if (this.element.hasAttribute("selected")) {
+ selectedOptionCount--;
+ selectedOption = null;
+ UpdateSelectMultiple();
+ }
+};
+
+/* 4 cases:
+ * a) optgroup -> optgroup
+ * ... ...
+ * option option
+ * b) optgroup -> option
+ * option optgroup
+ * ... ...
+ * c) option
+ * option
+ * d) option
+ * option
+ */
+
+optionObject.prototype.moveUp = function() {
+ var index = treeSelection.currentIndex;
+ if (
+ itemArray[index].level <
+ itemArray[index - 1].level + itemArray[index - 1].container
+ ) {
+ // we need to repaint the tree's lines
+ theTree.invalidateRange(getParentIndex(index), index);
+ // a) option is just after an optgroup, so it becomes the last child
+ itemArray[index].level = 2;
+ theTree.view.selectionChanged();
+ } else {
+ // otherwise new option level is now the same as the previous item
+ itemArray[index].level = itemArray[index - 1].level;
+ // swap the option with the previous item
+ itemArray.splice(index, 0, itemArray.splice(--index, 1)[0]);
+ }
+ selectTreeIndex(index, true);
+};
+
+optionObject.prototype.canMoveDown = function() {
+ // move down is not allowed on the last option if its level is 1
+ return this.level > 1 || itemArray.length - treeSelection.currentIndex > 1;
+};
+
+optionObject.prototype.moveDown = function() {
+ var index = treeSelection.currentIndex;
+ if (
+ index + 1 == itemArray.length ||
+ itemArray[index].level > itemArray[index + 1].level
+ ) {
+ // we need to repaint the tree's lines
+ theTree.invalidateRange(getParentIndex(index), index);
+ // a) option is last child of an optgroup, so it moves just after
+ itemArray[index].level = 1;
+ theTree.view.selectionChanged();
+ } else {
+ // level increases if the option was preceding an optgroup
+ itemArray[index].level += itemArray[index + 1].container;
+ // swap the option with the next item
+ itemArray.splice(index, 0, itemArray.splice(++index, 1)[0]);
+ }
+ selectTreeIndex(index, true);
+};
+
+optionObject.prototype.appendOption = function(child, parent) {
+ // special case quick check
+ if (this.level == 1) {
+ return gDialog.appendOption(child, 0);
+ }
+
+ // append the option to the parent element
+ parent = getParentIndex(parent);
+ return itemArray[parent].appendOption(child, parent);
+};
+
+// OPTGROUP element wrapper object
+
+function optgroupObject(optgroup) {
+ this.element = optgroup;
+}
+
+optgroupObject.prototype.level = 1;
+
+optgroupObject.prototype.container = true;
+
+optgroupObject.prototype.getCellText = function(column) {
+ return column.id == "SelectTextCol" ? this.element.label : "";
+};
+
+optgroupObject.prototype.cycleCell = function(index) {};
+
+optgroupObject.prototype.onFocus = function() {
+ gDialog.optgroupLabel.value = this.element.label;
+ gDialog.optgroupDisabled.checked = this.element.disabled;
+ gDialog.selectDeck.setAttribute("selectedIndex", "1");
+};
+
+optgroupObject.prototype.onBlur = function() {
+ this.element.label = gDialog.optgroupLabel.value;
+ this.element.disabled = gDialog.optgroupDisabled.checked;
+};
+
+optgroupObject.prototype.canDestroy = function(prompt) {
+ // Only removing empty option groups for now
+ return (
+ gDialog.nextChild(treeSelection.currentIndex) -
+ treeSelection.currentIndex ==
+ 1
+ );
+ /* && (!prompt ||
+ ConfirmWithTitle(GetString("DeleteOptGroup"),
+ GetString("DeleteOptGroupMsg"),
+ GetString("DeleteOptGroup")));
+*/
+};
+
+optgroupObject.prototype.destroy = function() {};
+
+optgroupObject.prototype.moveUp = function() {
+ // Find the index of the previous and next elements at the same level
+ var index = treeSelection.currentIndex;
+ var i = index;
+ // eslint-disable-next-line curly
+ while (itemArray[--index].level > 1);
+ var j = gDialog.nextChild(i);
+ // Cut out the element, cut the array in two, then join together
+ var movedItems = itemArray.splice(i, j - i);
+ var endItems = itemArray.splice(index);
+ itemArray = itemArray.concat(movedItems).concat(endItems);
+ // Repaint the lot
+ theTree.invalidateRange(index, j);
+ selectTreeIndex(index, true);
+};
+
+optgroupObject.prototype.canMoveDown = function() {
+ return gDialog.lastChild() > treeSelection.currentIndex;
+};
+
+optgroupObject.prototype.moveDown = function() {
+ // Find the index of the next two elements at the same level
+ var index = treeSelection.currentIndex;
+ var i = gDialog.nextChild(index);
+ var j = gDialog.nextChild(i);
+ // Cut out the element, cut the array in two, then join together
+ var movedItems = itemArray.splice(i, j - 1);
+ var endItems = itemArray.splice(index);
+ itemArray = itemArray.concat(movedItems).concat(endItems);
+ // Repaint the lot
+ theTree.invalidateRange(index, j);
+ index += j - i;
+ selectTreeIndex(index, true);
+};
+
+optgroupObject.prototype.appendOption = function(child, parent) {
+ var index = gDialog.nextChild(parent);
+ // XXX need to repaint the lines, tree won't do this
+ var primaryCol = theTree.columns.getPrimaryColumn();
+ theTree.invalidateCell(index - 1, primaryCol);
+ // insert the wrapped object as the last child
+ itemArray.splice(index, 0, new optionObject(child, 2));
+ theTree.rowCountChanged(index, 1);
+ selectTreeIndex(index, false);
+};
+
+// dialog initialization code
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ dump("Failed to get active editor!\n");
+ window.close();
+ return;
+ }
+
+ // Get a single selected select element
+ const kTagName = "select";
+ try {
+ selectElement = editor.getSelectedElement(kTagName);
+ } catch (e) {}
+
+ if (selectElement) {
+ // We found an element and don't need to insert one
+ insertNew = false;
+ } else {
+ insertNew = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+ try {
+ selectElement = editor.createElementWithDefaults(kTagName);
+ } catch (e) {}
+
+ if (!selectElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+ }
+
+ // SELECT element wrapper object
+ gDialog = {
+ // useful elements
+ accept: document.documentElement.getButton("accept"),
+ selectDeck: document.getElementById("SelectDeck"),
+ selectName: document.getElementById("SelectName"),
+ selectSize: document.getElementById("SelectSize"),
+ selectMultiple: document.getElementById("SelectMultiple"),
+ selectDisabled: document.getElementById("SelectDisabled"),
+ selectTabIndex: document.getElementById("SelectTabIndex"),
+ optgroupLabel: document.getElementById("OptGroupLabel"),
+ optgroupDisabled: document.getElementById("OptGroupDisabled"),
+ optionText: document.getElementById("OptionText"),
+ optionHasValue: document.getElementById("OptionHasValue"),
+ optionValue: document.getElementById("OptionValue"),
+ optionSelected: document.getElementById("OptionSelected"),
+ optionDisabled: document.getElementById("OptionDisabled"),
+ removeButton: document.getElementById("RemoveButton"),
+ previousButton: document.getElementById("PreviousButton"),
+ nextButton: document.getElementById("NextButton"),
+ tree: document.getElementById("SelectTree"),
+ // wrapper methods (except MoveUp and MoveDown)
+ element: selectElement.cloneNode(false),
+ level: 0,
+ container: true,
+ getCellText(column) {
+ return column.id == "SelectTextCol"
+ ? this.element.getAttribute("name")
+ : "";
+ },
+ cycleCell(index) {},
+ onFocus() {
+ gDialog.selectName.value = this.element.getAttribute("name");
+ gDialog.selectSize.value = this.element.getAttribute("size");
+ gDialog.selectMultiple.checked = this.element.hasAttribute("multiple");
+ gDialog.selectDisabled.checked = this.element.hasAttribute("disabled");
+ gDialog.selectTabIndex.value = this.element.getAttribute("tabindex");
+ this.selectDeck.setAttribute("selectedIndex", "0");
+ onNameInput();
+ },
+ onBlur() {
+ this.element.setAttribute("name", gDialog.selectName.value);
+ if (gDialog.selectSize.value) {
+ this.element.setAttribute("size", gDialog.selectSize.value);
+ } else {
+ this.element.removeAttribute("size");
+ }
+ if (gDialog.selectMultiple.checked) {
+ this.element.setAttribute("multiple", "");
+ } else {
+ this.element.removeAttribute("multiple");
+ }
+ if (gDialog.selectDisabled.checked) {
+ this.element.setAttribute("disabled", "");
+ } else {
+ this.element.removeAttribute("disabled");
+ }
+ if (gDialog.selectTabIndex.value) {
+ this.element.setAttribute("tabindex", gDialog.selectTabIndex.value);
+ } else {
+ this.element.removeAttribute("tabindex");
+ }
+ },
+ appendOption(child, parent) {
+ var index = itemArray.length;
+ // XXX need to repaint the lines, tree won't do this
+ theTree.invalidateRange(this.lastChild(), index);
+ // append the wrapped object
+ itemArray.push(new optionObject(child, 1));
+ theTree.rowCountChanged(index, 1);
+ selectTreeIndex(index, false);
+ },
+ canDestroy(prompt) {
+ return false;
+ },
+ canMoveDown() {
+ return false;
+ },
+ // helper methods
+ // Find the index of the next immediate child of the select
+ nextChild(index) {
+ // eslint-disable-next-line curly
+ while (++index < itemArray.length && itemArray[index].level > 1);
+ return index;
+ },
+ // Find the index of the last immediate child of the select
+ lastChild() {
+ var index = itemArray.length;
+ // eslint-disable-next-line curly
+ while (itemArray[--index].level > 1);
+ return index;
+ },
+ };
+ // Start with the <select> wrapper
+ itemArray = [gDialog];
+
+ // We modify the actual option and optgroup elements so clone them first
+ for (var child = selectElement.firstChild; child; child = child.nextSibling) {
+ if (child.tagName == "OPTION") {
+ itemArray.push(new optionObject(child.cloneNode(true), 1));
+ } else if (child.tagName == "OPTGROUP") {
+ itemArray.push(new optgroupObject(child.cloneNode(false)));
+ for (
+ var grandchild = child.firstChild;
+ grandchild;
+ grandchild = grandchild.nextSibling
+ ) {
+ if (grandchild.tagName == "OPTION") {
+ itemArray.push(new optionObject(grandchild.cloneNode(true), 2));
+ }
+ }
+ }
+ }
+
+ UpdateSelectMultiple();
+
+ // Define a custom view for the tree
+ theTree = gDialog.tree;
+ theTree.view = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsITreeView",
+ "nsISupportsWeakReference",
+ ]),
+ // useful for debugging
+ get wrappedJSObject() {
+ return this;
+ },
+ get rowCount() {
+ return itemArray.length;
+ },
+ get selection() {
+ return treeSelection;
+ },
+ set selection(selection) {
+ return (treeSelection = selection);
+ },
+ getRowProperties(index) {
+ return "";
+ },
+ // could have used a wrapper for this
+ getCellProperties(index, column) {
+ if (column.id == "SelectSelCol" && !itemArray[index].container) {
+ return "checked-" + itemArray[index].element.hasAttribute("selected");
+ }
+ return "";
+ },
+ getColumnProperties(column) {
+ return "";
+ },
+ // get info from wrapper
+ isContainer(index) {
+ return itemArray[index].container;
+ },
+ isContainerOpen(index) {
+ return true;
+ },
+ isContainerEmpty(index) {
+ return true;
+ },
+ isSeparator(index) {
+ return false;
+ },
+ isSorted() {
+ return false;
+ },
+ // d&d not implemented yet!
+ canDrop(index, orientation) {
+ return false;
+ },
+ drop(index, orientation) {
+ alert("drop:" + index + "," + orientation);
+ },
+ // same as the global helper
+ getParentIndex,
+ // tree needs to know when to paint lines
+ hasNextSibling(index, after) {
+ if (!index) {
+ return false;
+ }
+ var level = itemArray[index].level;
+ while (++after < itemArray.length) {
+ switch (level - itemArray[after].level) {
+ case 1:
+ return false;
+ case 0:
+ return true;
+ }
+ }
+ return false;
+ },
+ getLevel(index) {
+ return itemArray[index].level;
+ },
+ getImageSrc(index, column) {},
+ getProgressMode(index, column) {},
+ getCellValue(index, column) {},
+ getCellText(index, column) {
+ return itemArray[index].getCellText(column);
+ },
+ setTree(tree) {
+ this.tree = tree;
+ },
+ toggleOpenState(index) {},
+ cycleHeader(col) {},
+ selectionChanged() {
+ // Save current values and update buttons and deck
+ if (currentItem) {
+ currentItem.onBlur();
+ }
+ var currentIndex = treeSelection.currentIndex;
+ currentItem = itemArray[currentIndex];
+ gDialog.removeButton.disabled = !currentItem.canDestroy();
+ gDialog.previousButton.disabled = currentIndex < 2;
+ gDialog.nextButton.disabled = !currentItem.canMoveDown();
+ // For Advanced Edit
+ globalElement = currentItem.element;
+ currentItem.onFocus();
+ },
+ cycleCell(index, column) {
+ itemArray[index].cycleCell(index);
+ },
+ isEditable(index, column) {
+ return false;
+ },
+ };
+ treeSelection.select(0);
+ currentItem = gDialog;
+ // onNameInput();
+
+ SetTextboxFocus(gDialog.selectName);
+
+ SetWindowLocation();
+}
+
+// Called from Advanced Edit
+function InitDialog() {
+ currentItem.onFocus();
+}
+
+// Called from Advanced Edit
+function ValidateData() {
+ currentItem.onBlur();
+ return true;
+}
+
+function onAccept() {
+ // All values are valid - copy to actual element in doc or
+ // element created to insert
+ ValidateData();
+
+ var editor = GetCurrentEditor();
+
+ // Coalesce into one undo transaction
+ editor.beginTransaction();
+
+ try {
+ editor.cloneAttributes(selectElement, gDialog.element);
+
+ if (insertNew) {
+ // 'true' means delete the selection before inserting
+ editor.insertElementAtSelection(selectElement, true);
+ }
+
+ editor.setShouldTxnSetSelection(false);
+
+ while (selectElement.lastChild) {
+ editor.deleteNode(selectElement.lastChild);
+ }
+
+ var offset = 0;
+ for (var i = 1; i < itemArray.length; i++) {
+ if (itemArray[i].level > 1) {
+ selectElement.lastChild.appendChild(itemArray[i].element);
+ } else {
+ editor.insertNode(itemArray[i].element, selectElement, offset++);
+ }
+ }
+
+ editor.setShouldTxnSetSelection(true);
+ } finally {
+ editor.endTransaction();
+ }
+
+ SaveWindowLocation();
+}
+
+// Button actions
+function AddOption() {
+ currentItem.appendOption(
+ GetCurrentEditor().createElementWithDefaults("option"),
+ treeSelection.currentIndex
+ );
+ SetTextboxFocus(gDialog.optionText);
+}
+
+function AddOptGroup() {
+ var optgroupElement = GetCurrentEditor().createElementWithDefaults(
+ "optgroup"
+ );
+ var index = itemArray.length;
+ // XXX need to repaint the lines, tree won't do this
+ theTree.invalidateRange(gDialog.lastChild(), index);
+ // append the wrapped object
+ itemArray.push(new optgroupObject(optgroupElement));
+ theTree.rowCountChanged(index, 1);
+ selectTreeIndex(index, false);
+ SetTextboxFocus(gDialog.optgroupLabel);
+}
+
+function RemoveElement() {
+ if (currentItem.canDestroy(true)) {
+ // Only removing empty option groups for now
+ var index = treeSelection.currentIndex;
+ var level = itemArray[index].level;
+ // Perform necessary cleanup and remove the wrapper
+ itemArray[index].destroy();
+ itemArray.splice(index, 1);
+ --index;
+ // XXX need to repaint the lines, tree won't do this
+ if (level == 1) {
+ var last = gDialog.lastChild();
+ if (index > last) {
+ theTree.invalidateRange(last, index);
+ }
+ }
+ selectTreeIndex(index, true);
+ theTree.rowCountChanged(++index, -1);
+ }
+}
+
+// Event handler
+function onTreeKeyUp(event) {
+ if (event.keyCode == event.DOM_VK_SPACE) {
+ currentItem.cycleCell();
+ }
+}
+
+function onNameInput() {
+ var disabled = !gDialog.selectName.value;
+ if (gDialog.accept.disabled != disabled) {
+ gDialog.accept.disabled = disabled;
+ }
+ gDialog.element.setAttribute("name", gDialog.selectName.value);
+ // repaint the tree
+ var primaryCol = theTree.columns.getPrimaryColumn();
+ theTree.invalidateCell(treeSelection.currentIndex, primaryCol);
+}
+
+function onLabelInput() {
+ currentItem.element.setAttribute("label", gDialog.optgroupLabel.value);
+ // repaint the tree
+ var primaryCol = theTree.columns.getPrimaryColumn();
+ theTree.invalidateCell(treeSelection.currentIndex, primaryCol);
+}
+
+function onTextInput() {
+ currentItem.element.text = gDialog.optionText.value;
+ // repaint the tree
+ if (hasValue) {
+ var primaryCol = theTree.columns.getPrimaryColumn();
+ theTree.invalidateCell(treeSelection.currentIndex, primaryCol);
+ } else {
+ gDialog.optionValue.value = gDialog.optionText.value;
+ theTree.invalidateRow(treeSelection.currentIndex);
+ }
+}
+
+function onValueInput() {
+ gDialog.optionHasValue.checked = hasValue = true;
+ oldValue = gDialog.optionValue.value;
+ currentItem.element.setAttribute("value", oldValue);
+ // repaint the tree
+ var column = theTree.columns.SelectValCol;
+ theTree.invalidateCell(treeSelection.currentIndex, column);
+}
+
+function onHasValueClick() {
+ hasValue = gDialog.optionHasValue.checked;
+ if (hasValue) {
+ gDialog.optionValue.value = oldValue;
+ currentItem.element.setAttribute("value", oldValue);
+ } else {
+ oldValue = gDialog.optionValue.value;
+ gDialog.optionValue.value = gDialog.optionText.value;
+ currentItem.element.removeAttribute("value");
+ }
+ // repaint the tree
+ var column = theTree.columns.SelectValCol;
+ theTree.invalidateCell(treeSelection.currentIndex, column);
+}
+
+function onSelectMultipleClick() {
+ // Recalculate the unique selected option if we need it and have lost it
+ if (
+ !gDialog.selectMultiple.checked &&
+ selectedOptionCount == 1 &&
+ !selectedOption
+ ) {
+ // eslint-disable-next-line curly
+ for (
+ var i = 1;
+ !(selectedOption = itemArray[i].element).hasAttribute("selected");
+ i++
+ );
+ }
+}
+
+function selectTreeIndex(index, focus) {
+ treeSelection.select(index);
+ theTree.ensureRowIsVisible(index);
+ if (focus) {
+ gDialog.tree.focus();
+ }
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdSelectProps.xhtml b/comm/suite/editor/components/dialogs/content/EdSelectProps.xhtml
new file mode 100644
index 0000000000..ff91b02e28
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdSelectProps.xhtml
@@ -0,0 +1,143 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edSelectProperties SYSTEM "chrome://editor/locale/EditorSelectProperties.dtd">
+%edSelectProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ buttons="accept,cancel">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdSelectProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <!-- Setting rows="7" on tree isn't working, equalsize vbox sets tree height. -->
+ <vbox equalsize="always">
+ <tree id="SelectTree" onselect="treeBoxObject.view.selectionChanged();" onkeyup="onTreeKeyUp(event);">
+ <treecols id="SelectCols">
+ <treecol id="SelectTextCol" flex="3" label="&TextHeader.label;" primary="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="SelectValCol" flex="2" label="&ValueHeader.label;"/>
+ <treecol id="SelectSelCol" label="&SelectedHeader.label;" cycler="true"/>
+ </treecols>
+
+ <treechildren id="SelectTreeChildren"/>
+ </tree>
+
+ <hbox flex="1">
+ <deck flex="1" id="SelectDeck" index="0">
+ <groupbox flex="1">
+ <hbox class="groupbox-title">
+ <label class="header">&Select.label;</label>
+ </hbox>
+ <grid flex="1"><columns><column flex="1"/><column/></columns>
+ <rows>
+ <row align="center">
+ <label control="SelectName" value="&SelectName.label;" accesskey="&SelectName.accesskey;"/>
+ <textbox id="SelectName" flex="1" oninput="onNameInput();"/>
+ </row>
+ <row align="center">
+ <label control="SelectSize" value="&SelectSize.label;" accesskey="&SelectSize.accesskey;"/>
+ <hbox>
+ <textbox id="SelectSize" class="narrow" oninput="forceInteger(this.id);"/>
+ </hbox>
+ </row>
+ <row>
+ <spacer/>
+ <checkbox id="SelectMultiple" flex="1" label="&SelectMultiple.label;" accesskey="&SelectMultiple.accesskey;" oncommand="onSelectMultipleClick();"/>
+ </row>
+ <row>
+ <spacer/>
+ <checkbox id="SelectDisabled" flex="1" label="&SelectDisabled.label;" accesskey="&SelectDisabled.accesskey;"/>
+ </row>
+ <row align="center">
+ <label control="SelectTabIndex" value="&SelectTabIndex.label;" accesskey="&SelectTabIndex.accesskey;"/>
+ <hbox>
+ <textbox id="SelectTabIndex" class="narrow" oninput="forceInteger(this.id);"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <groupbox flex="1">
+ <hbox class="groupbox-title">
+ <label class="header">&OptGroup.label;</label>
+ </hbox>
+ <grid flex="1"><columns><column flex="1"/><column/></columns>
+ <rows>
+ <row align="center">
+ <label control="OptGroupLabel" value="&OptGroupLabel.label;" accesskey="&OptGroupLabel.accesskey;"/>
+ <textbox id="OptGroupLabel" oninput="onLabelInput();"/>
+ </row>
+ <row>
+ <spacer/>
+ <checkbox id="OptGroupDisabled" label="&OptGroupDisabled.label;" accesskey="&OptGroupDisabled.accesskey;"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <groupbox flex="1">
+ <hbox class="groupbox-title">
+ <label class="header">&Option.label;</label>
+ </hbox>
+ <grid flex="1"><columns><column flex="1"/><column/></columns>
+ <rows>
+ <row align="center">
+ <label control="OptionText" value="&OptionText.label;" accesskey="&OptionText.accesskey;"/>
+ <textbox id="OptionText" oninput="onTextInput();"/>
+ </row>
+ <row align="center">
+ <checkbox id="OptionHasValue" label="&OptionValue.label;" accesskey="&OptionValue.accesskey;" oncommand="onHasValueClick();"/>
+ <textbox id="OptionValue" oninput="onValueInput();"/>
+ </row>
+ <row>
+ <spacer/>
+ <checkbox id="OptionSelected" label="&OptionSelected.label;" accesskey="&OptionSelected.accesskey;" oncommand="currentItem.cycleCell();"/>
+ </row>
+ <row>
+ <spacer/>
+ <checkbox id="OptionDisabled" label="&OptionDisabled.label;" accesskey="&OptionDisabled.accesskey;"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+ </deck>
+
+ <vbox>
+ <button label="&AddOption.label;" accesskey="&AddOption.accesskey;" oncommand="AddOption();"/>
+ <button label="&AddOptGroup.label;" accesskey="&AddOptGroup.accesskey;" oncommand="AddOptGroup();"/>
+ <button id="RemoveButton" label="&RemoveElement.label;" accesskey="&RemoveElement.accesskey;"
+ oncommand="RemoveElement();" disabled="true"/>
+ <button id="PreviousButton" label="&MoveElementUp.label;" accesskey="&MoveElementUp.accesskey;"
+ oncommand="currentItem.moveUp();" disabled="true" type="row"/>
+ <button id="NextButton" label="&MoveElementDown.label;" accesskey="&MoveElementDown.accesskey;"
+ oncommand="currentItem.moveDown();" disabled="true" type="row"/>
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </vbox>
+ </hbox>
+ </vbox>
+
+ <separator class="groove"/>
+
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdSnapToGrid.js b/comm/suite/editor/components/dialogs/content/EdSnapToGrid.js
new file mode 100644
index 0000000000..ed546d3540
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdSnapToGrid.js
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gEditor;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup()
+{
+ gEditor = GetCurrentEditor();
+ if (!gEditor)
+ {
+ window.close();
+ return;
+ }
+
+ gEditor instanceof Ci.nsIHTMLAbsPosEditor;
+
+ gDialog.enableSnapToGrid = document.getElementById("enableSnapToGrid");
+ gDialog.sizeInput = document.getElementById("size");
+ gDialog.sizeLabel = document.getElementById("sizeLabel");
+ gDialog.unitLabel = document.getElementById("unitLabel");
+
+ // Initialize control values based on existing attributes
+ InitDialog()
+
+ // SET FOCUS TO FIRST CONTROL
+ SetTextboxFocus(gDialog.sizeInput);
+
+ // Resize window
+ window.sizeToContent();
+
+ SetWindowLocation();
+}
+
+// Set dialog widgets with attribute data
+// We get them from globalElement copy so this can be used
+// by AdvancedEdit(), which is shared by all property dialogs
+function InitDialog()
+{
+ gDialog.enableSnapToGrid.checked = gEditor.snapToGridEnabled;
+ toggleSnapToGrid();
+
+ gDialog.sizeInput.value = gEditor.gridSize;
+}
+
+function onAccept()
+{
+ gEditor.snapToGridEnabled = gDialog.enableSnapToGrid.checked;
+ gEditor.gridSize = gDialog.sizeInput.value;
+}
+
+function toggleSnapToGrid()
+{
+ SetElementEnabledById("size", gDialog.enableSnapToGrid.checked)
+ SetElementEnabledById("sizeLabel", gDialog.enableSnapToGrid.checked)
+ SetElementEnabledById("unitLabel", gDialog.enableSnapToGrid.checked)
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdSnapToGrid.xhtml b/comm/suite/editor/components/dialogs/content/EdSnapToGrid.xhtml
new file mode 100644
index 0000000000..9ae6643975
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdSnapToGrid.xhtml
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorSnapToGrid.dtd">
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <!--- Element-specific methods -->
+ <script src="chrome://editor/content/EdSnapToGrid.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <checkbox id="enableSnapToGrid"
+ label="&enableSnapToGrid.label;"
+ accesskey="&enableSnapToGrid.accessKey;"
+ oncommand="toggleSnapToGrid();"/>
+
+ <spacer class="spacer"/>
+
+ <grid>
+ <columns><column/><column/><column /></columns>
+ <rows>
+ <row align="center">
+ <label value="&sizeEditField.label;"
+ id="sizeLabel"
+ control="size"
+ accesskey="&sizeEditField.accessKey;"/>
+ <textbox class="narrow" id="size" oninput="forceInteger('size')"/>
+ <label id="unitLabel"
+ value="&pixelsLabel.value;" />
+ </row>
+ </rows>
+ </grid>
+
+ <spacer class="spacer"/>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdSpellCheck.js b/comm/suite/editor/components/dialogs/content/EdSpellCheck.js
new file mode 100644
index 0000000000..ddb23726cd
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdSpellCheck.js
@@ -0,0 +1,495 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../../../mail/base/content/utilityOverlay.js */
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var { InlineSpellChecker } = ChromeUtils.import(
+ "resource://gre/modules/InlineSpellChecker.jsm"
+);
+
+var gMisspelledWord;
+var gSpellChecker = null;
+var gAllowSelectWord = true;
+var gPreviousReplaceWord = "";
+var gFirstTime = true;
+var gLastSelectedLang = null;
+var gDictCount = 0;
+
+document.addEventListener("dialogaccept", doDefault);
+document.addEventListener("dialogcancel", CancelSpellCheck);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ // Get the spellChecker shell
+ gSpellChecker = Cu.createSpellChecker();
+ if (!gSpellChecker) {
+ dump("SpellChecker not found!!!\n");
+ window.close();
+ return;
+ }
+
+ // Start the spell checker module.
+ try {
+ var skipBlockQuotes = window.arguments[1];
+ var enableSelectionChecking = window.arguments[2];
+
+ gSpellChecker.setFilterType(
+ skipBlockQuotes
+ ? Ci.nsIEditorSpellCheck.FILTERTYPE_MAIL
+ : Ci.nsIEditorSpellCheck.FILTERTYPE_NORMAL
+ );
+ gSpellChecker.InitSpellChecker(
+ editor,
+ enableSelectionChecking,
+ spellCheckStarted
+ );
+ } catch (ex) {
+ dump("*** Exception error: InitSpellChecker\n");
+ window.close();
+ }
+}
+
+function spellCheckStarted() {
+ gDialog.MisspelledWordLabel = document.getElementById("MisspelledWordLabel");
+ gDialog.MisspelledWord = document.getElementById("MisspelledWord");
+ gDialog.ReplaceButton = document.getElementById("Replace");
+ gDialog.IgnoreButton = document.getElementById("Ignore");
+ gDialog.StopButton = document.getElementById("Stop");
+ gDialog.CloseButton = document.getElementById("Close");
+ gDialog.ReplaceWordInput = document.getElementById("ReplaceWordInput");
+ gDialog.SuggestedList = document.getElementById("SuggestedList");
+ gDialog.LanguageMenulist = document.getElementById("LanguageMenulist");
+
+ // Fill in the language menulist and sync it up
+ // with the spellchecker's current language.
+
+ var curLang;
+
+ try {
+ curLang = gSpellChecker.GetCurrentDictionary();
+ } catch (ex) {
+ curLang = "";
+ }
+
+ InitLanguageMenu(curLang);
+
+ // Get the first misspelled word and setup all UI
+ NextWord();
+
+ // When startup param is true, setup different UI when spell checking
+ // just before sending mail message
+ if (window.arguments[0]) {
+ // If no misspelled words found, simply close dialog and send message
+ if (!gMisspelledWord) {
+ onClose();
+ return;
+ }
+
+ // Hide "Close" button and use "Send" instead
+ gDialog.CloseButton.hidden = true;
+ gDialog.CloseButton = document.getElementById("Send");
+ gDialog.CloseButton.hidden = false;
+ } else {
+ // Normal spell checking - hide the "Stop" button
+ // (Note that this button is the "Cancel" button for
+ // Esc keybinding and related window close actions)
+ gDialog.StopButton.hidden = true;
+ }
+
+ // Clear flag that determines message when
+ // no misspelled word is found
+ // (different message when used for the first time)
+ gFirstTime = false;
+
+ window.sizeToContent();
+}
+
+function InitLanguageMenu(aCurLang) {
+ // Get the list of dictionaries from
+ // the spellchecker.
+
+ var dictList;
+ try {
+ dictList = gSpellChecker.GetDictionaryList();
+ } catch (ex) {
+ dump("Failed to get DictionaryList!\n");
+ return;
+ }
+
+ // If we're not just starting up and dictionary count
+ // hasn't changed then no need to update the menu.
+ if (gDictCount == dictList.length) {
+ return;
+ }
+
+ // Store current dictionary count.
+ gDictCount = dictList.length;
+
+ var inlineSpellChecker = new InlineSpellChecker();
+ var sortedList = inlineSpellChecker.sortDictionaryList(dictList);
+
+ // Remove any languages from the list.
+ var languageMenuPopup = gDialog.LanguageMenulist.menupopup;
+ while (languageMenuPopup.firstChild.localName != "menuseparator") {
+ languageMenuPopup.firstChild.remove();
+ }
+
+ var defaultItem = null;
+
+ for (var i = 0; i < gDictCount; i++) {
+ let item = document.createXULElement("menuitem");
+ item.setAttribute("label", sortedList[i].displayName);
+ item.setAttribute("value", sortedList[i].localeCode);
+ let beforeItem = gDialog.LanguageMenulist.getItemAtIndex(i);
+ languageMenuPopup.insertBefore(item, beforeItem);
+
+ if (aCurLang && sortedList[i].localeCode == aCurLang) {
+ defaultItem = item;
+ }
+ }
+
+ // Now make sure the correct item in the menu list is selected.
+ if (defaultItem) {
+ gDialog.LanguageMenulist.selectedItem = defaultItem;
+ gLastSelectedLang = defaultItem;
+ }
+}
+
+function DoEnabling() {
+ if (!gMisspelledWord) {
+ // No more misspelled words
+ gDialog.MisspelledWord.setAttribute(
+ "value",
+ GetString(gFirstTime ? "NoMisspelledWord" : "CheckSpellingDone")
+ );
+
+ gDialog.ReplaceButton.removeAttribute("default");
+ gDialog.IgnoreButton.removeAttribute("default");
+
+ gDialog.CloseButton.setAttribute("default", "true");
+ // Shouldn't have to do this if "default" is true?
+ gDialog.CloseButton.focus();
+
+ SetElementEnabledById("MisspelledWordLabel", false);
+ SetElementEnabledById("ReplaceWordLabel", false);
+ SetElementEnabledById("ReplaceWordInput", false);
+ SetElementEnabledById("CheckWord", false);
+ SetElementEnabledById("SuggestedListLabel", false);
+ SetElementEnabledById("SuggestedList", false);
+ SetElementEnabledById("Ignore", false);
+ SetElementEnabledById("IgnoreAll", false);
+ SetElementEnabledById("Replace", false);
+ SetElementEnabledById("ReplaceAll", false);
+ SetElementEnabledById("AddToDictionary", false);
+ } else {
+ SetElementEnabledById("MisspelledWordLabel", true);
+ SetElementEnabledById("ReplaceWordLabel", true);
+ SetElementEnabledById("ReplaceWordInput", true);
+ SetElementEnabledById("CheckWord", true);
+ SetElementEnabledById("SuggestedListLabel", true);
+ SetElementEnabledById("SuggestedList", true);
+ SetElementEnabledById("Ignore", true);
+ SetElementEnabledById("IgnoreAll", true);
+ SetElementEnabledById("AddToDictionary", true);
+
+ gDialog.CloseButton.removeAttribute("default");
+ SetReplaceEnable();
+ }
+}
+
+function NextWord() {
+ gMisspelledWord = gSpellChecker.GetNextMisspelledWord();
+ SetWidgetsForMisspelledWord();
+}
+
+function SetWidgetsForMisspelledWord() {
+ gDialog.MisspelledWord.setAttribute("value", gMisspelledWord);
+
+ // Initial replace word is misspelled word
+ gDialog.ReplaceWordInput.value = gMisspelledWord;
+ gPreviousReplaceWord = gMisspelledWord;
+
+ // This sets gDialog.ReplaceWordInput to first suggested word in list
+ FillSuggestedList(gMisspelledWord);
+
+ DoEnabling();
+
+ if (gMisspelledWord) {
+ SetTextboxFocus(gDialog.ReplaceWordInput);
+ }
+}
+
+function CheckWord() {
+ var word = gDialog.ReplaceWordInput.value;
+ if (word) {
+ if (gSpellChecker.CheckCurrentWord(word)) {
+ FillSuggestedList(word);
+ SetReplaceEnable();
+ } else {
+ ClearListbox(gDialog.SuggestedList);
+ var item = gDialog.SuggestedList.appendItem(
+ GetString("CorrectSpelling"),
+ ""
+ );
+ if (item) {
+ item.setAttribute("disabled", "true");
+ }
+ // Suppress being able to select the message text
+ gAllowSelectWord = false;
+ }
+ }
+}
+
+function SelectSuggestedWord() {
+ if (gAllowSelectWord) {
+ if (gDialog.SuggestedList.selectedItem) {
+ var selValue = gDialog.SuggestedList.selectedItem.label;
+ gDialog.ReplaceWordInput.value = selValue;
+ gPreviousReplaceWord = selValue;
+ } else {
+ gDialog.ReplaceWordInput.value = gPreviousReplaceWord;
+ }
+ SetReplaceEnable();
+ }
+}
+
+function ChangeReplaceWord() {
+ // Calling this triggers SelectSuggestedWord(),
+ // so temporarily suppress the effect of that
+ var saveAllow = gAllowSelectWord;
+ gAllowSelectWord = false;
+
+ // Select matching word in list
+ var newSelectedItem;
+ var replaceWord = TrimString(gDialog.ReplaceWordInput.value);
+ if (replaceWord) {
+ for (var i = 0; i < gDialog.SuggestedList.getRowCount(); i++) {
+ var item = gDialog.SuggestedList.getItemAtIndex(i);
+ if (item.label == replaceWord) {
+ newSelectedItem = item;
+ break;
+ }
+ }
+ }
+ gDialog.SuggestedList.selectedItem = newSelectedItem;
+
+ gAllowSelectWord = saveAllow;
+
+ // Remember the new word
+ gPreviousReplaceWord = gDialog.ReplaceWordInput.value;
+
+ SetReplaceEnable();
+}
+
+function Ignore() {
+ NextWord();
+}
+
+function IgnoreAll() {
+ if (gMisspelledWord) {
+ gSpellChecker.IgnoreWordAllOccurrences(gMisspelledWord);
+ }
+ NextWord();
+}
+
+function Replace(newWord) {
+ if (!newWord) {
+ return;
+ }
+
+ if (gMisspelledWord && gMisspelledWord != newWord) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+ try {
+ gSpellChecker.ReplaceWord(gMisspelledWord, newWord, false);
+ } catch (e) {}
+ editor.endTransaction();
+ }
+ NextWord();
+}
+
+function ReplaceAll() {
+ var newWord = gDialog.ReplaceWordInput.value;
+ if (gMisspelledWord && gMisspelledWord != newWord) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+ try {
+ gSpellChecker.ReplaceWord(gMisspelledWord, newWord, true);
+ } catch (e) {}
+ editor.endTransaction();
+ }
+ NextWord();
+}
+
+function AddToDictionary() {
+ if (gMisspelledWord) {
+ gSpellChecker.AddWordToDictionary(gMisspelledWord);
+ }
+ NextWord();
+}
+
+function EditDictionary() {
+ window.openDialog(
+ "chrome://editor/content/EdDictionary.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ "",
+ gMisspelledWord
+ );
+}
+
+function SelectLanguage() {
+ var item = gDialog.LanguageMenulist.selectedItem;
+ if (item.value != "more-cmd") {
+ gSpellChecker.SetCurrentDictionary(item.value);
+ // For compose windows we need to set the "lang" attribute so the
+ // core editor uses the correct dictionary for the inline spell check.
+ if (window.arguments[1]) {
+ if ("ComposeChangeLanguage" in window.opener) {
+ // We came here from a compose window.
+ window.opener.ComposeChangeLanguage(item.value);
+ } else {
+ window.opener.document.documentElement.setAttribute("lang", item.value);
+ }
+ }
+ gLastSelectedLang = item;
+ } else {
+ openDictionaryList();
+
+ if (gLastSelectedLang) {
+ gDialog.LanguageMenulist.selectedItem = gLastSelectedLang;
+ }
+ }
+}
+
+function Recheck() {
+ var recheckLanguage;
+
+ function finishRecheck() {
+ gSpellChecker.SetCurrentDictionary(recheckLanguage);
+ gMisspelledWord = gSpellChecker.GetNextMisspelledWord();
+ SetWidgetsForMisspelledWord();
+ }
+
+ // TODO: Should we bother to add a "Recheck" method to interface?
+ try {
+ recheckLanguage = gSpellChecker.GetCurrentDictionary();
+ gSpellChecker.UninitSpellChecker();
+ // Clear the ignore all list.
+ Cc["@mozilla.org/spellchecker/personaldictionary;1"]
+ .getService(Ci.mozIPersonalDictionary)
+ .endSession();
+ gSpellChecker.InitSpellChecker(GetCurrentEditor(), false, finishRecheck);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+}
+
+function FillSuggestedList(misspelledWord) {
+ var list = gDialog.SuggestedList;
+
+ // Clear the current contents of the list
+ gAllowSelectWord = false;
+ ClearListbox(list);
+ var item;
+
+ if (misspelledWord.length > 0) {
+ // Get suggested words until an empty string is returned
+ var count = 0;
+ do {
+ var word = gSpellChecker.GetSuggestedWord();
+ if (word.length > 0) {
+ list.appendItem(word, "");
+ count++;
+ }
+ } while (word.length > 0);
+
+ if (count == 0) {
+ // No suggestions - show a message but don't let user select it
+ item = list.appendItem(GetString("NoSuggestedWords"));
+ if (item) {
+ item.setAttribute("disabled", "true");
+ }
+ gAllowSelectWord = false;
+ } else {
+ gAllowSelectWord = true;
+ // Initialize with first suggested list by selecting it
+ gDialog.SuggestedList.selectedIndex = 0;
+ }
+ } else {
+ item = list.appendItem("", "");
+ if (item) {
+ item.setAttribute("disabled", "true");
+ }
+ }
+}
+
+function SetReplaceEnable() {
+ // Enable "Change..." buttons only if new word is different than misspelled
+ var newWord = gDialog.ReplaceWordInput.value;
+ var enable = newWord.length > 0 && newWord != gMisspelledWord;
+ SetElementEnabledById("Replace", enable);
+ SetElementEnabledById("ReplaceAll", enable);
+ if (enable) {
+ gDialog.ReplaceButton.setAttribute("default", "true");
+ gDialog.IgnoreButton.removeAttribute("default");
+ } else {
+ gDialog.IgnoreButton.setAttribute("default", "true");
+ gDialog.ReplaceButton.removeAttribute("default");
+ }
+}
+
+function doDefault(event) {
+ if (gDialog.ReplaceButton.getAttribute("default") == "true") {
+ Replace(gDialog.ReplaceWordInput.value);
+ } else if (gDialog.IgnoreButton.getAttribute("default") == "true") {
+ Ignore();
+ } else if (gDialog.CloseButton.getAttribute("default") == "true") {
+ onClose();
+ }
+
+ event.preventDefault();
+}
+
+function ExitSpellChecker() {
+ if (gSpellChecker) {
+ try {
+ gSpellChecker.UninitSpellChecker();
+ // now check the document over again with the new dictionary
+ // if we have an inline spellchecker
+ if (
+ "InlineSpellCheckerUI" in window.opener &&
+ window.opener.InlineSpellCheckerUI.enabled
+ ) {
+ window.opener.InlineSpellCheckerUI.mInlineSpellChecker.spellCheckRange(
+ null
+ );
+ }
+ } finally {
+ gSpellChecker = null;
+ }
+ }
+}
+
+function CancelSpellCheck() {
+ ExitSpellChecker();
+
+ // Signal to calling window that we canceled
+ window.opener.cancelSendMessage = true;
+}
+
+function onClose() {
+ ExitSpellChecker();
+
+ window.opener.cancelSendMessage = false;
+ window.close();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdSpellCheck.xhtml b/comm/suite/editor/components/dialogs/content/EdSpellCheck.xhtml
new file mode 100644
index 0000000000..4cb9f49198
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdSpellCheck.xhtml
@@ -0,0 +1,113 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorSpellCheck.dtd">
+
+<!-- dialog containing a control requiring initial setup -->
+<dialog id="spellCheckDlg" buttons="cancel" title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ persist="screenX screenY"
+ onload="Startup()">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://communicator/content/utilityOverlay.js"/>
+ <script src="chrome://editor/content/EdSpellCheck.js"/>
+ <script src="chrome://global/content/contentAreaUtils.js"/>
+
+ <stringbundle id="languageBundle" src="chrome://global/locale/languageNames.properties"/>
+ <stringbundle id="regionBundle" src="chrome://global/locale/regionNames.properties"/>
+
+ <grid>
+ <columns>
+ <column class="spell-check"/>
+ <column class="spell-check" flex="1"/>
+ <column class="spell-check"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label id="MisspelledWordLabel" value="&misspelledWord.label;"/>
+ <label class="bold" id="MisspelledWord" crop="end"/>
+ <button class="spell-check" label="&recheckButton2.label;" oncommand="Recheck();"
+ accesskey="&recheckButton2.accessKey;"/>
+ </row>
+ <row align="center">
+ <label id="ReplaceWordLabel" value="&wordEditField.label;"
+ control="ReplaceWordInput"
+ accesskey="&wordEditField.accessKey;"/>
+ <textbox id="ReplaceWordInput" oninput="ChangeReplaceWord()" flex="1"/>
+ <button id="CheckWord" oncommand="CheckWord()" label="&checkwordButton.label;"
+ accesskey="&checkwordButton.accessKey;"/>
+ </row>
+ </rows>
+ </grid>
+ <label id="SuggestedListLabel" value="&suggestions.label;"
+ control="SuggestedList"
+ accesskey="&suggestions.accessKey;"/>
+ <grid flex="1">
+ <columns><column flex="1"/><column/></columns>
+ <rows>
+ <row flex="1">
+ <!-- BUG! setting class="MinWidth20em" on tree doesn't work (width=0) -->
+ <richlistbox id="SuggestedList"
+ class="theme-listbox"
+ onselect="SelectSuggestedWord()"
+ ondblclick="if (gAllowSelectWord) { Replace(event.target.value); }"/>
+ <vbox>
+ <grid>
+ <columns><column class="spell-check" flex="1"/><column class="spell-check" flex="1"/></columns>
+ <rows>
+ <row>
+ <button id="Replace" label="&replaceButton.label;"
+ oncommand="Replace(gDialog.ReplaceWordInput.value);"
+ accesskey="&replaceButton.accessKey;"/>
+ <button id="Ignore" oncommand="Ignore();" label="&ignoreButton.label;"
+ accesskey="&ignoreButton.accessKey;"/>
+ </row>
+ <row>
+ <button id="ReplaceAll" oncommand="ReplaceAll();" label="&replaceAllButton.label;"
+ accesskey="&replaceAllButton.accessKey;"/>
+ <button id="IgnoreAll" oncommand="IgnoreAll();" label="&ignoreAllButton.label;"
+ accesskey="&ignoreAllButton.accessKey;"/>
+ </row>
+ </rows>
+ </grid>
+ <separator/>
+ <label value="&userDictionary.label;"/>
+ <hbox align="start">
+ <button class="spell-check" id="AddToDictionary" oncommand="AddToDictionary()" label="&addToUserDictionaryButton.label;"
+ accesskey="&addToUserDictionaryButton.accessKey;"/>
+ <button class="spell-check" id="EditDictionary" oncommand="EditDictionary()" label="&editUserDictionaryButton.label;"
+ accesskey="&editUserDictionaryButton.accessKey;"/>
+ </hbox>
+ </vbox>
+ </row>
+ <label value ="&languagePopup.label;"
+ control="LanguageMenulist"
+ accesskey="&languagePopup.accessKey;"/>
+ <row>
+ <menulist id="LanguageMenulist" oncommand="SelectLanguage()">
+ <menupopup onpopupshowing="InitLanguageMenu(gDialog.LanguageMenulist.selectedItem.value);">
+ <!-- dynamic content populated by JS -->
+ <menuseparator/>
+ <menuitem value="more-cmd" label="&moreDictionaries.label;"/>
+ </menupopup>
+ </menulist>
+ <hbox flex="1">
+ <button class="spell-check" dlgtype="cancel" id="Stop" label="&stopButton.label;" oncommand="CancelSpellCheck();"
+ accesskey="&stopButton.accessKey;"/>
+ <spacer flex="1"/>
+ <button class="spell-check" id="Close" label="&closeButton.label;" oncommand="onClose();"
+ accesskey="&closeButton.accessKey;"/>
+ <button class="spell-check" id="Send" label="&sendButton.label;" oncommand="onClose();"
+ accesskey="&sendButton.accessKey;" hidden="true"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdTableProps.js b/comm/suite/editor/components/dialogs/content/EdTableProps.js
new file mode 100644
index 0000000000..e78a89bc41
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdTableProps.js
@@ -0,0 +1,1439 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// Cancel() is in EdDialogCommon.js
+
+var gTableElement;
+var gCellElement;
+var gTableCaptionElement;
+var globalCellElement;
+var globalTableElement;
+var gValidateTab;
+const defHAlign = "left";
+const centerStr = "center"; // Index=1
+const rightStr = "right"; // 2
+const justifyStr = "justify"; // 3
+const charStr = "char"; // 4
+const defVAlign = "middle";
+const topStr = "top";
+const bottomStr = "bottom";
+const bgcolor = "bgcolor";
+var gTableColor;
+var gCellColor;
+
+const cssBackgroundColorStr = "background-color";
+
+var gRowCount = 1;
+var gColCount = 1;
+var gLastRowIndex;
+var gLastColIndex;
+var gNewRowCount;
+var gNewColCount;
+var gCurRowIndex;
+var gCurColIndex;
+var gCurColSpan;
+var gSelectedCellsType = 1;
+const SELECT_CELL = 1;
+const SELECT_ROW = 2;
+const SELECT_COLUMN = 3;
+const RESET_SELECTION = 0;
+var gCellData = {
+ value: null,
+ startRowIndex: 0,
+ startColIndex: 0,
+ rowSpan: 0,
+ colSpan: 0,
+ actualRowSpan: 0,
+ actualColSpan: 0,
+ isSelected: false,
+};
+var gAdvancedEditUsed;
+var gAlignWasChar = false;
+
+/*
+From C++:
+ 0 TABLESELECTION_TABLE
+ 1 TABLESELECTION_CELL There are 1 or more cells selected
+ but complete rows or columns are not selected
+ 2 TABLESELECTION_ROW All cells are in 1 or more rows
+ and in each row, all cells selected
+ Note: This is the value if all rows (thus all cells) are selected
+ 3 TABLESELECTION_COLUMN All cells are in 1 or more columns
+*/
+
+var gSelectedCellCount = 0;
+var gApplyUsed = false;
+var gSelection;
+var gCellDataChanged = false;
+var gCanDelete = false;
+var gUseCSS = true;
+var gActiveEditor;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogextra1", Apply);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ gActiveEditor = GetCurrentTableEditor();
+ if (!gActiveEditor) {
+ window.close();
+ return;
+ }
+
+ try {
+ gSelection = gActiveEditor.selection;
+ } catch (e) {}
+ if (!gSelection) {
+ return;
+ }
+
+ // Get dialog widgets - Table Panel
+ gDialog.TableRowsInput = document.getElementById("TableRowsInput");
+ gDialog.TableColumnsInput = document.getElementById("TableColumnsInput");
+ gDialog.TableWidthInput = document.getElementById("TableWidthInput");
+ gDialog.TableWidthUnits = document.getElementById("TableWidthUnits");
+ gDialog.TableHeightInput = document.getElementById("TableHeightInput");
+ gDialog.TableHeightUnits = document.getElementById("TableHeightUnits");
+ try {
+ if (
+ !Services.prefs.getBoolPref("editor.use_css") ||
+ gActiveEditor.flags & 1
+ ) {
+ gUseCSS = false;
+ var tableHeightLabel = document.getElementById("TableHeightLabel");
+ tableHeightLabel.remove();
+ gDialog.TableHeightInput.remove();
+ gDialog.TableHeightUnits.remove();
+ }
+ } catch (e) {}
+ gDialog.BorderWidthInput = document.getElementById("BorderWidthInput");
+ gDialog.SpacingInput = document.getElementById("SpacingInput");
+ gDialog.PaddingInput = document.getElementById("PaddingInput");
+ gDialog.TableAlignList = document.getElementById("TableAlignList");
+ gDialog.TableCaptionList = document.getElementById("TableCaptionList");
+ gDialog.TableInheritColor = document.getElementById("TableInheritColor");
+ gDialog.TabBox = document.getElementById("TabBox");
+
+ // Cell Panel
+ gDialog.SelectionList = document.getElementById("SelectionList");
+ gDialog.PreviousButton = document.getElementById("PreviousButton");
+ gDialog.NextButton = document.getElementById("NextButton");
+ // Currently, we always apply changes and load new attributes when changing selection
+ // (Let's keep this for possible future use)
+ // gDialog.ApplyBeforeMove = document.getElementById("ApplyBeforeMove");
+ // gDialog.KeepCurrentData = document.getElementById("KeepCurrentData");
+
+ gDialog.CellHeightInput = document.getElementById("CellHeightInput");
+ gDialog.CellHeightUnits = document.getElementById("CellHeightUnits");
+ gDialog.CellWidthInput = document.getElementById("CellWidthInput");
+ gDialog.CellWidthUnits = document.getElementById("CellWidthUnits");
+ gDialog.CellHAlignList = document.getElementById("CellHAlignList");
+ gDialog.CellVAlignList = document.getElementById("CellVAlignList");
+ gDialog.CellInheritColor = document.getElementById("CellInheritColor");
+ gDialog.CellStyleList = document.getElementById("CellStyleList");
+ gDialog.TextWrapList = document.getElementById("TextWrapList");
+
+ // In cell panel, user must tell us which attributes to apply via checkboxes,
+ // else we would apply values from one cell to ALL in selection
+ // and that's probably not what they expect!
+ gDialog.CellHeightCheckbox = document.getElementById("CellHeightCheckbox");
+ gDialog.CellWidthCheckbox = document.getElementById("CellWidthCheckbox");
+ gDialog.CellHAlignCheckbox = document.getElementById("CellHAlignCheckbox");
+ gDialog.CellVAlignCheckbox = document.getElementById("CellVAlignCheckbox");
+ gDialog.CellStyleCheckbox = document.getElementById("CellStyleCheckbox");
+ gDialog.TextWrapCheckbox = document.getElementById("TextWrapCheckbox");
+ gDialog.CellColorCheckbox = document.getElementById("CellColorCheckbox");
+ gDialog.TableTab = document.getElementById("TableTab");
+ gDialog.CellTab = document.getElementById("CellTab");
+ gDialog.AdvancedEditCell = document.getElementById("AdvancedEditButton2");
+ // Save "normal" tooltip message for Advanced Edit button
+ gDialog.AdvancedEditCellToolTipText = gDialog.AdvancedEditCell.getAttribute(
+ "tooltiptext"
+ );
+
+ try {
+ gTableElement = gActiveEditor.getElementOrParentByTagName("table", null);
+ } catch (e) {}
+ if (!gTableElement) {
+ dump("Failed to get table element!\n");
+ window.close();
+ return;
+ }
+ globalTableElement = gTableElement.cloneNode(false);
+
+ var tagNameObj = { value: "" };
+ var countObj = { value: 0 };
+ var tableOrCellElement;
+ try {
+ tableOrCellElement = gActiveEditor.getSelectedOrParentTableElement(
+ tagNameObj,
+ countObj
+ );
+ } catch (e) {}
+
+ if (tagNameObj.value == "td") {
+ // We are in a cell
+ gSelectedCellCount = countObj.value;
+ gCellElement = tableOrCellElement;
+ globalCellElement = gCellElement.cloneNode(false);
+
+ // Tells us whether cell, row, or column is selected
+ try {
+ gSelectedCellsType = gActiveEditor.getSelectedCellsType(gTableElement);
+ } catch (e) {}
+
+ // Ignore types except Cell, Row, and Column
+ if (
+ gSelectedCellsType < SELECT_CELL ||
+ gSelectedCellsType > SELECT_COLUMN
+ ) {
+ gSelectedCellsType = SELECT_CELL;
+ }
+
+ // Be sure at least 1 cell is selected.
+ // (If the count is 0, then we were inside the cell.)
+ if (gSelectedCellCount == 0) {
+ DoCellSelection();
+ }
+
+ // Get location in the cell map
+ var rowIndexObj = { value: 0 };
+ var colIndexObj = { value: 0 };
+ try {
+ gActiveEditor.getCellIndexes(gCellElement, rowIndexObj, colIndexObj);
+ } catch (e) {}
+ gCurRowIndex = rowIndexObj.value;
+ gCurColIndex = colIndexObj.value;
+
+ // We save the current colspan to quickly
+ // move selection from from cell to cell
+ if (GetCellData(gCurRowIndex, gCurColIndex)) {
+ gCurColSpan = gCellData.colSpan;
+ }
+
+ // Starting TabPanel name is passed in
+ if (window.arguments[1] == "CellPanel") {
+ gDialog.TabBox.selectedTab = gDialog.CellTab;
+ }
+ }
+
+ if (gDialog.TabBox.selectedTab == gDialog.TableTab) {
+ // We may call this with table selected, but no cell,
+ // so disable the Cell Properties tab
+ if (!gCellElement) {
+ // XXX: Disabling of tabs is currently broken, so for
+ // now we'll just remove the tab completely.
+ // gDialog.CellTab.disabled = true;
+ gDialog.CellTab.remove();
+ }
+ }
+
+ // Note: we must use gTableElement, not globalTableElement for these,
+ // thus we should not put this in InitDialog.
+ // Instead, monitor desired counts with separate globals
+ var rowCountObj = { value: 0 };
+ var colCountObj = { value: 0 };
+ try {
+ gActiveEditor.getTableSize(gTableElement, rowCountObj, colCountObj);
+ } catch (e) {}
+
+ gRowCount = rowCountObj.value;
+ gLastRowIndex = gRowCount - 1;
+ gColCount = colCountObj.value;
+ gLastColIndex = gColCount - 1;
+
+ // Set appropriate icons and enable state for the Previous/Next buttons
+ SetSelectionButtons();
+
+ // If only one cell in table, disable change-selection widgets
+ if (gRowCount == 1 && gColCount == 1) {
+ gDialog.SelectionList.setAttribute("disabled", "true");
+ }
+
+ // User can change these via textboxes
+ gNewRowCount = gRowCount;
+ gNewColCount = gColCount;
+
+ // This flag is used to control whether set check state
+ // on "set attribute" checkboxes
+ // (Advanced Edit dialog use calls InitDialog when done)
+ gAdvancedEditUsed = false;
+ InitDialog();
+ gAdvancedEditUsed = true;
+
+ // If first initializing, we really aren't changing anything
+ gCellDataChanged = false;
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ // Get Table attributes
+ gDialog.TableRowsInput.value = gRowCount;
+ gDialog.TableColumnsInput.value = gColCount;
+ gDialog.TableWidthInput.value = InitPixelOrPercentMenulist(
+ globalTableElement,
+ gTableElement,
+ "width",
+ "TableWidthUnits",
+ gPercent
+ );
+ if (gUseCSS) {
+ gDialog.TableHeightInput.value = InitPixelOrPercentMenulist(
+ globalTableElement,
+ gTableElement,
+ "height",
+ "TableHeightUnits",
+ gPercent
+ );
+ }
+ gDialog.BorderWidthInput.value = globalTableElement.border;
+ gDialog.SpacingInput.value = globalTableElement.cellSpacing;
+ gDialog.PaddingInput.value = globalTableElement.cellPadding;
+
+ var marginLeft = GetHTMLOrCSSStyleValue(
+ globalTableElement,
+ "align",
+ "margin-left"
+ );
+ var marginRight = GetHTMLOrCSSStyleValue(
+ globalTableElement,
+ "align",
+ "margin-right"
+ );
+ var halign = marginLeft.toLowerCase() + " " + marginRight.toLowerCase();
+ if (halign == "center center" || halign == "auto auto") {
+ gDialog.TableAlignList.value = "center";
+ } else if (halign == "right right" || halign == "auto 0px") {
+ gDialog.TableAlignList.value = "right";
+ } else {
+ // Default is left.
+ gDialog.TableAlignList.value = "left";
+ }
+
+ // Be sure to get caption from table in doc, not the copied "globalTableElement"
+ gTableCaptionElement = gTableElement.caption;
+ if (gTableCaptionElement) {
+ var align = GetHTMLOrCSSStyleValue(
+ gTableCaptionElement,
+ "align",
+ "caption-side"
+ );
+ if (align != "bottom" && align != "left" && align != "right") {
+ align = "top";
+ }
+ gDialog.TableCaptionList.value = align;
+ }
+
+ gTableColor = GetHTMLOrCSSStyleValue(
+ globalTableElement,
+ bgcolor,
+ cssBackgroundColorStr
+ );
+ gTableColor = ConvertRGBColorIntoHEXColor(gTableColor);
+ SetColor("tableBackgroundCW", gTableColor);
+
+ InitCellPanel();
+}
+
+function InitCellPanel() {
+ // Get cell attributes
+ if (globalCellElement) {
+ // This assumes order of items is Cell, Row, Column
+ gDialog.SelectionList.value = gSelectedCellsType;
+
+ var previousValue = gDialog.CellHeightInput.value;
+ gDialog.CellHeightInput.value = InitPixelOrPercentMenulist(
+ globalCellElement,
+ gCellElement,
+ "height",
+ "CellHeightUnits",
+ gPixel
+ );
+ gDialog.CellHeightCheckbox.checked =
+ gAdvancedEditUsed && previousValue != gDialog.CellHeightInput.value;
+
+ previousValue = gDialog.CellWidthInput.value;
+ gDialog.CellWidthInput.value = InitPixelOrPercentMenulist(
+ globalCellElement,
+ gCellElement,
+ "width",
+ "CellWidthUnits",
+ gPixel
+ );
+ gDialog.CellWidthCheckbox.checked =
+ gAdvancedEditUsed && previousValue != gDialog.CellWidthInput.value;
+
+ var previousIndex = gDialog.CellVAlignList.selectedIndex;
+ var valign = GetHTMLOrCSSStyleValue(
+ globalCellElement,
+ "valign",
+ "vertical-align"
+ ).toLowerCase();
+ if (valign == topStr || valign == bottomStr) {
+ gDialog.CellVAlignList.value = valign;
+ } else {
+ // Default is middle.
+ gDialog.CellVAlignList.value = defVAlign;
+ }
+
+ gDialog.CellVAlignCheckbox.checked =
+ gAdvancedEditUsed &&
+ previousIndex != gDialog.CellVAlignList.selectedIndex;
+
+ previousIndex = gDialog.CellHAlignList.selectedIndex;
+
+ gAlignWasChar = false;
+
+ var halign = GetHTMLOrCSSStyleValue(
+ globalCellElement,
+ "align",
+ "text-align"
+ ).toLowerCase();
+ switch (halign) {
+ case centerStr:
+ case rightStr:
+ case justifyStr:
+ gDialog.CellHAlignList.value = halign;
+ break;
+ case charStr:
+ // We don't support UI for this because layout doesn't work: bug 2212.
+ // Remember that's what they had so we don't change it
+ // unless they change the alignment by using the menulist
+ gAlignWasChar = true;
+ // Fall through to use show default alignment in menu
+ default:
+ // Default depends on cell type (TH is "center", TD is "left")
+ gDialog.CellHAlignList.value =
+ globalCellElement.nodeName.toLowerCase() == "th" ? "center" : "left";
+ break;
+ }
+
+ gDialog.CellHAlignCheckbox.checked =
+ gAdvancedEditUsed &&
+ previousIndex != gDialog.CellHAlignList.selectedIndex;
+
+ previousIndex = gDialog.CellStyleList.selectedIndex;
+ gDialog.CellStyleList.value = globalCellElement.nodeName.toLowerCase();
+ gDialog.CellStyleCheckbox.checked =
+ gAdvancedEditUsed && previousIndex != gDialog.CellStyleList.selectedIndex;
+
+ previousIndex = gDialog.TextWrapList.selectedIndex;
+ if (
+ GetHTMLOrCSSStyleValue(globalCellElement, "nowrap", "white-space") ==
+ "nowrap"
+ ) {
+ gDialog.TextWrapList.value = "nowrap";
+ } else {
+ gDialog.TextWrapList.value = "wrap";
+ }
+ gDialog.TextWrapCheckbox.checked =
+ gAdvancedEditUsed && previousIndex != gDialog.TextWrapList.selectedIndex;
+
+ previousValue = gCellColor;
+ gCellColor = GetHTMLOrCSSStyleValue(
+ globalCellElement,
+ bgcolor,
+ cssBackgroundColorStr
+ );
+ gCellColor = ConvertRGBColorIntoHEXColor(gCellColor);
+ SetColor("cellBackgroundCW", gCellColor);
+ gDialog.CellColorCheckbox.checked =
+ gAdvancedEditUsed && previousValue != gCellColor;
+
+ // We want to set this true in case changes came
+ // from Advanced Edit dialog session (must assume something changed)
+ gCellDataChanged = true;
+ }
+}
+
+function GetCellData(rowIndex, colIndex) {
+ // Get actual rowspan and colspan
+ var startRowIndexObj = { value: 0 };
+ var startColIndexObj = { value: 0 };
+ var rowSpanObj = { value: 0 };
+ var colSpanObj = { value: 0 };
+ var actualRowSpanObj = { value: 0 };
+ var actualColSpanObj = { value: 0 };
+ var isSelectedObj = { value: false };
+
+ try {
+ gActiveEditor.getCellDataAt(
+ gTableElement,
+ rowIndex,
+ colIndex,
+ gCellData,
+ startRowIndexObj,
+ startColIndexObj,
+ rowSpanObj,
+ colSpanObj,
+ actualRowSpanObj,
+ actualColSpanObj,
+ isSelectedObj
+ );
+ // We didn't find a cell
+ if (!gCellData.value) {
+ return false;
+ }
+ } catch (ex) {
+ return false;
+ }
+
+ gCellData.startRowIndex = startRowIndexObj.value;
+ gCellData.startColIndex = startColIndexObj.value;
+ gCellData.rowSpan = rowSpanObj.value;
+ gCellData.colSpan = colSpanObj.value;
+ gCellData.actualRowSpan = actualRowSpanObj.value;
+ gCellData.actualColSpan = actualColSpanObj.value;
+ gCellData.isSelected = isSelectedObj.value;
+ return true;
+}
+
+function SelectCellHAlign() {
+ SetCheckbox("CellHAlignCheckbox");
+ // Once user changes the alignment,
+ // we lose their original "CharAt" alignment"
+ gAlignWasChar = false;
+}
+
+function GetColorAndUpdate(ColorWellID) {
+ var colorWell = document.getElementById(ColorWellID);
+ if (!colorWell) {
+ return;
+ }
+
+ var colorObj = {
+ Type: "",
+ TableColor: 0,
+ CellColor: 0,
+ NoDefault: false,
+ Cancel: false,
+ BackgroundColor: 0,
+ };
+
+ switch (ColorWellID) {
+ case "tableBackgroundCW":
+ colorObj.Type = "Table";
+ colorObj.TableColor = gTableColor;
+ break;
+ case "cellBackgroundCW":
+ colorObj.Type = "Cell";
+ colorObj.CellColor = gCellColor;
+ break;
+ }
+ window.openDialog(
+ "chrome://editor/content/EdColorPicker.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ "",
+ colorObj
+ );
+
+ // User canceled the dialog
+ if (colorObj.Cancel) {
+ return;
+ }
+
+ switch (ColorWellID) {
+ case "tableBackgroundCW":
+ gTableColor = colorObj.BackgroundColor;
+ SetColor(ColorWellID, gTableColor);
+ break;
+ case "cellBackgroundCW":
+ gCellColor = colorObj.BackgroundColor;
+ SetColor(ColorWellID, gCellColor);
+ SetCheckbox("CellColorCheckbox");
+ break;
+ }
+}
+
+function SetColor(ColorWellID, color) {
+ // Save the color
+ if (ColorWellID == "cellBackgroundCW") {
+ if (color) {
+ try {
+ gActiveEditor.setAttributeOrEquivalent(
+ globalCellElement,
+ bgcolor,
+ color,
+ true
+ );
+ } catch (e) {}
+ gDialog.CellInheritColor.collapsed = true;
+ } else {
+ try {
+ gActiveEditor.removeAttributeOrEquivalent(
+ globalCellElement,
+ bgcolor,
+ true
+ );
+ } catch (e) {}
+ // Reveal addition message explaining "default" color
+ gDialog.CellInheritColor.collapsed = false;
+ }
+ } else {
+ if (color) {
+ try {
+ gActiveEditor.setAttributeOrEquivalent(
+ globalTableElement,
+ bgcolor,
+ color,
+ true
+ );
+ } catch (e) {}
+ gDialog.TableInheritColor.collapsed = true;
+ } else {
+ try {
+ gActiveEditor.removeAttributeOrEquivalent(
+ globalTableElement,
+ bgcolor,
+ true
+ );
+ } catch (e) {}
+ gDialog.TableInheritColor.collapsed = false;
+ }
+ SetCheckbox("CellColorCheckbox");
+ }
+
+ setColorWell(ColorWellID, color);
+}
+
+function ChangeSelectionToFirstCell() {
+ if (!GetCellData(0, 0)) {
+ dump("Can't find first cell in table!\n");
+ return;
+ }
+ gCellElement = gCellData.value;
+ globalCellElement = gCellElement;
+
+ gCurRowIndex = 0;
+ gCurColIndex = 0;
+ ChangeSelection(RESET_SELECTION);
+}
+
+function ChangeSelection(newType) {
+ newType = Number(newType);
+
+ if (gSelectedCellsType == newType) {
+ return;
+ }
+
+ if (newType == RESET_SELECTION) {
+ // Restore selection to existing focus cell
+ gSelection.collapse(gCellElement, 0);
+ } else {
+ gSelectedCellsType = newType;
+ }
+
+ // Keep the same focus gCellElement, just change the type
+ DoCellSelection();
+ SetSelectionButtons();
+
+ // Note: globalCellElement should still be a clone of gCellElement
+}
+
+function MoveSelection(forward) {
+ var newRowIndex = gCurRowIndex;
+ var newColIndex = gCurColIndex;
+ var inRow = false;
+
+ if (gSelectedCellsType == SELECT_ROW) {
+ newRowIndex += forward ? 1 : -1;
+
+ // Wrap around if before first or after last row
+ if (newRowIndex < 0) {
+ newRowIndex = gLastRowIndex;
+ } else if (newRowIndex > gLastRowIndex) {
+ newRowIndex = 0;
+ }
+ inRow = true;
+
+ // Use first cell in row for focus cell
+ newColIndex = 0;
+ } else {
+ // Cell or column:
+ if (!forward) {
+ newColIndex--;
+ }
+
+ if (gSelectedCellsType == SELECT_CELL) {
+ // Skip to next cell
+ if (forward) {
+ newColIndex += gCurColSpan;
+ }
+ } else {
+ // SELECT_COLUMN
+ // Use first cell in column for focus cell
+ newRowIndex = 0;
+
+ // Don't skip by colspan,
+ // but find first cell in next cellmap column
+ if (forward) {
+ newColIndex++;
+ }
+ }
+
+ if (newColIndex < 0) {
+ // Request is before the first cell in column
+
+ // Wrap to last cell in column
+ newColIndex = gLastColIndex;
+
+ if (gSelectedCellsType == SELECT_CELL) {
+ // If moving by cell, also wrap to previous...
+ if (newRowIndex > 0) {
+ newRowIndex -= 1;
+ } else {
+ // ...or the last row.
+ newRowIndex = gLastRowIndex;
+ }
+
+ inRow = true;
+ }
+ } else if (newColIndex > gLastColIndex) {
+ // Request is after the last cell in column
+
+ // Wrap to first cell in column
+ newColIndex = 0;
+
+ if (gSelectedCellsType == SELECT_CELL) {
+ // If moving by cell, also wrap to next...
+ if (newRowIndex < gLastRowIndex) {
+ newRowIndex++;
+ } else {
+ // ...or the first row.
+ newRowIndex = 0;
+ }
+
+ inRow = true;
+ }
+ }
+ }
+
+ // Get the cell at the new location
+ do {
+ if (!GetCellData(newRowIndex, newColIndex)) {
+ dump("MoveSelection: CELL NOT FOUND\n");
+ return;
+ }
+ if (inRow) {
+ if (gCellData.startRowIndex == newRowIndex) {
+ break;
+ } else {
+ // Cell spans from a row above, look for the next cell in row.
+ newRowIndex += gCellData.actualRowSpan;
+ }
+ } else if (gCellData.startColIndex == newColIndex) {
+ break;
+ } else {
+ // Cell spans from a Col above, look for the next cell in column
+ newColIndex += gCellData.actualColSpan;
+ }
+ } while (true);
+
+ // Save data for current selection before changing
+ if (gCellDataChanged) {
+ // && gDialog.ApplyBeforeMove.checked)
+ if (!ValidateCellData()) {
+ return;
+ }
+
+ gActiveEditor.beginTransaction();
+ // Apply changes to all selected cells
+ ApplyCellAttributes();
+ gActiveEditor.endTransaction();
+
+ SetCloseButton();
+ }
+
+ // Set cell and other data for new selection
+ gCellElement = gCellData.value;
+
+ // Save globals for new current cell
+ gCurRowIndex = gCellData.startRowIndex;
+ gCurColIndex = gCellData.startColIndex;
+ gCurColSpan = gCellData.actualColSpan;
+
+ // Copy for new global cell
+ globalCellElement = gCellElement.cloneNode(false);
+
+ // Change the selection
+ DoCellSelection();
+
+ // Scroll page so new selection is visible
+ // Using SELECTION_ANCHOR_REGION makes the upper-left corner of first selected cell
+ // the point to bring into view.
+ try {
+ var selectionController = gActiveEditor.selectionController;
+ selectionController.scrollSelectionIntoView(
+ selectionController.SELECTION_NORMAL,
+ selectionController.SELECTION_ANCHOR_REGION,
+ true
+ );
+ } catch (e) {}
+
+ // Reinitialize dialog using new cell
+ // if (!gDialog.KeepCurrentData.checked)
+ // Setting this false unchecks all "set attributes" checkboxes
+ gAdvancedEditUsed = false;
+ InitCellPanel();
+ gAdvancedEditUsed = true;
+}
+
+function DoCellSelection() {
+ // Collapse selection into to the focus cell
+ // so editor uses that as start cell
+ gSelection.collapse(gCellElement, 0);
+
+ var tagNameObj = { value: "" };
+ var countObj = { value: 0 };
+ try {
+ switch (gSelectedCellsType) {
+ case SELECT_CELL:
+ gActiveEditor.selectTableCell();
+ break;
+ case SELECT_ROW:
+ gActiveEditor.selectTableRow();
+ break;
+ default:
+ gActiveEditor.selectTableColumn();
+ break;
+ }
+ // Get number of cells selected
+ gActiveEditor.getSelectedOrParentTableElement(tagNameObj, countObj);
+ } catch (e) {}
+
+ if (tagNameObj.value == "td") {
+ gSelectedCellCount = countObj.value;
+ } else {
+ gSelectedCellCount = 0;
+ }
+
+ // Currently, we can only allow advanced editing on ONE cell element at a time
+ // else we ignore CSS, JS, and HTML attributes not already in dialog
+ SetElementEnabled(gDialog.AdvancedEditCell, gSelectedCellCount == 1);
+
+ gDialog.AdvancedEditCell.setAttribute(
+ "tooltiptext",
+ gSelectedCellCount > 1
+ ? GetString("AdvancedEditForCellMsg")
+ : gDialog.AdvancedEditCellToolTipText
+ );
+}
+
+function SetSelectionButtons() {
+ if (gSelectedCellsType == SELECT_ROW) {
+ // Trigger CSS to set images of up and down arrows
+ gDialog.PreviousButton.setAttribute("type", "row");
+ gDialog.NextButton.setAttribute("type", "row");
+ } else {
+ // or images of left and right arrows
+ gDialog.PreviousButton.setAttribute("type", "col");
+ gDialog.NextButton.setAttribute("type", "col");
+ }
+ DisableSelectionButtons(
+ (gSelectedCellsType == SELECT_ROW && gRowCount == 1) ||
+ (gSelectedCellsType == SELECT_COLUMN && gColCount == 1) ||
+ (gRowCount == 1 && gColCount == 1)
+ );
+}
+
+function DisableSelectionButtons(disable) {
+ gDialog.PreviousButton.setAttribute("disabled", disable ? "true" : "false");
+ gDialog.NextButton.setAttribute("disabled", disable ? "true" : "false");
+}
+
+function SwitchToValidatePanel() {
+ if (gDialog.TabBox.selectedTab != gValidateTab) {
+ gDialog.TabBox.selectedTab = gValidateTab;
+ }
+}
+
+function SetAlign(listID, defaultValue, element, attName) {
+ var value = document.getElementById(listID).value;
+ if (value == defaultValue) {
+ try {
+ gActiveEditor.removeAttributeOrEquivalent(element, attName, true);
+ } catch (e) {}
+ } else {
+ try {
+ gActiveEditor.setAttributeOrEquivalent(element, attName, value, true);
+ } catch (e) {}
+ }
+}
+
+function ValidateTableData() {
+ gValidateTab = gDialog.TableTab;
+ gNewRowCount = Number(
+ ValidateNumber(gDialog.TableRowsInput, null, 1, gMaxRows, null, true, true)
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ gNewColCount = Number(
+ ValidateNumber(
+ gDialog.TableColumnsInput,
+ null,
+ 1,
+ gMaxColumns,
+ null,
+ true,
+ true
+ )
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ // If user is deleting any cells, get confirmation
+ // (This is a global to the dialog and we ask only once per dialog session)
+ if (!gCanDelete && (gNewRowCount < gRowCount || gNewColCount < gColCount)) {
+ if (
+ ConfirmWithTitle(
+ GetString("DeleteTableTitle"),
+ GetString("DeleteTableMsg"),
+ GetString("DeleteCells")
+ )
+ ) {
+ gCanDelete = true;
+ } else {
+ SetTextboxFocus(
+ gNewRowCount < gRowCount
+ ? gDialog.TableRowsInput
+ : gDialog.TableColumnsInput
+ );
+ return false;
+ }
+ }
+
+ ValidateNumber(
+ gDialog.TableWidthInput,
+ gDialog.TableWidthUnits,
+ 1,
+ gMaxTableSize,
+ globalTableElement,
+ "width"
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ if (gUseCSS) {
+ ValidateNumber(
+ gDialog.TableHeightInput,
+ gDialog.TableHeightUnits,
+ 1,
+ gMaxTableSize,
+ globalTableElement,
+ "height"
+ );
+ if (gValidationError) {
+ return false;
+ }
+ }
+
+ ValidateNumber(
+ gDialog.BorderWidthInput,
+ null,
+ 0,
+ gMaxPixels,
+ globalTableElement,
+ "border"
+ );
+ // TODO: Deal with "BORDER" without value issue
+ if (gValidationError) {
+ return false;
+ }
+
+ ValidateNumber(
+ gDialog.SpacingInput,
+ null,
+ 0,
+ gMaxPixels,
+ globalTableElement,
+ "cellspacing"
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ ValidateNumber(
+ gDialog.PaddingInput,
+ null,
+ 0,
+ gMaxPixels,
+ globalTableElement,
+ "cellpadding"
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ SetAlign("TableAlignList", defHAlign, globalTableElement, "align");
+
+ // Color is set on globalCellElement immediately
+ return true;
+}
+
+function ValidateCellData() {
+ gValidateTab = gDialog.CellTab;
+
+ if (gDialog.CellHeightCheckbox.checked) {
+ ValidateNumber(
+ gDialog.CellHeightInput,
+ gDialog.CellHeightUnits,
+ 1,
+ gMaxTableSize,
+ globalCellElement,
+ "height"
+ );
+ if (gValidationError) {
+ return false;
+ }
+ }
+
+ if (gDialog.CellWidthCheckbox.checked) {
+ ValidateNumber(
+ gDialog.CellWidthInput,
+ gDialog.CellWidthUnits,
+ 1,
+ gMaxTableSize,
+ globalCellElement,
+ "width"
+ );
+ if (gValidationError) {
+ return false;
+ }
+ }
+
+ if (gDialog.CellHAlignCheckbox.checked) {
+ var hAlign = gDialog.CellHAlignList.value;
+
+ // Horizontal alignment is complicated by "char" type
+ // We don't change current values if user didn't edit alignment
+ if (!gAlignWasChar) {
+ globalCellElement.removeAttribute(charStr);
+
+ // Always set "align" attribute,
+ // so the default "left" is effective in a cell
+ // when parent row has align set.
+ globalCellElement.setAttribute("align", hAlign);
+ }
+ }
+
+ if (gDialog.CellVAlignCheckbox.checked) {
+ // Always set valign (no default in 2nd param) so
+ // the default "middle" is effective in a cell
+ // when parent row has valign set.
+ SetAlign("CellVAlignList", "", globalCellElement, "valign");
+ }
+
+ if (gDialog.TextWrapCheckbox.checked) {
+ if (gDialog.TextWrapList.value == "nowrap") {
+ try {
+ gActiveEditor.setAttributeOrEquivalent(
+ globalCellElement,
+ "nowrap",
+ "nowrap",
+ true
+ );
+ } catch (e) {}
+ } else {
+ try {
+ gActiveEditor.removeAttributeOrEquivalent(
+ globalCellElement,
+ "nowrap",
+ true
+ );
+ } catch (e) {}
+ }
+ }
+
+ return true;
+}
+
+function ValidateData() {
+ var result;
+
+ // Validate current panel first
+ if (gDialog.TabBox.selectedTab == gDialog.TableTab) {
+ result = ValidateTableData();
+ if (result) {
+ result = ValidateCellData();
+ }
+ } else {
+ result = ValidateCellData();
+ if (result) {
+ result = ValidateTableData();
+ }
+ }
+ if (!result) {
+ return false;
+ }
+
+ // Set global element for AdvancedEdit
+ if (gDialog.TabBox.selectedTab == gDialog.TableTab) {
+ globalElement = globalTableElement;
+ } else {
+ globalElement = globalCellElement;
+ }
+
+ return true;
+}
+
+function ChangeCellTextbox(textboxID) {
+ // Filter input for just integers
+ forceInteger(textboxID);
+
+ if (gDialog.TabBox.selectedTab == gDialog.CellTab) {
+ gCellDataChanged = true;
+ }
+}
+
+// Call this when a textbox or menulist is changed
+// so the checkbox is automatically set
+function SetCheckbox(checkboxID) {
+ if (checkboxID && checkboxID.length > 0) {
+ // Set associated checkbox
+ document.getElementById(checkboxID).checked = true;
+ }
+ gCellDataChanged = true;
+}
+
+function ChangeIntTextbox(textboxID, checkboxID) {
+ // Filter input for just integers
+ forceInteger(textboxID);
+
+ // Set associated checkbox
+ SetCheckbox(checkboxID);
+}
+
+function CloneAttribute(destElement, srcElement, attr) {
+ var value = srcElement.getAttribute(attr);
+ // Use editor methods since we are always
+ // modifying a table in the document and
+ // we need transaction system for undo
+ try {
+ if (!value || value.length == 0) {
+ gActiveEditor.removeAttributeOrEquivalent(destElement, attr, false);
+ } else {
+ gActiveEditor.setAttributeOrEquivalent(destElement, attr, value, false);
+ }
+ } catch (e) {}
+}
+
+/* eslint-disable complexity */
+function ApplyTableAttributes() {
+ var newAlign = gDialog.TableCaptionList.value;
+ if (!newAlign) {
+ newAlign = "";
+ }
+
+ if (gTableCaptionElement) {
+ // Get current alignment
+ var align = GetHTMLOrCSSStyleValue(
+ gTableCaptionElement,
+ "align",
+ "caption-side"
+ ).toLowerCase();
+ // This is the default
+ if (!align) {
+ align = "top";
+ }
+
+ if (newAlign == "") {
+ // Remove existing caption
+ try {
+ gActiveEditor.deleteNode(gTableCaptionElement);
+ } catch (e) {}
+ gTableCaptionElement = null;
+ } else if (newAlign != align) {
+ try {
+ if (newAlign == "top") {
+ // This is default, so don't explicitly set it
+ gActiveEditor.removeAttributeOrEquivalent(
+ gTableCaptionElement,
+ "align",
+ false
+ );
+ } else {
+ gActiveEditor.setAttributeOrEquivalent(
+ gTableCaptionElement,
+ "align",
+ newAlign,
+ false
+ );
+ }
+ } catch (e) {}
+ }
+ } else if (newAlign != "") {
+ // Create and insert a caption:
+ try {
+ gTableCaptionElement = gActiveEditor.createElementWithDefaults("caption");
+ } catch (e) {}
+ if (gTableCaptionElement) {
+ if (newAlign != "top") {
+ gTableCaptionElement.setAttribute("align", newAlign);
+ }
+
+ // Insert it into the table - caption is always inserted as first child
+ try {
+ gActiveEditor.insertNode(gTableCaptionElement, gTableElement, 0);
+ } catch (e) {}
+
+ // Put selection back where it was
+ ChangeSelection(RESET_SELECTION);
+ }
+ }
+
+ var countDelta;
+ var foundCell;
+ var i;
+
+ if (gNewRowCount != gRowCount) {
+ countDelta = gNewRowCount - gRowCount;
+ if (gNewRowCount > gRowCount) {
+ // Append new rows
+ // Find first cell in last row
+ if (GetCellData(gLastRowIndex, 0)) {
+ try {
+ // Move selection to the last cell
+ gSelection.collapse(gCellData.value, 0);
+ // Insert new rows after it
+ gActiveEditor.insertTableRow(countDelta, true);
+ gRowCount = gNewRowCount;
+ gLastRowIndex = gRowCount - 1;
+ // Put selection back where it was
+ ChangeSelection(RESET_SELECTION);
+ } catch (ex) {
+ dump("FAILED TO FIND FIRST CELL IN LAST ROW\n");
+ }
+ }
+ } else if (gCanDelete) {
+ // Delete rows
+ // Find first cell starting in first row we delete
+ var firstDeleteRow = gRowCount + countDelta;
+ foundCell = false;
+ for (i = 0; i <= gLastColIndex; i++) {
+ if (!GetCellData(firstDeleteRow, i)) {
+ // We failed to find a cell.
+ break;
+ }
+
+ if (gCellData.startRowIndex == firstDeleteRow) {
+ foundCell = true;
+ break;
+ }
+ }
+ if (foundCell) {
+ try {
+ // Move selection to the cell we found
+ gSelection.collapse(gCellData.value, 0);
+ gActiveEditor.deleteTableRow(-countDelta);
+ gRowCount = gNewRowCount;
+ gLastRowIndex = gRowCount - 1;
+ if (gCurRowIndex > gLastRowIndex) {
+ // We are deleting our selection
+ // move it to start of table
+ ChangeSelectionToFirstCell();
+ } else {
+ // Put selection back where it was.
+ ChangeSelection(RESET_SELECTION);
+ }
+ } catch (ex) {
+ dump("FAILED TO FIND FIRST CELL IN LAST ROW\n");
+ }
+ }
+ }
+ }
+
+ if (gNewColCount != gColCount) {
+ countDelta = gNewColCount - gColCount;
+
+ if (gNewColCount > gColCount) {
+ // Append new columns
+ // Find last cell in first column
+ if (GetCellData(0, gLastColIndex)) {
+ try {
+ // Move selection to the last cell
+ gSelection.collapse(gCellData.value, 0);
+ gActiveEditor.insertTableColumn(countDelta, true);
+ gColCount = gNewColCount;
+ gLastColIndex = gColCount - 1;
+ // Restore selection
+ ChangeSelection(RESET_SELECTION);
+ } catch (ex) {
+ dump("FAILED TO FIND FIRST CELL IN LAST COLUMN\n");
+ }
+ }
+ } else if (gCanDelete) {
+ // Delete columns
+ var firstDeleteCol = gColCount + countDelta;
+ foundCell = false;
+ for (i = 0; i <= gLastRowIndex; i++) {
+ // Find first cell starting in first column we delete
+ if (!GetCellData(i, firstDeleteCol)) {
+ // We failed to find a cell.
+ break;
+ }
+
+ if (gCellData.startColIndex == firstDeleteCol) {
+ foundCell = true;
+ break;
+ }
+ }
+ if (foundCell) {
+ try {
+ // Move selection to the cell we found
+ gSelection.collapse(gCellData.value, 0);
+ gActiveEditor.deleteTableColumn(-countDelta);
+ gColCount = gNewColCount;
+ gLastColIndex = gColCount - 1;
+ if (gCurColIndex > gLastColIndex) {
+ ChangeSelectionToFirstCell();
+ } else {
+ ChangeSelection(RESET_SELECTION);
+ }
+ } catch (ex) {
+ dump("FAILED TO FIND FIRST CELL IN LAST ROW\n");
+ }
+ }
+ }
+ }
+
+ // Clone all remaining attributes to pick up
+ // anything changed by Advanced Edit Dialog
+ try {
+ gActiveEditor.cloneAttributes(gTableElement, globalTableElement);
+ } catch (e) {}
+}
+/* eslint-enable complexity */
+
+function ApplyCellAttributes() {
+ var rangeObj = { value: null };
+ var selectedCell;
+ try {
+ selectedCell = gActiveEditor.getFirstSelectedCell(rangeObj);
+ } catch (e) {}
+
+ if (!selectedCell) {
+ return;
+ }
+
+ if (gSelectedCellCount == 1) {
+ // When only one cell is selected, simply clone entire element,
+ // thus CSS and JS from Advanced edit is copied
+ try {
+ gActiveEditor.cloneAttributes(selectedCell, globalCellElement);
+ } catch (e) {}
+
+ if (gDialog.CellStyleCheckbox.checked) {
+ var currentStyleIndex =
+ selectedCell.nodeName.toLowerCase() == "th" ? 1 : 0;
+ if (gDialog.CellStyleList.selectedIndex != currentStyleIndex) {
+ // Switch cell types
+ // (replaces with new cell and copies attributes and contents)
+ try {
+ selectedCell = gActiveEditor.switchTableCellHeaderType(selectedCell);
+ } catch (e) {}
+ }
+ }
+ } else {
+ // Apply changes to all selected cells
+ // XXX THIS DOESN'T COPY ADVANCED EDIT CHANGES!
+ try {
+ while (selectedCell) {
+ ApplyAttributesToOneCell(selectedCell);
+ selectedCell = gActiveEditor.getNextSelectedCell(rangeObj);
+ }
+ } catch (e) {}
+ }
+ gCellDataChanged = false;
+}
+
+function ApplyAttributesToOneCell(destElement) {
+ if (gDialog.CellHeightCheckbox.checked) {
+ CloneAttribute(destElement, globalCellElement, "height");
+ }
+
+ if (gDialog.CellWidthCheckbox.checked) {
+ CloneAttribute(destElement, globalCellElement, "width");
+ }
+
+ if (gDialog.CellHAlignCheckbox.checked) {
+ CloneAttribute(destElement, globalCellElement, "align");
+ CloneAttribute(destElement, globalCellElement, charStr);
+ }
+
+ if (gDialog.CellVAlignCheckbox.checked) {
+ CloneAttribute(destElement, globalCellElement, "valign");
+ }
+
+ if (gDialog.TextWrapCheckbox.checked) {
+ CloneAttribute(destElement, globalCellElement, "nowrap");
+ }
+
+ if (gDialog.CellStyleCheckbox.checked) {
+ var newStyleIndex = gDialog.CellStyleList.selectedIndex;
+ var currentStyleIndex = destElement.nodeName.toLowerCase() == "th" ? 1 : 0;
+
+ if (newStyleIndex != currentStyleIndex) {
+ // Switch cell types
+ // (replaces with new cell and copies attributes and contents)
+ try {
+ destElement = gActiveEditor.switchTableCellHeaderType(destElement);
+ } catch (e) {}
+ }
+ }
+
+ if (gDialog.CellColorCheckbox.checked) {
+ CloneAttribute(destElement, globalCellElement, "bgcolor");
+ }
+}
+
+function SetCloseButton() {
+ // Change text on "Cancel" button after Apply is used
+ if (!gApplyUsed) {
+ document.documentElement.setAttribute(
+ "buttonlabelcancel",
+ document.documentElement.getAttribute("buttonlabelclose")
+ );
+ gApplyUsed = true;
+ }
+}
+
+function Apply() {
+ if (ValidateData()) {
+ gActiveEditor.beginTransaction();
+
+ ApplyTableAttributes();
+
+ // We may have just a table, so check for cell element
+ if (globalCellElement) {
+ ApplyCellAttributes();
+ }
+
+ gActiveEditor.endTransaction();
+
+ SetCloseButton();
+ return true;
+ }
+ return false;
+}
+
+function onAccept(event) {
+ // Do same as Apply and close window if ValidateData succeeded
+ var retVal = Apply();
+ if (retVal) {
+ SaveWindowLocation();
+ } else {
+ event.preventDefault();
+ }
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdTableProps.xhtml b/comm/suite/editor/components/dialogs/content/EdTableProps.xhtml
new file mode 100644
index 0000000000..30979acb4d
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdTableProps.xhtml
@@ -0,0 +1,287 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edTableProperties SYSTEM "chrome://editor/locale/EditorTableProperties.dtd">
+%edTableProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&tableWindow.title;"
+ id="tableDlg"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()"
+ buttons="accept,extra1,cancel"
+ buttonlabelclose="&closeButton.label;"
+ buttonlabelextra1="&applyButton.label;"
+ buttonaccesskeyextra1="&applyButton.accesskey;">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdTableProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <tabbox id="TabBox">
+ <tabs flex="1">
+ <tab id="TableTab" label="&tableTab.label;"/>
+ <tab id="CellTab" label="&cellTab.label;"/>
+ </tabs>
+ <tabpanels>
+
+ <!-- TABLE PANEL -->
+ <vbox>
+ <groupbox orient="horizontal">
+ <hbox class="groupbox-title">
+ <label class="header">&size.label;</label>
+ </hbox>
+ <grid>
+ <columns><column/><column/><column/><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label value="&tableRows.label;" accesskey="&tableRows.accessKey;" control="TableRowsInput"/>
+ <textbox class="narrow" id="TableRowsInput" oninput="forceInteger(this.id);"/>
+ <spring class="bigspacer"/>
+ <label value="&tableHeight.label;" accesskey="&tableHeight.accessKey;"
+ id="TableHeightLabel" control="TableHeightInput"/>
+ <textbox class="narrow" id="TableHeightInput" oninput="forceInteger(this.id);"/>
+ <menulist id="TableHeightUnits"/>
+ </row>
+ <row align="center">
+ <label value="&tableColumns.label;" accesskey="&tableColumns.accessKey;" control="TableColumnsInput"/>
+ <textbox class="narrow" id="TableColumnsInput" oninput="forceInteger(this.id);"/>
+ <spring class="bigspacer"/>
+ <label value="&tableWidth.label;" accesskey="&tableWidth.accessKey;" control="TableWidthInput"/>
+ <textbox class="narrow" id="TableWidthInput" oninput="forceInteger(this.id);"/>
+ <menulist id="TableWidthUnits"/>
+ </row>
+ </rows>
+ <!-- KEEP GRID LAYOUT here since we will be adding back support for table HEIGHT via CSS -->
+ </grid>
+ </groupbox>
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&tableBorderSpacing.label;</label>
+ </hbox>
+ <grid>
+ <columns><column/><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label control="BorderWidthInput"
+ value="&tableBorderWidth.label;"
+ accesskey="&tableBorderWidth.accessKey;"/>
+ <textbox class="narrow" id="BorderWidthInput" oninput="forceInteger(this.id);"/>
+ <label align="left" value="&pixels.label;"/>
+ </row>
+ <row align="center">
+ <label control="SpacingInput"
+ value="&tableSpacing.label;"
+ accesskey="&tableSpacing.accessKey;"/>
+ <textbox class="narrow" id="SpacingInput" oninput="forceInteger(this.id);"/>
+ <label value="&tablePxBetwCells.label;"/>
+ </row>
+ <row align="center">
+ <label control="PaddingInput"
+ value="&tablePadding.label;"
+ accesskey="&tablePadding.accessKey;"/>
+ <textbox class="narrow" id="PaddingInput" oninput="forceInteger(this.id);"/>
+ <label value="&tablePxBetwBrdrCellContent.label;"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+ <!-- Table Alignment and Caption -->
+ <hbox flex="1" align="center">
+ <label control="TableAlignList"
+ value="&tableAlignment.label;"
+ accesskey="&tableAlignment.accessKey;"/>
+ <menulist id="TableAlignList">
+ <menupopup>
+ <menuitem label="&AlignLeft.label;" value="left"/>
+ <menuitem label="&AlignCenter.label;" value="center"/>
+ <menuitem label="&AlignRight.label;" value="right"/>
+ </menupopup>
+ </menulist>
+ <spacer class="spacer"/>
+ <label control="TableCaptionList"
+ value="&tableCaption.label;"
+ accesskey="&tableCaption.accessKey;"/>
+ <menulist id="TableCaptionList">
+ <menupopup>
+ <menuitem label="&tableCaptionNone.label;" value=""/>
+ <menuitem label="&tableCaptionAbove.label;" value="top"/>
+ <menuitem label="&tableCaptionBelow.label;" value="bottom"/>
+ <menuitem label="&tableCaptionLeft.label;" value="left"/>
+ <menuitem label="&tableCaptionRight.label;" value="right"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ <separator class="groove"/>
+ <hbox align="center">
+ <label value="&backgroundColor.label;"/>
+ <button id="tableBackground" class="color-button" oncommand="GetColorAndUpdate('tableBackgroundCW');">
+ <spacer id="tableBackgroundCW" class="color-well"/>
+ </button>
+ <spacer class="spacer"/>
+ <label id="TableInheritColor" value="&tableInheritColor.label;" collapsed="true"/>
+ </hbox>
+ <separator class="groove"/>
+ <hbox flex="1" align="center">
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <spacer flex="1"/>
+ </vbox><!-- Table Panel -->
+
+ <!-- CELL PANEL -->
+ <vbox>
+ <groupbox orient="horizontal" align="center">
+ <hbox class="groupbox-title">
+ <label class="header">&cellSelection.label;</label>
+ </hbox>
+ <vbox>
+ <menulist id="SelectionList" oncommand="ChangeSelection(event.target.value)" flex="1">
+ <menupopup>
+ <!-- JS code assumes order is Cell, Row, Column -->
+ <menuitem label="&cellSelectCell.label;" value="1"/>
+ <menuitem label="&cellSelectRow.label;" value="2"/>
+ <menuitem label="&cellSelectColumn.label;" value="3"/>
+ </menupopup>
+ </menulist>
+ <hbox flex="1">
+ <button id="PreviousButton"
+ oncommand="MoveSelection(0)"
+ flex="1"
+ align="center">
+ <image/>
+ <label value="&cellSelectPrevious.label;"
+ accesskey="&cellSelectPrevious.accessKey;"
+ control="PreviousButton"/>
+ </button>
+ <button id="NextButton"
+ oncommand="MoveSelection(1)"
+ class="align-right"
+ flex="1"
+ align="center">
+ <image/>
+ <label value="&cellSelectNext.label;"
+ accesskey="&cellSelectNext.accessKey;"
+ control="NextButton"/>
+ </button>
+ </hbox>
+ </vbox>
+ <spacer class="bigspacer"/>
+ <description class="wrap" flex="1">&applyBeforeChange.label;</description>
+ </groupbox>
+ <hbox align="center">
+ <!-- cell size groupbox -->
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&size.label;</label>
+ </hbox>
+ <grid>
+ <columns><column/><column/><column flex="1"/></columns>
+ <rows>
+ <row align="center">
+ <checkbox id="CellHeightCheckbox" label="&tableHeight.label;" accesskey="&tableHeight.accessKey;"/>
+ <textbox class="narrow" id="CellHeightInput"
+ oninput="ChangeIntTextbox(this.id, 'CellHeightCheckbox');"/>
+ <menulist id="CellHeightUnits" oncommand="SetCheckbox('CellHeightCheckbox');"/>
+ </row>
+ <row align="center">
+ <checkbox id="CellWidthCheckbox" label="&tableWidth.label;" accesskey="&tableWidth.accessKey;"/>
+ <textbox class="narrow" id="CellWidthInput"
+ oninput="ChangeIntTextbox(this.id, 'CellWidthCheckbox');"/>
+ <menulist id="CellWidthUnits" oncommand="SetCheckbox('CellWidthCheckbox');"/>
+ </row>
+ </rows>
+ </grid>
+ <spacer class="bigspacer"/>
+ </groupbox>
+ <!-- Alignment -->
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&cellContentAlignment.label;</label>
+ </hbox>
+ <grid>
+ <columns><column/><column flex="1"/><column/></columns>
+ <rows>
+ <row align="center">
+ <checkbox id="CellVAlignCheckbox" label="&cellVertical.label;" accesskey="&cellVertical.accessKey;"/>
+ <menulist id="CellVAlignList" oncommand="SetCheckbox('CellVAlignCheckbox');">
+ <menupopup>
+ <menuitem label="&cellAlignTop.label;" value="top"/>
+ <menuitem label="&cellAlignMiddle.label;" value="middle"/>
+ <menuitem label="&cellAlignBottom.label;" value="bottom"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row align="center">
+ <checkbox id="CellHAlignCheckbox" label="&cellHorizontal.label;" accesskey="&cellHorizontal.accessKey;"/>
+ <menulist id="CellHAlignList" oncommand="SelectCellHAlign()">
+ <menupopup>
+ <menuitem label="&AlignLeft.label;" value="left"/>
+ <menuitem label="&AlignCenter.label;" value="center"/>
+ <menuitem label="&AlignRight.label;" value="right"/>
+ <menuitem label="&cellAlignJustify.label;" value="justify"/>
+ </menupopup>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+ </hbox>
+ <spacer class="spacer"/>
+ <hbox align="center">
+ <checkbox id="CellStyleCheckbox" label="&cellStyle.label;" accesskey="&cellStyle.accessKey;"/>
+ <menulist id="CellStyleList" oncommand="SetCheckbox('CellStyleCheckbox');">
+ <menupopup>
+ <menuitem label="&cellNormal.label;" value="td"/>
+ <menuitem label="&cellHeader.label;" value="th"/>
+ </menupopup>
+ </menulist>
+ <spacer class="bigspacer"/>
+ <checkbox id="TextWrapCheckbox" label="&cellTextWrap.label;" accesskey="&cellTextWrap.accessKey;"/>
+ <menulist id="TextWrapList" oncommand="SetCheckbox('TextWrapCheckbox');">
+ <menupopup>
+ <menuitem label="&cellWrap.label;" value="wrap"/>
+ <menuitem label="&cellNoWrap.label;" value="nowrap"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ <separator class="groove"/>
+ <hbox align="center">
+ <checkbox id="CellColorCheckbox" label="&backgroundColor.label;" accesskey="&backgroundColor.accessKey;"/>
+ <button class="color-button" oncommand="GetColorAndUpdate('cellBackgroundCW');">
+ <spacer id="cellBackgroundCW" class="color-well"/>
+ </button>
+ <spacer class="spacer"/>
+ <label id="CellInheritColor" value="&cellInheritColor.label;" collapsed="true"/>
+ </hbox>
+ <separator class="groove"/>
+ <hbox align="center">
+ <description class="wrap" flex="1" style="width: 1em">&cellUseCheckboxHelp.label;</description>
+ <button id="AdvancedEditButton2"
+ oncommand="onAdvancedEdit()"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <spacer flex="1"/>
+ </vbox><!-- Cell Panel -->
+ </tabpanels>
+ </tabbox>
+ <spacer class="spacer"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdTextAreaProps.js b/comm/suite/editor/components/dialogs/content/EdTextAreaProps.js
new file mode 100644
index 0000000000..da33ab60c9
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdTextAreaProps.js
@@ -0,0 +1,171 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var insertNew;
+var textareaElement;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ dump("Failed to get active editor!\n");
+ window.close();
+ return;
+ }
+
+ gDialog = {
+ accept: document.documentElement.getButton("accept"),
+ textareaName: document.getElementById("TextAreaName"),
+ textareaRows: document.getElementById("TextAreaRows"),
+ textareaCols: document.getElementById("TextAreaCols"),
+ textareaWrap: document.getElementById("TextAreaWrap"),
+ textareaReadOnly: document.getElementById("TextAreaReadOnly"),
+ textareaDisabled: document.getElementById("TextAreaDisabled"),
+ textareaTabIndex: document.getElementById("TextAreaTabIndex"),
+ textareaAccessKey: document.getElementById("TextAreaAccessKey"),
+ textareaValue: document.getElementById("TextAreaValue"),
+ MoreSection: document.getElementById("MoreSection"),
+ MoreFewerButton: document.getElementById("MoreFewerButton"),
+ };
+
+ // Get a single selected text area element
+ const kTagName = "textarea";
+ try {
+ textareaElement = editor.getSelectedElement(kTagName);
+ } catch (e) {}
+
+ if (textareaElement) {
+ // We found an element and don't need to insert one
+ insertNew = false;
+
+ gDialog.textareaValue.value = textareaElement.value;
+ } else {
+ insertNew = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+ try {
+ textareaElement = editor.createElementWithDefaults(kTagName);
+ } catch (e) {}
+
+ if (!textareaElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+ gDialog.textareaValue.value = GetSelectionAsText();
+ }
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = textareaElement.cloneNode(false);
+
+ InitDialog();
+
+ InitMoreFewer();
+
+ SetTextboxFocus(gDialog.textareaName);
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ gDialog.textareaName.value = globalElement.getAttribute("name");
+ gDialog.textareaRows.value = globalElement.getAttribute("rows");
+ gDialog.textareaCols.value = globalElement.getAttribute("cols");
+ gDialog.textareaWrap.value = GetHTMLOrCSSStyleValue(
+ globalElement,
+ "wrap",
+ "white-space"
+ );
+ gDialog.textareaReadOnly.checked = globalElement.hasAttribute("readonly");
+ gDialog.textareaDisabled.checked = globalElement.hasAttribute("disabled");
+ gDialog.textareaTabIndex.value = globalElement.getAttribute("tabindex");
+ gDialog.textareaAccessKey.value = globalElement.getAttribute("accesskey");
+ onInput();
+}
+
+function onInput() {
+ var disabled =
+ !gDialog.textareaName.value ||
+ !gDialog.textareaRows.value ||
+ !gDialog.textareaCols.value;
+ if (gDialog.accept.disabled != disabled) {
+ gDialog.accept.disabled = disabled;
+ }
+}
+
+function ValidateData() {
+ var attributes = {
+ name: gDialog.textareaName.value,
+ rows: gDialog.textareaRows.value,
+ cols: gDialog.textareaCols.value,
+ wrap: gDialog.textareaWrap.value,
+ tabindex: gDialog.textareaTabIndex.value,
+ accesskey: gDialog.textareaAccessKey.value,
+ };
+ var flags = {
+ readonly: gDialog.textareaReadOnly.checked,
+ disabled: gDialog.textareaDisabled.checked,
+ };
+ for (var a in attributes) {
+ if (attributes[a]) {
+ globalElement.setAttribute(a, attributes[a]);
+ } else {
+ globalElement.removeAttribute(a);
+ }
+ }
+ for (var f in flags) {
+ if (flags[f]) {
+ globalElement.setAttribute(f, "");
+ } else {
+ globalElement.removeAttribute(f);
+ }
+ }
+ return true;
+}
+
+function onAccept() {
+ // All values are valid - copy to actual element in doc or
+ // element created to insert
+ ValidateData();
+
+ var editor = GetCurrentEditor();
+
+ editor.beginTransaction();
+
+ try {
+ editor.cloneAttributes(textareaElement, globalElement);
+
+ if (insertNew) {
+ editor.insertElementAtSelection(textareaElement, true);
+ }
+
+ // undoably set value
+ var initialText = gDialog.textareaValue.value;
+ if (initialText != textareaElement.value) {
+ editor.setShouldTxnSetSelection(false);
+
+ while (textareaElement.hasChildNodes()) {
+ editor.deleteNode(textareaElement.lastChild);
+ }
+ if (initialText) {
+ var textNode = editor.document.createTextNode(initialText);
+ editor.insertNode(textNode, textareaElement, 0);
+ }
+
+ editor.setShouldTxnSetSelection(true);
+ }
+ } finally {
+ editor.endTransaction();
+ }
+
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdTextAreaProps.xhtml b/comm/suite/editor/components/dialogs/content/EdTextAreaProps.xhtml
new file mode 100644
index 0000000000..9ab91664e8
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdTextAreaProps.xhtml
@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edTextAreaProperties SYSTEM "chrome://editor/locale/EditorTextAreaProperties.dtd">
+%edTextAreaProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ buttons="accept,cancel">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdTextAreaProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&Settings.label;</label>
+ </hbox>
+ <grid><columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label control="TextAreaName" value="&TextAreaName.label;" accesskey="&TextAreaName.accessKey;"/>
+ <textbox id="TextAreaName" oninput="onInput();"/>
+ </row>
+ <row align="center">
+ <label control="TextAreaRows" value="&TextAreaRows.label;" accesskey="&TextAreaRows.accessKey;"/>
+ <hbox>
+ <textbox id="TextAreaRows" class="narrow" oninput="forceInteger(this.id); onInput();"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="TextAreaCols" value="&TextAreaCols.label;" accesskey="&TextAreaCols.accessKey;"/>
+ <hbox>
+ <textbox id="TextAreaCols" class="narrow" oninput="forceInteger(this.id); onInput();"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ <hbox>
+ <button id="MoreFewerButton" oncommand="onMoreFewer();" persist="more"/>
+ </hbox>
+ <grid id="MoreSection"><columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label control="TextAreaWrap" value="&TextAreaWrap.label;" accesskey="&TextAreaWrap.accessKey;"/>
+ <menulist id="TextAreaWrap">
+ <menupopup>
+ <menuitem label="&WrapDefault.value;"/>
+ <menuitem label="&WrapOff.value;" value="off"/>
+ <menuseparator/>
+ <menuitem label="&WrapSoft.value;" value="soft"/>
+ <menuitem label="&WrapHard.value;" value="hard"/>
+ <menuseparator/>
+ <menuitem label="&WrapPhysical.value;" value="physical"/>
+ <menuitem label="&WrapVirtual.value;" value="virtual"/>
+ <menuseparator/>
+ <menuitem label="normal" value="normal"/>
+ <menuitem label="nowrap" value="nowrap"/>
+ <menuitem label="pre" value="pre"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row>
+ <spacer/>
+ <checkbox id="TextAreaReadOnly" label="&TextAreaReadOnly.label;" accesskey="&TextAreaReadOnly.accessKey;"/>
+ </row>
+ <row>
+ <spacer/>
+ <checkbox id="TextAreaDisabled" label="&TextAreaDisabled.label;" accesskey="&TextAreaDisabled.accessKey;"/>
+ </row>
+ <row align="center">
+ <label control="TextAreaTabIndex" value="&TextAreaTabIndex.label;" accesskey="&TextAreaTabIndex.accessKey;"/>
+ <hbox>
+ <textbox id="TextAreaTabIndex" class="narrow" oninput="forceInteger(this.id);"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="TextAreaAccessKey" value="&TextAreaAccessKey.label;" accesskey="&TextAreaAccessKey.accessKey;"/>
+ <hbox>
+ <textbox id="TextAreaAccessKey" class="narrow" maxlength="1"/>
+ </hbox>
+ </row>
+ <row>
+ <label control="TextAreaValue" value="&InitialText.label;" accesskey="&InitialText.accessKey;"/>
+ </row>
+ <html:textarea id="TextAreaValue" flex="1" rows="5"/>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <vbox id="AdvancedEdit">
+ <hbox flex="1" style="margin-top: 0.2em" align="center">
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator id="advancedSeparator" class="groove"/>
+ </vbox>
+
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EditConflict.js b/comm/suite/editor/components/dialogs/content/EditConflict.js
new file mode 100644
index 0000000000..28611796cd
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditConflict.js
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// dialog initialization code
+
+document.addEventListener("dialogcancel", onClose);
+
+function Startup()
+{
+ if (!GetCurrentEditor())
+ {
+ window.close();
+ return;
+ }
+
+ SetWindowLocation();
+}
+
+function KeepCurrentPage()
+{
+ // Simply close dialog and don't change current page
+ //TODO: Should we force saving of the current page?
+ SaveWindowLocation();
+ return true;
+}
+
+function UseOtherPage()
+{
+ // Reload the URL -- that will get other editor's contents
+ window.opener.setTimeout(window.opener.EditorLoadUrl, 0, GetDocumentUrl());
+ SaveWindowLocation();
+ return true;
+}
+
+function PreventCancel()
+{
+ SaveWindowLocation();
+
+ // Don't let Esc key close the dialog!
+ return false;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EditConflict.xhtml b/comm/suite/editor/components/dialogs/content/EditConflict.xhtml
new file mode 100644
index 0000000000..fce32792d2
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditConflict.xhtml
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditConflict.dtd">
+
+<dialog buttons="cancel" title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EditConflict.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <label value ="&conflictWarning.label;"/>
+ <spacer class="bigspacer"/>
+ <label value ="&conflictResolve.label;"/>
+ <spacer class="bigspacer"/>
+ <hbox flex="1">
+ <spacer class="bigspacer"/>
+ <button label="&keepCurrentPageButton.label;"
+ oncommand="KeepCurrentPage()"/>
+ <spacer class="bigspacer"/>
+ </hbox>
+ <hbox flex="1">
+ <spacer class="bigspacer"/>
+ <button dlgtype="cancel"
+ label="&useOtherPageButton.label;"
+ oncommand="UseOtherPage()"/>
+ <spacer class="bigspacer"/>
+ </hbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EditorPublish.js b/comm/suite/editor/components/dialogs/content/EditorPublish.js
new file mode 100644
index 0000000000..6947a439ee
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorPublish.js
@@ -0,0 +1,558 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gPublishSiteData;
+var gReturnData;
+var gDefaultSiteIndex = -1;
+var gDefaultSiteName;
+var gPreviousDefaultDir;
+var gPreviousTitle;
+var gSettingsChanged = false;
+var gInitialSiteName;
+var gInitialSiteIndex = -1;
+var gPasswordManagerOn = true;
+
+// Dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup()
+{
+ window.opener.ok = false;
+
+ // Element to edit is passed in
+ gInitialSiteName = window.arguments[1];
+ gReturnData = window.arguments[2];
+ if (!gReturnData || !GetCurrentEditor())
+ {
+ dump("Publish: No editor or return data object not supplied\n");
+ window.close();
+ return;
+ }
+
+ gDialog.TabBox = document.getElementById("TabBox");
+ gDialog.PublishTab = document.getElementById("PublishTab");
+ gDialog.SettingsTab = document.getElementById("SettingsTab");
+
+ // Publish panel
+ gDialog.PageTitleInput = document.getElementById("PageTitleInput");
+ gDialog.FilenameInput = document.getElementById("FilenameInput");
+ gDialog.SiteList = document.getElementById("SiteList");
+ gDialog.DocDirList = document.getElementById("DocDirList");
+ gDialog.OtherDirCheckbox = document.getElementById("OtherDirCheckbox");
+ gDialog.OtherDirRadiogroup = document.getElementById("OtherDirRadiogroup");
+ gDialog.SameLocationRadio = document.getElementById("SameLocationRadio");
+ gDialog.UseSubdirRadio = document.getElementById("UseSubdirRadio");
+ gDialog.OtherDirList = document.getElementById("OtherDirList");
+
+ // Settings Panel
+ gDialog.SiteNameInput = document.getElementById("SiteNameInput");
+ gDialog.PublishUrlInput = document.getElementById("PublishUrlInput");
+ gDialog.BrowseUrlInput = document.getElementById("BrowseUrlInput");
+ gDialog.UsernameInput = document.getElementById("UsernameInput");
+ gDialog.PasswordInput = document.getElementById("PasswordInput");
+ gDialog.SavePassword = document.getElementById("SavePassword");
+
+ gPasswordManagerOn = Services.prefs.getBoolPref("signon.rememberSignons");
+ gDialog.SavePassword.disabled = !gPasswordManagerOn;
+
+ gPublishSiteData = GetPublishSiteData();
+ gDefaultSiteName = GetDefaultPublishSiteName();
+
+ var addNewSite = false;
+ if (gPublishSiteData)
+ {
+ FillSiteList();
+ }
+ else
+ {
+ // No current site data, start a new item in the Settings panel
+ AddNewSite();
+ addNewSite = true;
+ }
+
+ var docUrl = GetDocumentUrl();
+ var scheme = GetScheme(docUrl);
+ var filename = "";
+
+ if (scheme)
+ {
+ filename = GetFilename(docUrl);
+
+ if (scheme != "file")
+ {
+ var siteFound = false;
+
+ // Editing a remote URL.
+ // Attempt to find doc URL in Site Data
+ if (gPublishSiteData)
+ {
+ var dirObj = {};
+ var siteIndex = FindSiteIndexAndDocDir(gPublishSiteData, docUrl, dirObj);
+
+ // Select this site only if the same as user's intended site, or there wasn't one
+ if (siteIndex != -1 && (gInitialSiteIndex == -1 || siteIndex == gInitialSiteIndex))
+ {
+ siteFound = true;
+
+ // Select the site we found
+ gDialog.SiteList.selectedIndex = siteIndex;
+ var docDir = dirObj.value;
+
+ // Use the directory within site in the editable menulist
+ gPublishSiteData[siteIndex].docDir = docDir;
+
+ //XXX HOW DO WE DECIDE WHAT "OTHER" DIR TO USE?
+ //gPublishSiteData[siteIndex].otherDir = docDir;
+ }
+ }
+ if (!siteFound)
+ {
+ // Not found in site database
+ // Setup for a new site and use data from a remote URL
+ if (!addNewSite)
+ AddNewSite();
+
+ addNewSite = true;
+
+ var publishData = CreatePublishDataFromUrl(docUrl);
+ if (publishData)
+ {
+ filename = publishData.filename;
+ gDialog.SiteNameInput.value = publishData.siteName;
+ gDialog.PublishUrlInput.value = publishData.publishUrl;
+ gDialog.BrowseUrlInput.value = publishData.browseUrl;
+ gDialog.UsernameInput.value = publishData.username;
+ gDialog.PasswordInput.value = publishData.password;
+ gDialog.SavePassword.checked = false;
+ }
+ }
+ }
+ }
+ try {
+ gPreviousTitle = GetDocumentTitle();
+ } catch (e) {}
+
+ gDialog.PageTitleInput.value = gPreviousTitle;
+ gDialog.FilenameInput.value = decodeURIComponent(filename);
+
+ if (!addNewSite)
+ {
+ // If not adding a site and we haven't selected a site -- use initial or default site
+ if (gDialog.SiteList.selectedIndex == -1)
+ gDialog.SiteList.selectedIndex = (gInitialSiteIndex != -1) ? gInitialSiteIndex : gDefaultSiteIndex;
+
+ // Fill in all the site data for currently-selected site
+ SelectSiteList();
+ SetTextboxFocus(gDialog.PageTitleInput);
+ }
+
+ if (gDialog.SiteList.selectedIndex == -1)
+ {
+ // No selected site -- assume same directory
+ gDialog.OtherDirRadiogroup.selectedItem = gDialog.SameLocationRadio;
+ }
+ else if (gPublishSiteData[gDialog.SiteList.selectedIndex].docDir ==
+ gPublishSiteData[gDialog.SiteList.selectedIndex].otherDir)
+ {
+ // For now, check "same location" if dirs are already set to same directory
+ gDialog.OtherDirRadiogroup.selectedItem = gDialog.SameLocationRadio;
+ }
+ else
+ {
+ gDialog.OtherDirRadiogroup.selectedItem = gDialog.UseSubdirRadio;
+ }
+
+ doEnabling();
+
+ SetWindowLocation();
+}
+
+function FillSiteList()
+{
+ gDialog.SiteList.removeAllItems();
+ gDefaultSiteIndex = -1;
+
+ // Fill the site lists
+ var count = gPublishSiteData.length;
+ var i;
+
+ for (i = 0; i < count; i++)
+ {
+ var name = gPublishSiteData[i].siteName;
+ var menuitem = gDialog.SiteList.appendItem(name);
+ // Highlight the default site
+ if (name == gDefaultSiteName)
+ {
+ gDefaultSiteIndex = i;
+ if (menuitem)
+ {
+ menuitem.setAttribute("class", "menuitem-highlight-1");
+ menuitem.setAttribute("default", "true");
+ }
+ }
+ // Find initial site location
+ if (name == gInitialSiteName)
+ gInitialSiteIndex = i;
+ }
+}
+
+function doEnabling()
+{
+ var disableOther = !gDialog.OtherDirCheckbox.checked;
+ gDialog.SameLocationRadio.disabled = disableOther;
+ gDialog.UseSubdirRadio.disabled = disableOther;
+ gDialog.OtherDirList.disabled = (disableOther || gDialog.SameLocationRadio.selected);
+}
+
+function SelectSiteList()
+{
+ var selectedSiteIndex = gDialog.SiteList.selectedIndex;
+
+ var siteName = "";
+ var publishUrl = "";
+ var browseUrl = "";
+ var username = "";
+ var password = "";
+ var savePassword = false;
+ var publishOtherFiles = true;
+
+ gDialog.DocDirList.removeAllItems();
+ gDialog.OtherDirList.removeAllItems();
+
+ if (gPublishSiteData && selectedSiteIndex != -1)
+ {
+ siteName = gPublishSiteData[selectedSiteIndex].siteName;
+ publishUrl = gPublishSiteData[selectedSiteIndex].publishUrl;
+ browseUrl = gPublishSiteData[selectedSiteIndex].browseUrl;
+ username = gPublishSiteData[selectedSiteIndex].username;
+ savePassword = gPasswordManagerOn ? gPublishSiteData[selectedSiteIndex].savePassword : false;
+ if (savePassword)
+ password = gPublishSiteData[selectedSiteIndex].password;
+
+ // Fill the directory menulists
+ if (gPublishSiteData[selectedSiteIndex].dirList.length)
+ {
+ for (var i = 0; i < gPublishSiteData[selectedSiteIndex].dirList.length; i++)
+ {
+ gDialog.DocDirList.appendItem(gPublishSiteData[selectedSiteIndex].dirList[i]);
+ gDialog.OtherDirList.appendItem(gPublishSiteData[selectedSiteIndex].dirList[i]);
+ }
+ }
+ gDialog.DocDirList.value = FormatDirForPublishing(gPublishSiteData[selectedSiteIndex].docDir);
+ gDialog.OtherDirList.value = FormatDirForPublishing(gPublishSiteData[selectedSiteIndex].otherDir);
+ publishOtherFiles = gPublishSiteData[selectedSiteIndex].publishOtherFiles;
+
+ }
+ else
+ {
+ gDialog.DocDirList.value = "";
+ gDialog.OtherDirList.value = "";
+ }
+
+ gDialog.SiteNameInput.value = siteName;
+ gDialog.PublishUrlInput.value = publishUrl;
+ gDialog.BrowseUrlInput.value = browseUrl;
+ gDialog.UsernameInput.value = username;
+ gDialog.PasswordInput.value = password;
+ gDialog.SavePassword.checked = savePassword;
+ gDialog.OtherDirCheckbox.checked = publishOtherFiles;
+
+ doEnabling();
+}
+
+function AddNewSite()
+{
+ // Button in Publish panel allows user
+ // to automatically switch to "Settings" panel
+ // to enter data for new site
+ SwitchPanel(gDialog.SettingsTab);
+
+ gDialog.SiteList.selectedIndex = -1;
+
+ SelectSiteList();
+
+ gSettingsChanged = true;
+
+ SetTextboxFocus(gDialog.SiteNameInput);
+}
+
+function SelectPublishTab()
+{
+ if (gSettingsChanged && !ValidateSettings())
+ return;
+
+ SwitchPanel(gDialog.PublishTab);
+ SetTextboxFocus(gDialog.PageTitleInput);
+}
+
+function SelectSettingsTab()
+{
+ SwitchPanel(gDialog.SettingsTab);
+ SetTextboxFocus(gDialog.SiteNameInput);
+}
+
+function SwitchPanel(tab)
+{
+ if (gDialog.TabBox.selectedTab != tab)
+ gDialog.TabBox.selectedTab = tab;
+}
+
+function onInputSettings()
+{
+ // TODO: Save current data during SelectSite and compare here
+ // to detect if real change has occurred?
+ gSettingsChanged = true;
+}
+
+function GetPublishUrlInput()
+{
+ gDialog.PublishUrlInput.value = FormatUrlForPublishing(gDialog.PublishUrlInput.value);
+ return gDialog.PublishUrlInput.value;
+}
+
+function GetBrowseUrlInput()
+{
+ gDialog.BrowseUrlInput.value = FormatUrlForPublishing(gDialog.BrowseUrlInput.value);
+ return gDialog.BrowseUrlInput.value;
+}
+
+function GetDocDirInput()
+{
+ gDialog.DocDirList.value = FormatDirForPublishing(gDialog.DocDirList.value);
+ return gDialog.DocDirList.value;
+}
+
+function GetOtherDirInput()
+{
+ gDialog.OtherDirList.value = FormatDirForPublishing(gDialog.OtherDirList.value);
+ return gDialog.OtherDirList.value;
+}
+
+function ChooseDir(menulist)
+{
+ //TODO: For FTP publish destinations, get file listing of just dirs
+ // and build a tree to let user select dir
+}
+
+function ValidateSettings()
+{
+ var siteName = TrimString(gDialog.SiteNameInput.value);
+ if (!siteName)
+ {
+ ShowErrorInPanel(gDialog.SettingsTab, "MissingSiteNameError", gDialog.SiteNameInput);
+ return false;
+ }
+ if (PublishSiteNameExists(siteName, gPublishSiteData, gDialog.SiteList.selectedIndex))
+ {
+ SwitchPanel(gDialog.SettingsTab);
+ ShowInputErrorMessage(GetString("DuplicateSiteNameError").replace(/%name%/, siteName));
+ SetTextboxFocus(gDialog.SiteNameInput);
+ return false;
+ }
+
+ // Extract username and password while removing them from publishingUrl
+ var urlUserObj = {};
+ var urlPassObj = {};
+ var publishUrl = StripUsernamePassword(gDialog.PublishUrlInput.value, urlUserObj, urlPassObj);
+ if (publishUrl)
+ {
+ publishUrl = FormatUrlForPublishing(publishUrl);
+
+ // Assume scheme = "ftp://" if missing
+ // This compensates when user enters hostname w/o scheme (as most ISPs provide)
+ if (!GetScheme(publishUrl))
+ publishUrl = "ftp://" + publishUrl;
+
+ gDialog.PublishUrlInput.value = publishUrl;
+ }
+ else
+ {
+ ShowErrorInPanel(gDialog.SettingsTab, "MissingPublishUrlError", gDialog.PublishUrlInput);
+ return false;
+ }
+ var browseUrl = GetBrowseUrlInput();
+
+ var username = TrimString(gDialog.UsernameInput.value);
+ var savePassword = gDialog.SavePassword.checked;
+ var password = gDialog.PasswordInput.value;
+ var publishOtherFiles = gDialog.OtherDirCheckbox.checked;
+
+ //XXX If there was a username and/or password in the publishUrl
+ // AND in the input field, which do we use?
+ // Let's use those in url only if input is empty
+ if (!username)
+ {
+ username = urlUserObj.value;
+ gDialog.UsernameInput.value = username;
+ gSettingsChanged = true;
+ }
+ if (!password)
+ {
+ password = urlPassObj.value;
+ gDialog.PasswordInput.value = password;
+ gSettingsChanged = true;
+ }
+
+ // Update or add data for a site
+ var siteIndex = gDialog.SiteList.selectedIndex;
+ var newSite = false;
+
+ if (siteIndex == -1)
+ {
+ // No site is selected, add a new site at the end
+ if (gPublishSiteData)
+ {
+ siteIndex = gPublishSiteData.length;
+ }
+ else
+ {
+ // First time: start entire site array
+ gPublishSiteData = new Array(1);
+ siteIndex = 0;
+ gDefaultSiteIndex = 0;
+ gDefaultSiteName = siteName;
+ }
+ gPublishSiteData[siteIndex] = {};
+ gPublishSiteData[siteIndex].docDir = "";
+ gPublishSiteData[siteIndex].otherDir = "";
+ gPublishSiteData[siteIndex].dirList = [""];
+ gPublishSiteData[siteIndex].publishOtherFiles = true;
+ gPublishSiteData[siteIndex].previousSiteName = siteName;
+ newSite = true;
+ }
+ gPublishSiteData[siteIndex].siteName = siteName;
+ gPublishSiteData[siteIndex].publishUrl = publishUrl;
+ gPublishSiteData[siteIndex].browseUrl = browseUrl;
+ gPublishSiteData[siteIndex].username = username;
+ // Don't save password in data that will be saved in prefs
+ gPublishSiteData[siteIndex].password = savePassword ? password : "";
+ gPublishSiteData[siteIndex].savePassword = savePassword;
+
+ if (publishOtherFiles != gPublishSiteData[siteIndex].publishOtherFiles)
+ gSettingsChanged = true;
+
+ gPublishSiteData[siteIndex].publishOtherFiles = publishOtherFiles;
+
+ gDialog.SiteList.selectedIndex = siteIndex;
+ if (siteIndex == gDefaultSiteIndex)
+ gDefaultSiteName = siteName;
+
+ // Should never be empty, but be sure we have a default site
+ if (!gDefaultSiteName)
+ {
+ gDefaultSiteName = gPublishSiteData[0].siteName;
+ gDefaultSiteIndex = 0;
+ }
+
+ // Rebuild the site menulist if we added a new site
+ if (newSite)
+ {
+ FillSiteList();
+ gDialog.SiteList.selectedIndex = siteIndex;
+ }
+ else
+ {
+ // Update selected item if sitename changed
+ var selectedItem = gDialog.SiteList.selectedItem;
+ if (selectedItem)
+ {
+ var oldName = selectedItem.getAttribute("label");
+ if (oldName != siteName)
+ {
+ selectedItem.setAttribute("label", siteName);
+ gDialog.SiteList.setAttribute("label", siteName);
+ gSettingsChanged = true;
+ if (oldName == gDefaultSiteName)
+ gDefaultSiteName = siteName;
+ }
+ }
+ }
+
+ // Get the directory name in site to publish to
+ var docDir = GetDocDirInput();
+
+ gPublishSiteData[siteIndex].docDir = docDir;
+
+ // And directory for images and other files
+ var otherDir = GetOtherDirInput();
+ if (gDialog.SameLocationRadio.selected)
+ otherDir = docDir;
+ else
+ otherDir = GetOtherDirInput();
+
+ gPublishSiteData[siteIndex].otherDir = otherDir;
+
+ // Fill return data object
+ gReturnData.siteName = siteName;
+ gReturnData.previousSiteName = gPublishSiteData[siteIndex].previousSiteName;
+ gReturnData.publishUrl = publishUrl;
+ gReturnData.browseUrl = browseUrl;
+ gReturnData.username = username;
+ // Note that we use the password for the next publish action
+ // even if savePassword is false; but we won't save it in PasswordManager database
+ gReturnData.password = password;
+ gReturnData.savePassword = savePassword;
+ gReturnData.docDir = gPublishSiteData[siteIndex].docDir;
+ gReturnData.otherDir = gPublishSiteData[siteIndex].otherDir;
+ gReturnData.publishOtherFiles = publishOtherFiles;
+ gReturnData.dirList = gPublishSiteData[siteIndex].dirList;
+ return true;
+}
+
+function ValidateData()
+{
+ if (!ValidateSettings())
+ return false;
+
+ var siteIndex = gDialog.SiteList.selectedIndex;
+ if (siteIndex == -1)
+ return false;
+
+ var filename = TrimString(gDialog.FilenameInput.value);
+ if (!filename)
+ {
+ ShowErrorInPanel(gDialog.PublishTab, "MissingPublishFilename", gDialog.FilenameInput);
+ return false;
+ }
+ gReturnData.filename = filename;
+
+ return true;
+}
+
+function ShowErrorInPanel(tab, errorMsgId, widgetWithError)
+{
+ SwitchPanel(tab);
+ ShowInputErrorMessage(GetString(errorMsgId));
+ if (widgetWithError)
+ SetTextboxFocus(widgetWithError);
+}
+
+function onAccept(event)
+{
+ if (ValidateData())
+ {
+ // DON'T save the docDir and otherDir before trying to publish
+ gReturnData.saveDirs = false;
+
+ // We save new site data to prefs only if we are attempting to publish
+ if (gSettingsChanged)
+ SavePublishDataToPrefs(gReturnData);
+
+ // Set flag to resave data after publishing
+ // so we save docDir and otherDir if we published successfully
+ gReturnData.savePublishData = true;
+
+ var title = TrimString(gDialog.PageTitleInput.value);
+ if (title != gPreviousTitle)
+ SetDocumentTitle(title);
+
+ SaveWindowLocation();
+ window.opener.ok = true;
+ return;
+ }
+
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EditorPublish.xhtml b/comm/suite/editor/components/dialogs/content/EditorPublish.xhtml
new file mode 100644
index 0000000000..ed15c9f7d2
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorPublish.xhtml
@@ -0,0 +1,132 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/menulist.css" type="text/css"?>
+
+<?xul-overlay href="chrome://editor/content/EditorPublishOverlay.xhtml"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorPublish.dtd">
+
+<dialog title="&windowTitle.label;"
+ id="publishDlg"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()"
+ buttons="accept,cancel"
+ buttonlabelaccept="&publishButton.label;">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EditorPublish.js"/>
+ <script src="chrome://editor/content/publishprefs.js"/>
+ <script src="chrome://messenger/content/customElements.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <tabbox id="TabBox">
+ <tabs flex="1">
+ <tab id="PublishTab" oncommand="SelectPublishTab()" label="&publishTab.label;"/>
+ <tab id="SettingsTab" oncommand="SelectSettingsTab()" label="&settingsTab.label;"/>
+ </tabs>
+ <tabpanels>
+ <!-- PUBLISH PANEL -->
+ <vbox>
+ <spacer class="spacer"/>
+ <grid pack="start">
+ <columns><column/><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label value="&siteList.label;"
+ accesskey="&siteList.accesskey;"
+ control="SiteList"/>
+ <!-- Contents filled in at runtime -->
+ <menulist id="SiteList"
+ style="min-width:18em; max-width:18em;" crop="right"
+ tooltiptext="&siteList.tooltip;"
+ oncommand="SelectSiteList();"/>
+ <hbox>
+ <button label="&newSiteButton.label;"
+ accesskey="&newSiteButton.accesskey;"
+ oncommand="AddNewSite();"/>
+ <spacer flex="1"/>
+ </hbox>
+ </row>
+ <spacer class="spacer"/>
+ <row align="center">
+ <label value="&pageTitle.label;" accesskey="&pageTitle.accesskey;"
+ control="PageTitleInput"/>
+ <textbox id="PageTitleInput"
+ tooltiptext="&pageTitle.tooltip;" class="minWidth15"/>
+ <label value="&pageTitleExample.label;"/>
+ </row>
+ <row align="center">
+ <label value="&filename.label;" accesskey="&filename.accesskey;"
+ control="FilenameInput"/>
+ <textbox id="FilenameInput"
+ tooltiptext="&filename.tooltip;" class="minWidth15 uri-element"/>
+ <label value="&filenameExample.label;"/>
+ </row>
+ </rows>
+ </grid>
+ <spacer class="spacer"/>
+ <label value="&docDirList.label;"
+ accesskey="&docDirList.accesskey;"
+ control="DocDirList"/>
+ <hbox align="center">
+ <!-- Contents filled in at runtime -->
+ <menulist is="menulist-editable" id="DocDirList"
+ class="minWidth20 uri-element" editable="true" flex="1"
+ tooltiptext="&docDirList.tooltip;" oninput="onInputSettings();"/>
+ </hbox>
+ <spacer class="spacer"/>
+ <groupbox>
+ <caption>
+ <checkbox id="OtherDirCheckbox" label="&publishImgCheckbox.label;"
+ accesskey="&publishImgCheckbox.accesskey;"
+ tooltiptext="&publishImgCheckbox.tooltip;"
+ oncommand="doEnabling();"/>
+ </caption>
+ <vbox>
+ <radiogroup id="OtherDirRadiogroup">
+ <hbox>
+ <spacer class="checkbox-spacer"/>
+ <radio id="SameLocationRadio" label="&sameLocationRadio.label;"
+ accesskey="&sameLocationRadio.accesskey;"
+ tooltiptext="&sameLocationRadio.tooltip;"
+ oncommand="doEnabling();"/>
+ </hbox>
+ <hbox>
+ <spacer class="checkbox-spacer"/>
+ <radio id="UseSubdirRadio" label="&useSubdirRadio.label;"
+ accesskey="&useSubdirRadio.accesskey;"
+ tooltiptext="&useSubdirRadio.tooltip;"
+ oncommand="doEnabling();"/>
+ </hbox>
+ </radiogroup>
+ </vbox>
+ <hbox>
+ <spacer class="checkbox-spacer"/>
+ <spacer class="radio-spacer"/>
+ <!-- Contents filled in at runtime -->
+ <menulist is="menulist-editable" id="OtherDirList"
+ class="minWidth20 uri-element"
+ editable="true" flex="1" tooltiptext="&otherDirList.tooltip;"
+ oninput="onInputSettings();"/>
+ </hbox>
+ </groupbox>
+ <spacer flex="1"/>
+ </vbox><!-- Publish Panel -->
+
+ <!-- SETTINGS PANEL -->
+ <hbox id="SettingsPanel">
+ <!-- from EditorPublishOverlay.xhtml -->
+ <vbox id="PublishSettingsInputs" flex="1"/>
+ </hbox><!-- Settings Panel -->
+ </tabpanels>
+ </tabbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EditorPublishOverlay.xhtml b/comm/suite/editor/components/dialogs/content/EditorPublishOverlay.xhtml
new file mode 100644
index 0000000000..136a75623c
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorPublishOverlay.xhtml
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://editor/locale/EditorPublish.dtd">
+
+<overlay id="EditorPublishOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+<vbox id="PublishSettingsInputs">
+ <groupbox id="ServerSettingsBox">
+ <label class="header">&serverInfo.label;</label>
+ <hbox align="center">
+ <label value="&siteName.label;" accesskey="&siteName.accesskey;"
+ control="SiteNameInput"/>
+ <textbox id="SiteNameInput" class="MinWidth20em" flex="1"
+ tooltiptext="&siteName.tooltip;" oninput="onInputSettings();"/>
+ </hbox>
+ <spacer class="spacer"/>
+ <label value="&siteUrl.label;" accesskey="&siteUrl.accesskey;"
+ control="PublishUrlInput"/>
+ <textbox id="PublishUrlInput" class="MinWidth20em uri-element"
+ tooltiptext="&siteUrl.tooltip;" oninput="onInputSettings();"/>
+ <spacer class="spacer"/>
+ <label value="&browseUrl.label;" accesskey="&browseUrl.accesskey;"
+ control="BrowseUrlInput"/>
+ <textbox id="BrowseUrlInput" class="MinWidth20em uri-element"
+ tooltiptext="&browseUrl.tooltip;" oninput="onInputSettings();"/>
+ <spacer class="spacer"/>
+ </groupbox>
+ <groupbox id="LoginSettingsBox">
+ <label class="header">&loginInfo.label;</label>
+ <grid>
+ <columns><column flex="1"/><column flex="3"/></columns>
+ <rows>
+ <row align="center">
+ <label value="&username.label;" accesskey="&username.accesskey;"
+ control="UsernameInput"/>
+ <textbox id="UsernameInput" class="MinWidth10em" flex="1"
+ tooltiptext="&username.tooltip;" oninput="onInputSettings();"/>
+ </row>
+ <row align="center">
+ <label value="&password.label;" accesskey="&password.accesskey;"
+ control="PasswordInput"/>
+ <hbox>
+ <textbox id="PasswordInput" type="password" class="MinWidth5em"
+ oninput="onInputSettings();"
+ tooltiptext="&password.tooltip;"/>
+ <checkbox id="SavePassword" label="&savePassword.label;"
+ accesskey="&savePassword.accesskey;"
+ tooltiptext="&savePassword.tooltip;"
+ oncommand="onInputSettings();"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ <spacer class="spacer"/>
+ </groupbox>
+</vbox>
+
+</overlay>
diff --git a/comm/suite/editor/components/dialogs/content/EditorPublishProgress.js b/comm/suite/editor/components/dialogs/content/EditorPublishProgress.js
new file mode 100644
index 0000000000..dafa053661
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorPublishProgress.js
@@ -0,0 +1,391 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gInProgress = true;
+var gPublishData;
+var gPersistObj;
+var gTotalFileCount = 0;
+var gSucceededCount = 0;
+var gFinished = false;
+var gPublishingFailed = false;
+var gFileNotFound = false;
+var gStatusMessage="";
+
+var gTimerID;
+var gAllowEnterKey = false;
+
+// Publishing error codes
+// These are translated from C++ error code strings like this:
+// kFileNotFound = "FILE_NOT_FOUND",
+const kNetReset = 2152398868; // nsISocketTransportService.idl
+const kFileNotFound = 2152857618;
+const kNotConnected = 2152398860; // in netCore.h
+const kConnectionRefused = 2152398861; // nsISocketTransportService.idl
+const kNetTimeout = 2152398862; // nsISocketTransportService.idl
+const kNoConnectionOrTimeout = 2152398878;
+const kPortAccessNotAllowed = 2152398867; // netCore.h
+const kOffline = 2152398865; // netCore.h
+const kDiskFull = 2152857610;
+const kNoDeviceSpace = 2152857616;
+const kNameTooLong = 2152857617;
+const kAccessDenied = 2152857621;
+
+// These are more errors that I don't think we encounter during publishing,
+// so we don't have error strings yet. Let's keep them here for future reference
+//const kUnrecognizedPath = 2152857601;
+//const kUnresolvableSymlink = 2152857602;
+//const kUnknownType = 2152857604;
+//const kDestinationNotDir = 2152857605;
+//const kTargetDoesNotExist = 2152857606;
+//const kAlreadyExists = 2152857608;
+//const kInvalidPath = 2152857609;
+//const kNotDirectory = 2152857612;
+//const kIsDirectory = 2152857613;
+//const kIsLocked = 2152857614;
+//const kTooBig = 2152857615;
+//const kReadOnly = 2152857619;
+//const kDirectoryNotEmpty = 2152857620;
+//const kErrorBindingRedirected = 2152398851;
+//const kAlreadyConnected = 2152398859; // in netCore.h
+//const kInProgress = 2152398863; // netCore.h
+//const kNoContent = 2152398865; // netCore.h
+//const kUnknownProtocol = 2152398866 // netCore.h
+//const kFtpLogin = 2152398869; // ftpCore.h
+//const kFtpCWD = 2152398870; // ftpCore.h
+//const kFtpPasv = 2152398871; // ftpCore.h
+//const kFtpPwd = 2152398872; // ftpCore.h
+
+document.addEventListener("dialogaccept", onEnterKey);
+document.addEventListener("dialogcancel", onClose);
+
+function Startup()
+{
+ gPublishData = window.arguments[0];
+ if (!gPublishData)
+ {
+ dump("No publish data!\n");
+ window.close();
+ return;
+ }
+
+ gDialog.FileList = document.getElementById("FileList");
+ gDialog.FinalStatusMessage = document.getElementById("FinalStatusMessage");
+ gDialog.StatusMessage = document.getElementById("StatusMessage");
+ gDialog.KeepOpen = document.getElementById("KeepOpen");
+ gDialog.Close = document.documentElement.getButton("cancel");
+
+ SetWindowLocation();
+ var title = GetDocumentTitle();
+ if (!title)
+ title = "(" + opener.gUntitledString + ")";
+ document.title = GetString("PublishProgressCaption").replace(/%title%/, title);
+
+ document.getElementById("PublishToSite").value =
+ GetString("PublishToSite").replace(/%title%/, TruncateStringAtWordEnd(gPublishData.siteName, 25));
+
+ // Show publishing destination URL
+ document.getElementById("PublishUrl").value = gPublishData.publishUrl;
+
+ // Show subdirectories only if not empty
+ if (gPublishData.docDir || gPublishData.otherDir)
+ {
+ if (gPublishData.docDir)
+ document.getElementById("docDir").value = gPublishData.docDir;
+ else
+ document.getElementById("DocSubdir").hidden = true;
+
+ if (gPublishData.publishOtherFiles && gPublishData.otherDir)
+ document.getElementById("otherDir").value = gPublishData.otherDir;
+ else
+ document.getElementById("OtherSubdir").hidden = true;
+ }
+ else
+ document.getElementById("Subdirectories").hidden = true;
+
+ // Add the document to the "publish to" list as quick as possible!
+ SetProgressStatus(gPublishData.filename, "busy");
+
+ if (gPublishData.publishOtherFiles)
+ {
+ // When publishing images as well, expand list to show more items
+ gDialog.FileList.setAttribute("rows", 5);
+ window.sizeToContent();
+ }
+
+ // Now that dialog is initialized, we can start publishing
+ gPersistObj = window.opener.StartPublishing();
+}
+
+// this function is to be used when we cancel persist's saving
+// since not all messages will be returned to us if we cancel
+// this function changes status for all non-done/non-failure to failure
+function SetProgressStatusCancel()
+{
+ let listitems = document.querySelectorAll('listitem:not([progress="done"]):not([progress="failed"])');
+ if (!listitems)
+ return;
+
+ for (var i=0; i < listitems.length; i++)
+ {
+ listitems[i].setAttribute("progress", "failed");
+ }
+}
+
+// Add filename to list of files to publish
+// or set status for file already in the list
+// Returns true if file was in the list
+function SetProgressStatus(filename, status)
+{
+ if (!filename)
+ return false;
+
+ if (!status)
+ status = "busy";
+
+ // Just set attribute for status icon if we already have this filename.
+ let listitem = document.querySelector('listitem[label="' + filename + '"]');
+ if (listitem)
+ {
+ listitem.setAttribute("progress", status);
+ return true;
+ }
+ // We're adding a new file item to list
+ gTotalFileCount++;
+
+ listitem = document.createXULElement("listitem");
+ if (listitem)
+ {
+ listitem.setAttribute("class", "listitem-iconic progressitem");
+ // This triggers CSS to show icon for each status state
+ listitem.setAttribute("progress", status);
+ listitem.setAttribute("label", filename);
+ gDialog.FileList.appendChild(listitem);
+ }
+ return false;
+}
+
+function SetProgressFinished(filename, networkStatus)
+{
+ var abortPublishing = false;
+ if (filename)
+ {
+ var status = networkStatus ? "failed" : "done";
+ if (networkStatus == 0)
+ gSucceededCount++;
+
+ SetProgressStatus(filename, status);
+ }
+
+ if (networkStatus != 0) // Error condition
+ {
+ // We abort on all errors except if image file was not found
+ abortPublishing = networkStatus != kFileNotFound;
+
+ // Mark all remaining files as "failed"
+ if (abortPublishing)
+ {
+ gPublishingFailed = true;
+ SetProgressStatusCancel();
+ gDialog.FinalStatusMessage.value = GetString("PublishFailed");
+ }
+
+ switch (networkStatus)
+ {
+ case kFileNotFound:
+ gFileNotFound = true;
+ if (filename)
+ gStatusMessage = GetString("FileNotFound").replace(/%file%/, filename);
+ break;
+ case kNetReset:
+ // We get this when subdir doesn't exist AND
+ // if filename used is same as an existing subdir
+ var dir = (gPublishData.filename == filename) ?
+ gPublishData.docDir : gPublishData.otherDir;
+
+ if (dir)
+ {
+ // This is the ambiguous case when we can't tell if subdir or filename is bad
+ // Remove terminal "/" from dir string and insert into message
+ gStatusMessage = GetString("SubdirDoesNotExist").replace(/%dir%/, dir.slice(0, dir.length-1));
+ gStatusMessage = gStatusMessage.replace(/%file%/, filename);
+
+ // Remove directory from saved prefs
+ // XXX Note that if subdir is good,
+ // but filename = next level subdirectory name,
+ // we really shouldn't remove subdirectory,
+ // but it's impossible to differentiate this case!
+ RemovePublishSubdirectoryFromPrefs(gPublishData, dir);
+ }
+ else if (filename)
+ gStatusMessage = GetString("FilenameIsSubdir").replace(/%file%/, filename);
+
+ break;
+ case kNotConnected:
+ case kConnectionRefused:
+ case kNetTimeout:
+ case kNoConnectionOrTimeout:
+ case kPortAccessNotAllowed:
+ gStatusMessage = GetString("ServerNotAvailable");
+ break;
+ case kOffline:
+ gStatusMessage = GetString("Offline");
+ break;
+ case kDiskFull:
+ case kNoDeviceSpace:
+ if (filename)
+ gStatusMessage = GetString("DiskFull").replace(/%file%/, filename);
+ break;
+ case kNameTooLong:
+ if (filename)
+ gStatusMessage = GetString("NameTooLong").replace(/%file%/, filename);
+ break;
+ case kAccessDenied:
+ if (filename)
+ gStatusMessage = GetString("AccessDenied").replace(/%file%/, filename);
+ break;
+ case kUnknownType:
+ default:
+ gStatusMessage = GetString("UnknownPublishError")
+ break;
+ }
+ }
+ else if (!filename)
+ {
+ gFinished = true;
+
+ document.documentElement.setAttribute("buttonlabelcancel",
+ document.documentElement.getAttribute("buttonlabelclose"));
+
+ if (!gStatusMessage)
+ gStatusMessage = GetString(gPublishingFailed ? "UnknownPublishError" : "AllFilesPublished");
+
+ // Now allow "Enter/Return" key to close the dialog
+ AllowDefaultButton();
+
+ if (gPublishingFailed || gFileNotFound)
+ {
+ // Show "Troubleshooting" button to help solving problems
+ // and key for successful / failed files
+ document.getElementById("failureBox").hidden = false;
+ }
+ }
+
+ if (gStatusMessage)
+ SetStatusMessage(gStatusMessage);
+}
+
+function CheckKeepOpen()
+{
+ if (gTimerID)
+ {
+ clearTimeout(gTimerID);
+ gTimerID = null;
+ }
+}
+
+function onClose()
+{
+ if (!gFinished)
+ {
+ const buttonFlags = (Services.prompt.BUTTON_TITLE_IS_STRING *
+ Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_CANCEL *
+ Services.prompt.BUTTON_POS_1);
+ let button = Services.prompt.confirmEx(window,
+ GetString("CancelPublishTitle"),
+ GetString("CancelPublishMessage"),
+ buttonFlags,
+ GetString("CancelPublishContinue"),
+ null, null, null, {});
+ if (button == 0)
+ return false;
+ }
+
+ if (gTimerID)
+ {
+ clearTimeout(gTimerID);
+ gTimerID = null;
+ }
+
+ if (!gFinished && gPersistObj)
+ {
+ try {
+ gPersistObj.cancelSave();
+ } catch (e) {}
+ }
+ SaveWindowLocation();
+
+ // Tell caller so they can cleanup and restore editability
+ window.opener.FinishPublishing();
+ return true;
+}
+
+function AllowDefaultButton()
+{
+ gDialog.Close.setAttribute("default","true");
+ gAllowEnterKey = true;
+}
+
+function onEnterKey(event)
+{
+ if (gAllowEnterKey)
+ return CloseDialog();
+
+ event.preventDefault();
+}
+
+function RequestCloseDialog()
+{
+ // Finish progress messages, settings buttons etc.
+ SetProgressFinished(null, 0);
+
+ if (!gDialog.KeepOpen.checked)
+ {
+ // Leave window open a minimum amount of time
+ gTimerID = setTimeout(CloseDialog, 3000);
+ }
+
+ // Set "completed" message if we succeeded
+ // (Some image files may have failed,
+ // but we don't abort publishing for that)
+ if (!gPublishingFailed)
+ {
+ gDialog.FinalStatusMessage.value = GetString("PublishCompleted");
+ if (gFileNotFound && gTotalFileCount-gSucceededCount)
+ {
+ // Show number of files that failed to upload
+ gStatusMessage =
+ (GetString("FailedFileMsg").replace(/%x%/,(gTotalFileCount-gSucceededCount)))
+ .replace(/%total%/,gTotalFileCount);
+
+ SetStatusMessage(gStatusMessage);
+ }
+ }
+}
+
+function SetStatusMessage(message)
+{
+ // Status message is a child of <description> element
+ // so text can wrap to multiple lines if necessary
+ if (gDialog.StatusMessage.firstChild)
+ {
+ gDialog.StatusMessage.firstChild.data = message;
+ }
+ else
+ {
+ var textNode = document.createTextNode(message);
+ if (textNode)
+ gDialog.StatusMessage.appendChild(textNode);
+ }
+ window.sizeToContent();
+}
+
+function CloseDialog()
+{
+ SaveWindowLocation();
+ window.opener.FinishPublishing();
+ try {
+ window.close();
+ } catch (e) {}
+}
diff --git a/comm/suite/editor/components/dialogs/content/EditorPublishProgress.xhtml b/comm/suite/editor/components/dialogs/content/EditorPublishProgress.xhtml
new file mode 100644
index 0000000000..2cf422c8b5
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorPublishProgress.xhtml
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorPublishProgress.dtd">
+
+<dialog title=""
+ id="publishProgressDlg"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ buttons="cancel"
+ buttonlabelclose="&closeButton.label;"
+ onload="Startup()">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/publishprefs.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EditorPublishProgress.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <caption><label id="PublishToSite"/></caption>
+ <label value="&siteUrl.label;"/>
+ <hbox>
+ <label class="indent bold" id="PublishUrl"/>
+ </hbox>
+ <spacer class="spacer"/>
+ <grid id="Subdirectories">
+ <columns><column/><column/></columns>
+ <rows>
+ <row id="DocSubdir">
+ <label value="&docSubdir.label;"/>
+ <label id="docDir"/>
+ </row>
+ <row id="OtherSubdir">
+ <label value="&otherSubdir.label;"/>
+ <label id="otherDir"/>
+ </row>
+ </rows>
+ </grid>
+ <label id="OtherUrl" class="bold" style="margin-left:3em"/>
+ </groupbox>
+ <groupbox>
+ <caption><label value="&fileList.label;"/></caption>
+ <vbox align="center" style="max-width:30em">
+ <label id="FinalStatusMessage" class="bold" value="&status.label;"/>
+ </vbox>
+ <description id="StatusMessage" class="wrap" style="max-width:30em; min-height: 1em"/>
+ <vbox flex="1">
+ <listbox id="FileList" rows="1"/>
+ </vbox>
+ <hbox align="center" id="failureBox" hidden="true">
+ <image class="progressitem" progress="done"/>
+ <label value="&succeeded.label;"/>
+ <spacer class="bigspacer"/>
+ <image class="progressitem" progress="failed"/>
+ <label value="&failed.label;"/>
+ </hbox>
+ </groupbox>
+ <checkbox id="KeepOpen" label="&keepOpen;" oncommand="CheckKeepOpen();" persist="checked"/>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EditorPublishSettings.js b/comm/suite/editor/components/dialogs/content/EditorPublishSettings.js
new file mode 100644
index 0000000000..01677ae8c0
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorPublishSettings.js
@@ -0,0 +1,343 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gPublishSiteData;
+var gPublishDataChanged = false;
+var gDefaultSiteIndex = -1;
+var gDefaultSiteName;
+var gPreviousDefaultSite;
+var gPreviousTitle;
+var gSettingsChanged = false;
+var gSiteDataChanged = false;
+var gAddNewSite = false;
+var gCurrentSiteIndex = -1;
+var gPasswordManagerOn = true;
+
+// Dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup()
+{
+ if (!GetCurrentEditor())
+ {
+ window.close();
+ return;
+ }
+
+ gDialog.SiteList = document.getElementById("SiteList");
+ gDialog.SiteNameInput = document.getElementById("SiteNameInput");
+ gDialog.PublishUrlInput = document.getElementById("PublishUrlInput");
+ gDialog.BrowseUrlInput = document.getElementById("BrowseUrlInput");
+ gDialog.UsernameInput = document.getElementById("UsernameInput");
+ gDialog.PasswordInput = document.getElementById("PasswordInput");
+ gDialog.SavePassword = document.getElementById("SavePassword");
+ gDialog.SetDefaultButton = document.getElementById("SetDefaultButton");
+ gDialog.RemoveSiteButton = document.getElementById("RemoveSiteButton");
+ gDialog.OkButton = document.documentElement.getButton("accept");
+
+ gPublishSiteData = GetPublishSiteData();
+ gDefaultSiteName = GetDefaultPublishSiteName();
+ gPreviousDefaultSite = gDefaultSiteName;
+
+ gPasswordManagerOn = Services.prefs.getBoolPref("signon.rememberSignons");
+ gDialog.SavePassword.disabled = !gPasswordManagerOn;
+
+ InitDialog();
+
+ SetWindowLocation();
+}
+
+function InitDialog()
+{
+ // If there's no current site data, start a new item in the Settings panel
+ if (!gPublishSiteData)
+ {
+ AddNewSite();
+ }
+ else
+ {
+ FillSiteList();
+
+ // uncomment next code line if you want preselection of the default
+ // publishing site
+ //InitSiteSettings(gDefaultSiteIndex);
+
+ SetTextboxFocus(gDialog.SiteNameInput);
+ }
+}
+
+function FillSiteList()
+{
+ // Prevent triggering SelectSiteList() actions
+ gIsSelecting = true;
+ ClearListbox(gDialog.SiteList);
+ gIsSelecting = false;
+ gDefaultSiteIndex = -1;
+
+ // Fill the site list
+ var count = gPublishSiteData.length;
+ for (var i = 0; i < count; i++)
+ {
+ var name = gPublishSiteData[i].siteName;
+ var item = gDialog.SiteList.appendItem(name);
+ SetPublishItemStyle(item);
+ if (name == gDefaultSiteName)
+ gDefaultSiteIndex = i;
+ }
+}
+
+function SetPublishItemStyle(item)
+{
+ // Display default site with bold style
+ if (item)
+ {
+ if (item.getAttribute("label") == gDefaultSiteName)
+ item.setAttribute("class", "bold");
+ else
+ item.removeAttribute("class");
+ }
+}
+
+function AddNewSite()
+{
+ // Save any pending changes locally first
+ if (!ApplyChanges())
+ return;
+
+ // Initialize Setting widgets to none of the selected sites
+ InitSiteSettings(-1);
+ gAddNewSite = true;
+
+ SetTextboxFocus(gDialog.SiteNameInput);
+}
+
+function RemoveSite()
+{
+ if (!gPublishSiteData)
+ return;
+
+ var index = gDialog.SiteList.selectedIndex;
+ if (index != -1)
+ {
+ let item = gDialog.SiteList.selectedItem;
+ var nameToRemove = item.getAttribute("label");
+
+ // Remove one item from site data array
+ gPublishSiteData.splice(index, 1);
+ // Remove item from site list
+ gDialog.SiteList.clearSelection();
+ item.remove();
+
+ // Adjust if we removed last item and reselect a site
+ if (index >= gPublishSiteData.length)
+ index--;
+ InitSiteSettings(index);
+
+ if (nameToRemove == gDefaultSiteName)
+ {
+ // Deleting current default -- set to new selected item
+ // Arbitrary, but what else to do?
+ SetDefault();
+ }
+ gSiteDataChanged = true;
+ }
+}
+
+function SetDefault()
+{
+ if (!gPublishSiteData)
+ return;
+
+ var index = gDialog.SiteList.selectedIndex;
+ if (index != -1)
+ {
+ gDefaultSiteIndex = index;
+ gDefaultSiteName = gPublishSiteData[index].siteName;
+
+ // Set bold style on new default
+ var item = gDialog.SiteList.firstChild;
+ while (item)
+ {
+ SetPublishItemStyle(item);
+ item = item.nextSibling;
+ }
+ }
+}
+
+// Recursion prevention:
+// Use when you don't want to trigger ApplyChanges and InitSiteSettings
+var gIsSelecting = false;
+
+function SelectSiteList()
+{
+ if (gIsSelecting)
+ return;
+
+ gIsSelecting = true;
+ var newIndex = gDialog.SiteList.selectedIndex;
+
+ // Save any pending changes locally first
+ if (!ApplyChanges())
+ return;
+
+ InitSiteSettings(newIndex);
+
+ gIsSelecting = false;
+}
+
+// Use this to prevent recursion in SelectSiteList
+function SetSelectedSiteIndex(index)
+{
+ gIsSelecting = true;
+ gDialog.SiteList.selectedIndex = index;
+ gIsSelecting = false;
+}
+
+function InitSiteSettings(selectedSiteIndex)
+{
+ // Index to the site we will need to update if settings changed
+ gCurrentSiteIndex = selectedSiteIndex;
+
+ SetSelectedSiteIndex(selectedSiteIndex);
+ var haveData = (gPublishSiteData && selectedSiteIndex != -1);
+
+ gDialog.SiteNameInput.value = haveData ? gPublishSiteData[selectedSiteIndex].siteName : "";
+ gDialog.PublishUrlInput.value = haveData ? gPublishSiteData[selectedSiteIndex].publishUrl : "";
+ gDialog.BrowseUrlInput.value = haveData ? gPublishSiteData[selectedSiteIndex].browseUrl : "";
+ gDialog.UsernameInput.value = haveData ? gPublishSiteData[selectedSiteIndex].username : "";
+
+ var savePassord = haveData && gPasswordManagerOn;
+ gDialog.PasswordInput.value = savePassord ? gPublishSiteData[selectedSiteIndex].password : "";
+ gDialog.SavePassword.checked = savePassord ? gPublishSiteData[selectedSiteIndex].savePassword : false;
+
+ gDialog.SetDefaultButton.disabled = !haveData;
+ gDialog.RemoveSiteButton.disabled = !haveData;
+ gSettingsChanged = false;
+}
+
+function onInputSettings()
+{
+ // TODO: Save current data during SelectSite1 and compare here
+ // to detect if real change has occurred?
+ gSettingsChanged = true;
+}
+
+function ApplyChanges()
+{
+ if (gSettingsChanged && !UpdateSettings())
+ {
+ // Restore selection to previously current site
+ SetSelectedSiteIndex(gCurrentSiteIndex);
+ return false;
+ }
+ return true;
+}
+
+function UpdateSettings()
+{
+ // Validate and add new site
+ var newName = TrimString(gDialog.SiteNameInput.value);
+ if (!newName)
+ {
+ ShowInputErrorMessage(GetString("MissingSiteNameError"), gDialog.SiteNameInput);
+ return false;
+ }
+ if (PublishSiteNameExists(newName, gPublishSiteData, gCurrentSiteIndex))
+ {
+ ShowInputErrorMessage(GetString("DuplicateSiteNameError").replace(/%name%/, newName));
+ SetTextboxFocus(gDialog.SiteNameInput);
+ return false;
+ }
+
+ var newUrl = FormatUrlForPublishing(gDialog.PublishUrlInput.value);
+ if (!newUrl)
+ {
+ ShowInputErrorMessage(GetString("MissingPublishUrlError"), gDialog.PublishUrlInput);
+ return false;
+ }
+
+ // Start assuming we're updating existing site at gCurrentSiteIndex
+ var newSiteData = false;
+
+ if (!gPublishSiteData)
+ {
+ // First time used - Create the first site profile
+ gPublishSiteData = new Array(1);
+ gCurrentSiteIndex = 0;
+ newSiteData = true;
+ }
+ else if (gCurrentSiteIndex == -1)
+ {
+ // No currently-selected site,
+ // must be adding a new site
+ // Add new data at the end of list
+ gCurrentSiteIndex = gPublishSiteData.length;
+ newSiteData = true;
+ }
+
+ if (newSiteData)
+ {
+ // Init new site profile
+ gPublishSiteData[gCurrentSiteIndex] = {};
+ gPublishSiteData[gCurrentSiteIndex].docDir = "";
+ gPublishSiteData[gCurrentSiteIndex].otherDir = "";
+ gPublishSiteData[gCurrentSiteIndex].dirList = [""];
+ gPublishSiteData[gCurrentSiteIndex].previousSiteName = newName;
+ }
+
+ gPublishSiteData[gCurrentSiteIndex].siteName = newName;
+ gPublishSiteData[gCurrentSiteIndex].publishUrl = newUrl;
+ gPublishSiteData[gCurrentSiteIndex].browseUrl = FormatUrlForPublishing(gDialog.BrowseUrlInput.value);
+ gPublishSiteData[gCurrentSiteIndex].username = TrimString(gDialog.UsernameInput.value);
+ gPublishSiteData[gCurrentSiteIndex].password= gDialog.PasswordInput.value;
+ gPublishSiteData[gCurrentSiteIndex].savePassword = gDialog.SavePassword.checked;
+
+ if (gCurrentSiteIndex == gDefaultSiteIndex)
+ gDefaultSiteName = newName;
+
+ // When adding the very first site, assume that's the default
+ if (gPublishSiteData.length == 1 && !gDefaultSiteName)
+ {
+ gDefaultSiteName = gPublishSiteData[0].siteName;
+ gDefaultSiteIndex = 0;
+ }
+
+ FillSiteList();
+
+ // Select current site in list
+ SetSelectedSiteIndex(gCurrentSiteIndex);
+
+ // Signal saving data to prefs
+ gSiteDataChanged = true;
+
+ // Clear current site flags
+ gSettingsChanged = false;
+ gAddNewSite = false;
+
+ return true;
+}
+
+function onAccept(event)
+{
+ // Save any pending changes locally first
+ if (!ApplyChanges()) {
+ event.preventDefault();
+ return;
+ }
+
+ if (gSiteDataChanged)
+ {
+ // Save all local data to prefs
+ SavePublishSiteDataToPrefs(gPublishSiteData, gDefaultSiteName);
+ }
+ else if (gPreviousDefaultSite != gDefaultSiteName)
+ {
+ // only the default site was changed
+ SetDefaultSiteName(gDefaultSiteName);
+ }
+
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EditorPublishSettings.xhtml b/comm/suite/editor/components/dialogs/content/EditorPublishSettings.xhtml
new file mode 100644
index 0000000000..ace6c2fc5d
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorPublishSettings.xhtml
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<?xul-overlay href="chrome://editor/content/EditorPublishOverlay.xhtml"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorPublish.dtd">
+
+<dialog title="&windowTitleSettings.label;"
+ id="publishSettingsDlg"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()"
+ buttons="accept,cancel">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EditorPublishSettings.js"/>
+ <script src="chrome://editor/content/publishprefs.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <hbox id="SettingsPanel">
+ <groupbox align="center">
+ <label class="header">&publishSites.label;</label>
+ <!-- XXX: If tree isn't wrapped in vbox, it appears BELOW next vbox -->
+ <vbox flex="1">
+ <listbox rows="4" id="SiteList" flex="1" onselect="SelectSiteList();"/>
+ </vbox>
+ <hbox pack="center">
+ <vbox>
+ <button id="NewSiteButton" label="&newSiteButton.label;"
+ accesskey="&newSiteButton.accesskey;" oncommand="AddNewSite();"/>
+ <button id="SetDefaultButton" label="&setDefaultButton.label;"
+ accesskey="&setDefaultButton.accesskey;" oncommand="SetDefault();"/>
+ <button id="RemoveSiteButton" label="&removeButton.label;"
+ accesskey="&removeButton.accesskey;" oncommand="RemoveSite();"/>
+ </vbox>
+ </hbox>
+ </groupbox>
+ <!-- from EditorPublishOverlay.xhtml -->
+ <vbox id="PublishSettingsInputs"/>
+ </hbox>
+ <spacer class="spacer"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.js b/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.js
new file mode 100644
index 0000000000..745a8bfd30
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.js
@@ -0,0 +1,155 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+var {CharsetMenu} = ChromeUtils.import("resource://gre/modules/CharsetMenu.jsm");
+
+var gCharset="";
+var gTitleWasEdited = false;
+var gCharsetWasChanged = false;
+var gInsertNewContentType = false;
+var gContenttypeElement;
+var gInitDone = false;
+var gCharsetInfo;
+
+//Cancel() is in EdDialogCommon.js
+
+var gCharsetView = {
+ get rowCount() { return gCharsetInfo.length; },
+ selection: null,
+ getRowProperties: function(index) { return ""; },
+ getCellProperties: function(index, column) { return ""; },
+ getColumnProperties: function(columm) { return ""; },
+ isContainer: function() { return false; },
+ isContainerOpen: function() { return false; },
+ isContainerEmpty: function() { return true; },
+ isSeparator: function() { return false; },
+ isSorted: function() { return false; },
+ canDrop: function(index, orientation) { return false; },
+ drop: function(index, orientation) {},
+ getParentIndex: function(index) { return -1; },
+ hasNextSibling: function(index, after) { return false; },
+ getLevel: function(index) { return 1; },
+ getImageSrc: function(index) { return null; },
+ getProgressMode: function(index) { return 0; },
+ getCellValue: function(index) { return ""; },
+ getCellText: function(index) { return gCharsetInfo[index].label; },
+ toggleOpenState: function(index) {},
+ cycleHeader: function(column) {},
+ selectionChanged: function() {},
+ cycleCell: function(index, column) {},
+ isEditable: function isEditable(index, column) { return false; },
+};
+
+function Startup()
+{
+ var editor = GetCurrentEditor();
+ if (!editor)
+ {
+ window.close();
+ return;
+ }
+
+ gDialog.TitleInput = document.getElementById("TitleInput");
+ gDialog.charsetTree = document.getElementById('CharsetTree');
+ gDialog.exportToText = document.getElementById('ExportToText');
+
+ gContenttypeElement = GetMetaElementByAttribute("http-equiv", "content-type");
+ if (!gContenttypeElement && (editor.contentsMIMEType != 'text/plain'))
+ {
+ gContenttypeElement = CreateMetaElementWithAttribute("http-equiv", "content-type");
+ if (!gContenttypeElement )
+ {
+ window.close();
+ return;
+ }
+ gInsertNewContentType = true;
+ }
+
+ try {
+ gCharset = editor.documentCharacterSet;
+ } catch (e) {}
+
+ var data = CharsetMenu.getData();
+ var charsets = data.pinnedCharsets.concat(data.otherCharsets);
+ gCharsetInfo = CharsetMenu.getCharsetInfo(charsets.map(info => info.value));
+ gDialog.charsetTree.view = gCharsetView;
+
+ InitDialog();
+
+ // Use the same text as the messagebox for getting title by regular "Save"
+ document.getElementById("EnterTitleLabel").setAttribute("value",GetString("NeedDocTitle"));
+ // This is an <HTML> element so it wraps -- append a child textnode
+ var helpTextParent = document.getElementById("TitleHelp");
+ var helpText = document.createTextNode(GetString("DocTitleHelp"));
+ if (helpTextParent)
+ helpTextParent.appendChild(helpText);
+
+ // SET FOCUS TO FIRST CONTROL
+ SetTextboxFocus(gDialog.TitleInput);
+
+ gInitDone = true;
+
+ SetWindowLocation();
+}
+
+
+function InitDialog()
+{
+ gDialog.TitleInput.value = GetDocumentTitle();
+
+ var tree = gDialog.charsetTree;
+ var index = gCharsetInfo.map(info => info.value).indexOf(gCharset);
+ if (index >= 0) {
+ tree.view.selection.select(index);
+ tree.ensureRowIsVisible(index);
+ }
+}
+
+
+function onAccept()
+{
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+
+ if(gCharsetWasChanged)
+ {
+ try {
+ SetMetaElementContent(gContenttypeElement, "text/html; charset=" + gCharset, gInsertNewContentType, true);
+ editor.documentCharacterSet = gCharset;
+ } catch (e) {}
+ }
+
+ editor.endTransaction();
+
+ if(gTitleWasEdited)
+ SetDocumentTitle(TrimString(gDialog.TitleInput.value));
+
+ window.opener.ok = true;
+ window.opener.exportToText = gDialog.exportToText.checked;
+ SaveWindowLocation();
+}
+
+
+function SelectCharset()
+{
+ if(gInitDone)
+ {
+ try
+ {
+ gCharset = gCharsetInfo[gDialog.charsetTree.currentIndex].value;
+ if (gCharset)
+ gCharsetWasChanged = true;
+ }
+ catch(e) {}
+ }
+}
+
+
+function TitleChanged()
+{
+ gTitleWasEdited = true;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.xhtml b/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.xhtml
new file mode 100644
index 0000000000..815a56dfe3
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.xhtml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorSaveAsCharset.dtd">
+
+<dialog title="&windowTitle2.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload = "Startup()"
+ style="width: 32em;">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EditorSaveAsCharset.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&documentTitleTitle.label;</label>
+ </hbox>
+ <label id="EnterTitleLabel"/>
+ <textbox id="TitleInput" oninput="TitleChanged();"/>
+ <description id="TitleHelp" class="wrap" style="width:1em" />
+ </groupbox>
+
+ <groupbox flex="1">
+ <hbox class="groupbox-title">
+ <label class="header">&documentCharsetTitle2.label;</label>
+ </hbox>
+ <label value="&documentCharsetDesc2.label;"/>
+ <tree id="CharsetTree" rows="8" hidecolumnpicker="true" onselect="SelectCharset();">
+ <treecols>
+ <treecol id="CharsetCol" flex="1" hideheader="true"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ </groupbox>
+
+ <checkbox id="ExportToText" label="&documentExportToText.label;" />
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/edImage.inc.xhtml b/comm/suite/editor/components/dialogs/content/edImage.inc.xhtml
new file mode 100644
index 0000000000..e80fb0457c
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/edImage.inc.xhtml
@@ -0,0 +1,248 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+ <vbox id="imageLocation">
+ <spacer class="spacer"/>
+ <label control = "srcInput"
+ value = "&locationEditField.label;"
+ accesskey="&locationEditField.accessKey;"
+ tooltiptext="&locationEditField.tooltip;"
+ />
+ <tooltip id="shortenedDataURI">
+ <label value="&locationEditField.shortenedDataURI;"/>
+ </tooltip>
+ <textbox id="srcInput" oninput="ChangeImageSrc();" tabindex="1" class="uri-element"
+ tooltiptext="&locationEditField.tooltip;"/>
+ <hbox id="MakeRelativeHbox">
+ <checkbox id="MakeRelativeCheckbox"
+ for="srcInput"
+ tabindex="2"
+ label="&makeUrlRelative.label;"
+ accesskey="&makeUrlRelative.accessKey;"
+ oncommand="MakeInputValueRelativeOrAbsolute(this);"
+ tooltiptext="&makeUrlRelative.tooltip;"/>
+ <checkbox id="AttachSourceToMail"
+ hidden="true"
+ label="&attachImageSource.label;"
+ accesskey="&attachImageSource.accesskey;"
+ oncommand="DoAttachSourceCheckbox()"/>
+ <spacer flex="1"/>
+ <button id="ChooseFile"
+ tabindex="3"
+ oncommand="chooseFile()"
+ label="&chooseFileButton.label;"
+ accesskey="&chooseFileButton.accessKey;"/>
+ </hbox>
+ <spacer class="spacer"/>
+ <radiogroup id="altTextRadioGroup" flex="1">
+ <grid>
+ <columns><column/><column flex="1"/></columns>
+ <rows>
+ <row align="center">
+ <label
+ style = "margin-left: 26px"
+ control = "titleInput"
+ accesskey = "&title.accessKey;"
+ value ="&title.label;"
+ tooltiptext="&title.tooltip;"
+ for = "titleInput"/>
+ <textbox flex="1"
+ id = "titleInput"
+ class = "MinWidth20em"
+ tooltiptext="&title.tooltip;"
+ tabindex="4"/>
+ </row>
+ <row align="center">
+ <radio id="altTextRadio" value="usealt-yes"
+ label="&altText.label;"
+ accesskey="&altText.accessKey;"
+ tooltiptext="&altTextEditField.tooltip;"
+#ifndef MOZ_SUITE
+ persist="selected"
+#endif
+ oncommand = "SetAltTextDisabled(false);"
+ tabindex="5"/>
+ <textbox flex="1"
+ id = "altTextInput"
+ class = "MinWidth20em"
+ tooltiptext="&altTextEditField.tooltip;"
+ oninput = "SetAltTextDisabled(false);"
+ tabindex="6"/>
+ </row>
+ </rows>
+ </grid>
+
+ <radio id="noAltTextRadio" value="usealt-no"
+ label="&noAltText.label;"
+ accesskey = "&noAltText.accessKey;"
+#ifndef MOZ_SUITE
+ persist="selected"
+#endif
+ oncommand = "SetAltTextDisabled(true);"/>
+ </radiogroup>
+ </vbox>
+
+ <vbox id="imageDimensions" align="start">
+ <spacer class="spacer"/>
+ <hbox>
+ <radiogroup id="imgSizeGroup">
+ <radio
+ id = "actualSizeRadio"
+ label = "&actualSizeRadio.label;"
+ accesskey = "&actualSizeRadio.accessKey;"
+ tooltiptext="&actualSizeRadio.tooltip;"
+ oncommand = "SetActualSize()"
+ value="actual"/>
+ <radio
+ id = "customSizeRadio"
+ label = "&customSizeRadio.label;"
+ selected = "true"
+ accesskey = "&customSizeRadio.accessKey;"
+ tooltiptext="&customSizeRadio.tooltip;"
+ oncommand = "doDimensionEnabling();"
+ value="custom"/>
+ </radiogroup>
+ <spacer flex="1"/>
+ <vbox>
+ <spacer flex="1"/>
+ <checkbox id="constrainCheckbox" label="&constrainCheckbox.label;"
+ accesskey="&constrainCheckbox.accessKey;"
+ oncommand="ToggleConstrain()"
+ tooltiptext="&constrainCheckbox.tooltip;"/>
+ </vbox>
+ <spacer flex="1"/>
+ </hbox>
+ <spacer class="spacer"/>
+ <grid class="indent">
+ <columns><column/><column/><column flex="1"/></columns>
+ <rows>
+ <row align="center">
+ <label id = "widthLabel"
+ control = "widthInput"
+ accesskey = "&widthEditField.accessKey;"
+ value = "&widthEditField.label;" />
+ <textbox
+ id = "widthInput"
+ class = "narrow"
+ oninput = "constrainProportions(this.id, 'heightInput')"/>
+ <menulist id = "widthUnitsMenulist"
+ oncommand = "doDimensionEnabling();" />
+ <!-- contents are appended by JS -->
+ </row>
+ <row align="center">
+ <label id = "heightLabel"
+ control = "heightInput"
+ accesskey = "&heightEditField.accessKey;"
+ value = "&heightEditField.label;" />
+ <textbox
+ id = "heightInput"
+ class = "narrow"
+ oninput = "constrainProportions(this.id, 'widthInput')"/>
+ <menulist id = "heightUnitsMenulist"
+ oncommand = "doDimensionEnabling();" />
+ <!-- contents are appended by JS -->
+ </row>
+ </rows>
+ </grid>
+ <spacer flex="1"/>
+ </vbox>
+
+ <hbox id="imageAppearance">
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label id="spacingLabel" class="header">&spacingBox.label;</label>
+ </hbox>
+ <grid>
+ <columns><column/><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label
+ class = "align-right"
+ id = "leftrightLabel"
+ control = "imageleftrightInput"
+ accesskey = "&leftRightEditField.accessKey;"
+ value = "&leftRightEditField.label;"/>
+ <textbox
+ class = "narrow"
+ id = "imageleftrightInput"
+ oninput = "forceInteger(this.id)"/>
+ <label
+ id = "leftrighttypeLabel"
+ value = "&pixelsPopup.value;" />
+ </row>
+ <spacer class="spacer"/>
+ <row align="center">
+ <label
+ class = "align-right"
+ id = "topbottomLabel"
+ control = "imagetopbottomInput"
+ accesskey = "&topBottomEditField.accessKey;"
+ value = "&topBottomEditField.label;"/>
+ <textbox
+ class = "narrow"
+ id = "imagetopbottomInput"
+ oninput = "forceInteger(this.id)"/>
+ <label id="topbottomtypeLabel"
+ value="&pixelsPopup.value;" />
+ </row>
+ <spacer class="spacer"/>
+ <row align="center">
+ <label class="align-right"
+ id="borderLabel"
+ control="border"
+ accesskey="&borderEditField.accessKey;"
+ value="&borderEditField.label;"/>
+ <textbox
+ class = "narrow"
+ id = "border"
+ oninput = "forceInteger(this.id)"/>
+ <label id="bordertypeLabel"
+ value="&pixelsPopup.value;" />
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <vbox>
+ <groupbox align="start">
+ <hbox class="groupbox-title">
+ <label id="alignLabel" class="header">&alignment.label;</label>
+ </hbox>
+ <menulist id="alignTypeSelect" class="align-menu">
+ <menupopup>
+ <menuitem class="align-menu menuitem-iconic"
+ value="top"
+ label="&topPopup.value;"/>
+ <menuitem class="align-menu menuitem-iconic"
+ value="middle"
+ label="&centerPopup.value;"/>
+ <menuitem class="align-menu menuitem-iconic"
+ value="bottom"
+ label="&bottomPopup.value;"/>
+ <!-- HTML attribute value is opposite of the button label on purpose -->
+ <menuitem class="align-menu menuitem-iconic"
+ value="right"
+ label="&wrapLeftPopup.value;"/>
+ <menuitem class="align-menu menuitem-iconic"
+ value="left"
+ label="&wrapRightPopup.value;"/>
+ </menupopup>
+ </menulist>
+ </groupbox>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label id="imagemapLabel" class="header">&imagemapBox.label;</label>
+ </hbox>
+ <hbox equalsize="always">
+ <button id="removeImageMap"
+ oncommand="removeImageMap()"
+ accesskey="&removeImageMapButton.accessKey;"
+ label="&removeImageMapButton.label;"
+ flex="1"/>
+ <spacer flex="1"/><!-- remove when we restore Image Map Editor -->
+ </hbox>
+ </groupbox>
+ </vbox>
+ </hbox>
diff --git a/comm/suite/editor/components/dialogs/jar.mn b/comm/suite/editor/components/dialogs/jar.mn
new file mode 100644
index 0000000000..705fe0068c
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/jar.mn
@@ -0,0 +1,82 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+comm.jar:
+ content/editor/EdAdvancedEdit.js (content/EdAdvancedEdit.js)
+ content/editor/EdAdvancedEdit.xhtml (content/EdAdvancedEdit.xhtml)
+ content/editor/EdAEAttributes.js (content/EdAEAttributes.js)
+ content/editor/EdAECSSAttributes.js (content/EdAECSSAttributes.js)
+ content/editor/EdAEHTMLAttributes.js (content/EdAEHTMLAttributes.js)
+ content/editor/EdAEJSEAttributes.js (content/EdAEJSEAttributes.js)
+ content/editor/EdButtonProps.js (content/EdButtonProps.js)
+ content/editor/EdButtonProps.xhtml (content/EdButtonProps.xhtml)
+ content/editor/EdColorPicker.js (content/EdColorPicker.js)
+ content/editor/EdColorPicker.xhtml (content/EdColorPicker.xhtml)
+ content/editor/EdColorProps.js (content/EdColorProps.js)
+ content/editor/EdColorProps.xhtml (content/EdColorProps.xhtml)
+ content/editor/EdConvertToTable.js (content/EdConvertToTable.js)
+ content/editor/EdConvertToTable.xhtml (content/EdConvertToTable.xhtml)
+ content/editor/EdDialogCommon.js (content/EdDialogCommon.js)
+ content/editor/EdDialogTemplate.js (content/EdDialogTemplate.js)
+ content/editor/EdDialogTemplate.xhtml (content/EdDialogTemplate.xhtml)
+ content/editor/EdDictionary.js (content/EdDictionary.js)
+ content/editor/EdDictionary.xhtml (content/EdDictionary.xhtml)
+ content/editor/EdFieldSetProps.js (content/EdFieldSetProps.js)
+ content/editor/EdFieldSetProps.xhtml (content/EdFieldSetProps.xhtml)
+ content/editor/EdFormProps.js (content/EdFormProps.js)
+ content/editor/EdFormProps.xhtml (content/EdFormProps.xhtml)
+ content/editor/EdHLineProps.js (content/EdHLineProps.js)
+ content/editor/EdHLineProps.xhtml (content/EdHLineProps.xhtml)
+ content/editor/edImage.inc.xhtml (content/edImage.inc.xhtml)
+ content/editor/EdImageDialog.js (content/EdImageDialog.js)
+ content/editor/EdImageLinkLoader.js (content/EdImageLinkLoader.js)
+ content/editor/EdImageProps.js (content/EdImageProps.js)
+* content/editor/EdImageProps.xhtml (content/EdImageProps.xhtml)
+ content/editor/EdInputImage.js (content/EdInputImage.js)
+* content/editor/EdInputImage.xhtml (content/EdInputImage.xhtml)
+ content/editor/EdInputProps.js (content/EdInputProps.js)
+ content/editor/EdInputProps.xhtml (content/EdInputProps.xhtml)
+ content/editor/EdInsertChars.js (content/EdInsertChars.js)
+ content/editor/EdInsertChars.xhtml (content/EdInsertChars.xhtml)
+ content/editor/EdInsertMath.js (content/EdInsertMath.js)
+ content/editor/EdInsertMath.xhtml (content/EdInsertMath.xhtml)
+ content/editor/EdInsertTable.js (content/EdInsertTable.js)
+ content/editor/EdInsertTable.xhtml (content/EdInsertTable.xhtml)
+ content/editor/EdInsertTOC.js (content/EdInsertTOC.js)
+ content/editor/EdInsertTOC.xhtml (content/EdInsertTOC.xhtml)
+ content/editor/EdInsSrc.js (content/EdInsSrc.js)
+ content/editor/EdInsSrc.xhtml (content/EdInsSrc.xhtml)
+ content/editor/EditConflict.js (content/EditConflict.js)
+ content/editor/EditConflict.xhtml (content/EditConflict.xhtml)
+ content/editor/EditorPublish.js (content/EditorPublish.js)
+ content/editor/EditorPublish.xhtml (content/EditorPublish.xhtml)
+ content/editor/EditorPublishOverlay.xhtml (content/EditorPublishOverlay.xhtml)
+ content/editor/EditorPublishProgress.js (content/EditorPublishProgress.js)
+ content/editor/EditorPublishProgress.xhtml (content/EditorPublishProgress.xhtml)
+ content/editor/EditorPublishSettings.js (content/EditorPublishSettings.js)
+ content/editor/EditorPublishSettings.xhtml (content/EditorPublishSettings.xhtml)
+ content/editor/EditorSaveAsCharset.js (content/EditorSaveAsCharset.js)
+ content/editor/EditorSaveAsCharset.xhtml (content/EditorSaveAsCharset.xhtml)
+ content/editor/EdLabelProps.js (content/EdLabelProps.js)
+ content/editor/EdLabelProps.xhtml (content/EdLabelProps.xhtml)
+ content/editor/EdLinkProps.js (content/EdLinkProps.js)
+ content/editor/EdLinkProps.xhtml (content/EdLinkProps.xhtml)
+ content/editor/EdListProps.js (content/EdListProps.js)
+ content/editor/EdListProps.xhtml (content/EdListProps.xhtml)
+ content/editor/EdNamedAnchorProps.js (content/EdNamedAnchorProps.js)
+ content/editor/EdNamedAnchorProps.xhtml (content/EdNamedAnchorProps.xhtml)
+ content/editor/EdPageProps.js (content/EdPageProps.js)
+ content/editor/EdPageProps.xhtml (content/EdPageProps.xhtml)
+ content/editor/EdReplace.js (content/EdReplace.js)
+ content/editor/EdReplace.xhtml (content/EdReplace.xhtml)
+ content/editor/EdSelectProps.js (content/EdSelectProps.js)
+ content/editor/EdSelectProps.xhtml (content/EdSelectProps.xhtml)
+ content/editor/EdSnapToGrid.js (content/EdSnapToGrid.js)
+ content/editor/EdSnapToGrid.xhtml (content/EdSnapToGrid.xhtml)
+ content/editor/EdSpellCheck.js (content/EdSpellCheck.js)
+ content/editor/EdSpellCheck.xhtml (content/EdSpellCheck.xhtml)
+ content/editor/EdTableProps.js (content/EdTableProps.js)
+ content/editor/EdTableProps.xhtml (content/EdTableProps.xhtml)
+ content/editor/EdTextAreaProps.js (content/EdTextAreaProps.js)
+ content/editor/EdTextAreaProps.xhtml (content/EdTextAreaProps.xhtml)
diff --git a/comm/suite/editor/components/dialogs/moz.build b/comm/suite/editor/components/dialogs/moz.build
new file mode 100644
index 0000000000..de5cd1bf81
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/moz.build
@@ -0,0 +1,6 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ["jar.mn"]
diff --git a/comm/suite/editor/components/moz.build b/comm/suite/editor/components/moz.build
new file mode 100644
index 0000000000..cc5a5d1960
--- /dev/null
+++ b/comm/suite/editor/components/moz.build
@@ -0,0 +1,10 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ "dialogs",
+ "prefs",
+ "texzilla",
+]
diff --git a/comm/suite/editor/components/prefs/content/editorPrefsOverlay.xhtml b/comm/suite/editor/components/prefs/content/editorPrefsOverlay.xhtml
new file mode 100644
index 0000000000..41112036ca
--- /dev/null
+++ b/comm/suite/editor/components/prefs/content/editorPrefsOverlay.xhtml
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<!DOCTYPE overlay [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % editorPrefsOverlayDTD SYSTEM "chrome://editor/locale/editorPrefsOverlay.dtd" >
+%editorPrefsOverlayDTD;
+]>
+
+<overlay id="editorPrefsOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <preferences id="appearance_preferences">
+ <preference id="general.startup.editor"
+ name="general.startup.editor"
+ type="bool"/>
+ </preferences>
+
+ <!-- editor startup toggle -->
+ <groupbox id="generalStartupPreferences">
+ <checkbox id="generalStartupEditor"
+ insertafter="generalStartupMail,generalStartupBrowser"
+ label="&editorCheck.label;"
+ accesskey="&editorCheck.accesskey;"
+ preference="general.startup.editor"/>
+ </groupbox>
+ <!-- category tree entries for editor -->
+ <treechildren id="prefsPanelChildren">
+ <treeitem container="true"
+ id="composerItem"
+ insertbefore="securityItem"
+ label="&compose.label;"
+ prefpane="composer_pane"
+ url="chrome://editor/content/pref-composer.xhtml"
+ helpTopic="composer_prefs_general">
+ <treechildren id="composerChildren">
+ <treeitem id="editingItem"
+ label="&editing.label;"
+ prefpane="editing_pane"
+ url="chrome://editor/content/pref-editing.xhtml"
+ helpTopic="composer_prefs_newpage"/>
+ </treechildren>
+ </treeitem>
+ </treechildren>
+
+</overlay>
diff --git a/comm/suite/editor/components/prefs/content/pref-composer.xhtml b/comm/suite/editor/components/prefs/content/pref-composer.xhtml
new file mode 100644
index 0000000000..79b4a1514c
--- /dev/null
+++ b/comm/suite/editor/components/prefs/content/pref-composer.xhtml
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay SYSTEM "chrome://editor/locale/pref-composer.dtd">
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+ <prefpane id="composer_pane" label="&pref.composer.title;">
+
+ <preferences id="composer_preferences">
+ <preference id="editor.history.url_maximum"
+ name="editor.history.url_maximum"
+ type="int"/>
+ <preference id="editor.prettyprint"
+ name="editor.prettyprint"
+ type="bool"
+ inverted="true"/>
+ <preference id="editor.save_associated_files"
+ name="editor.save_associated_files"
+ type="bool"/>
+ <preference id="editor.always_show_publish_dialog"
+ name="editor.always_show_publish_dialog"
+ type="bool"/>
+ <preference id="editor.table.maintain_structure"
+ name="editor.table.maintain_structure"
+ type="bool"/>
+ <preference id="editor.use_css"
+ name="editor.use_css"
+ type="bool"/>
+ <preference id="editor.CR_creates_new_p"
+ name="editor.CR_creates_new_p"
+ type="bool"/>
+ </preferences>
+
+ <!-- Recent files menu -->
+ <groupbox>
+ <label class="header">&recentFiles.title;</label>
+ <hbox align="center">
+ <label value="&documentsInMenu.label;"
+ accesskey="&documentsInMenu.accesskey;"
+ control="recentFiles"/>
+ <html:input id="recentFiles" type="number" class="size3"
+ min="0" max="99" value="10"
+ preference="editor.history.url_maximum"/>
+ </hbox>
+ </groupbox>
+
+ <!-- HTML formatting on output -->
+ <groupbox>
+ <label class="header">&savingFiles.title;</label>
+ <checkbox id="preserveFormatting"
+ label="&preserveExisting.label;"
+ accesskey="&preserveExisting.accesskey;"
+ tooltiptext="&preserveExisting.tooltip;"
+ preference="editor.prettyprint"/>
+ <checkbox id="saveAssociatedFiles"
+ label="&saveAssociatedFiles.label;"
+ accesskey="&saveAssociatedFiles.accesskey;"
+ preference="editor.save_associated_files"/>
+ <checkbox id="showPublishDialog"
+ label="&showPublishDialog.label;"
+ accesskey="&showPublishDialog.accesskey;"
+ preference="editor.always_show_publish_dialog"/>
+ </groupbox>
+
+ <groupbox align="start">
+ <label class="header">&composerEditing.label;</label>
+ <checkbox id="maintainTableStructure"
+ label="&maintainStructure.label;"
+ accesskey="&maintainStructure.accesskey;"
+ tooltiptext="&maintainStructure.tooltip;"
+ preference="editor.table.maintain_structure"/>
+ <checkbox id="useCSS"
+ label="&useCSS.label;"
+ accesskey="&useCSS.accesskey;"
+ preference="editor.use_css"/>
+ <checkbox id="crInPCreatesNewP"
+ label="&crInPCreatesNewP.label;"
+ accesskey="&crInPCreatesNewP.accesskey;"
+ preference="editor.CR_creates_new_p"/>
+ </groupbox>
+ </prefpane>
+</overlay>
diff --git a/comm/suite/editor/components/prefs/content/pref-editing.js b/comm/suite/editor/components/prefs/content/pref-editing.js
new file mode 100644
index 0000000000..61f4c1edef
--- /dev/null
+++ b/comm/suite/editor/components/prefs/content/pref-editing.js
@@ -0,0 +1,187 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const browserPrefsObserver =
+{
+ observe: function(aSubject, aTopic, aData)
+ {
+ if (aTopic != "nsPref:changed" || document.getElementById("editor.use_custom_colors").value)
+ return;
+
+ switch (aData)
+ {
+ case "browser.anchor_color":
+ SetColorPreview("linkText", Services.prefs.getCharPref(aData));
+ break;
+ case "browser.active_color":
+ SetColorPreview("activeLinkText", Services.prefs.getCharPref(aData));
+ break;
+ case "browser.visited_color":
+ SetColorPreview("visitedLinkText", Services.prefs.getCharPref(aData));
+ break;
+ default:
+ SetBgAndFgColors(Services.prefs.getBoolPref("browser.display.use_system_colors"))
+ }
+ }
+};
+
+function Startup()
+{
+ // Add browser prefs observers
+ Services.prefs.addObserver("browser.display.use_system_colors", browserPrefsObserver);
+ Services.prefs.addObserver("browser.display.foreground_color", browserPrefsObserver);
+ Services.prefs.addObserver("browser.display.background_color", browserPrefsObserver);
+ Services.prefs.addObserver("browser.anchor_color", browserPrefsObserver);
+ Services.prefs.addObserver("browser.active_color", browserPrefsObserver);
+ Services.prefs.addObserver("browser.visited_color", browserPrefsObserver);
+
+ // Add event listener so we can remove our observers
+ window.addEventListener("unload", WindowOnUnload, {capture: false, once: true});
+ UpdateDependent(document.getElementById("editor.use_custom_colors").value);
+}
+
+function GetColorAndUpdatePref(aType, aButtonID)
+{
+ // Don't allow a blank color, i.e., using the "default"
+ var colorObj = { NoDefault:true, Type:"", TextColor:0, PageColor:0, Cancel:false };
+ var preference = document.getElementById("editor." + aButtonID + "_color");
+
+ if (aButtonID == "background")
+ colorObj.PageColor = preference.value;
+ else
+ colorObj.TextColor = preference.value;
+
+ colorObj.Type = aType;
+
+ window.openDialog("chrome://editor/content/EdColorPicker.xhtml", "_blank", "chrome,close,titlebar,modal", "", colorObj);
+
+ // User canceled the dialog
+ if (colorObj.Cancel)
+ return;
+
+ // Update preference with picked color
+ if (aType == "Page")
+ preference.value = colorObj.BackgroundColor;
+ else
+ preference.value = colorObj.TextColor;
+}
+
+function UpdateDependent(aCustomEnabled)
+{
+ ToggleElements(aCustomEnabled);
+
+ if (aCustomEnabled)
+ { // Set current editor colors on preview and buttons
+ SetColors("textCW", "normalText", document.getElementById("editor.text_color").value);
+ SetColors("linkCW", "linkText", document.getElementById("editor.link_color").value);
+ SetColors("activeCW", "activeLinkText", document.getElementById("editor.active_link_color").value);
+ SetColors("visitedCW", "visitedLinkText", document.getElementById("editor.followed_link_color").value);
+ SetColors("backgroundCW", "ColorPreview", document.getElementById("editor.background_color").value);
+ }
+ else
+ { // Set current browser colors on preview
+ SetBgAndFgColors(Services.prefs.getBoolPref("browser.display.use_system_colors"));
+ SetColorPreview("linkText", Services.prefs.getCharPref("browser.anchor_color"));
+ SetColorPreview("activeLinkText", Services.prefs.getCharPref("browser.active_color"));
+ SetColorPreview("visitedLinkText", Services.prefs.getCharPref("browser.visited_color"));
+ }
+}
+
+function ToggleElements(aCustomEnabled)
+{
+ var buttons = document.getElementById("color-rows").getElementsByTagName("button");
+
+ for (var i = 0; i < buttons.length; i++)
+ {
+ let isLocked = CheckLocked(buttons[i].id);
+ buttons[i].disabled = !aCustomEnabled || isLocked;
+ buttons[i].previousSibling.disabled = !aCustomEnabled || isLocked;
+ buttons[i].firstChild.setAttribute("default", !aCustomEnabled || isLocked);
+ }
+}
+
+function CheckLocked(aButtonID)
+{
+ return document.getElementById("editor." + aButtonID + "_color").locked;
+}
+
+// Updates preview and button color when a editor color pref change
+function UpdateColors(aColorWellID, aPreviewID, aColor)
+{
+ // Only show editor colors from prefs if we're in custom mode
+ if (!document.getElementById("editor.use_custom_colors").value)
+ return;
+
+ SetColors(aColorWellID, aPreviewID, aColor)
+}
+
+function SetColors(aColorWellID, aPreviewID, aColor)
+{
+ SetColorWell(aColorWellID, aColor);
+ SetColorPreview(aPreviewID, aColor);
+}
+
+function SetColorWell(aColorWellID, aColor)
+{
+ document.getElementById(aColorWellID).style.backgroundColor = aColor;
+}
+
+function SetColorPreview(aPreviewID, aColor)
+{
+ if (aPreviewID == "ColorPreview")
+ document.getElementById(aPreviewID).style.backgroundColor = aColor;
+ else
+ document.getElementById(aPreviewID).style.color = aColor;
+}
+
+function UpdateBgImagePreview(aImage)
+{
+ var colorPreview = document.getElementById("ColorPreview");
+ colorPreview.style.backgroundImage = aImage && "url(" + aImage + ")";
+}
+
+// Sets browser background/foreground colors
+function SetBgAndFgColors(aSysPrefEnabled)
+{
+ if (aSysPrefEnabled)
+ { // Use system colors
+ SetColorPreview("normalText", "windowtext");
+ SetColorPreview("ColorPreview", "window");
+ }
+ else
+ {
+ SetColorPreview("normalText", Services.prefs.getCharPref("browser.display.foreground_color"));
+ SetColorPreview("ColorPreview", Services.prefs.getCharPref("browser.display.background_color"));
+ }
+}
+
+function ChooseImageFile()
+{
+ const nsIFilePicker = Ci.nsIFilePicker;
+ var fp = Cc["@mozilla.org/filepicker;1"]
+ .createInstance(nsIFilePicker);
+ var editorBundle = document.getElementById("bundle_editor");
+ var title = editorBundle.getString("SelectImageFile");
+ fp.init(window, title, nsIFilePicker.modeOpen);
+ fp.appendFilters(nsIFilePicker.filterImages);
+ fp.open(rv => {
+ if (rv != nsIFilePicker.returnOK || !fp.file) {
+ return;
+ }
+ document.getElementById("editor.default_background_image").value = fp.fileURL.spec;
+ let textbox = document.getElementById("backgroundImageInput");
+ textbox.focus();
+ textbox.select();
+ });
+}
+
+function WindowOnUnload()
+{
+ Services.prefs.removeObserver("browser.display.use_system_colors", browserPrefsObserver, false);
+ Services.prefs.removeObserver("browser.display.foreground_color", browserPrefsObserver, false);
+ Services.prefs.removeObserver("browser.display.background_color", browserPrefsObserver, false);
+ Services.prefs.removeObserver("browser.anchor_color", browserPrefsObserver, false);
+ Services.prefs.removeObserver("browser.active_color", browserPrefsObserver, false);
+ Services.prefs.removeObserver("browser.visited_color", browserPrefsObserver, false);
+}
diff --git a/comm/suite/editor/components/prefs/content/pref-editing.xhtml b/comm/suite/editor/components/prefs/content/pref-editing.xhtml
new file mode 100644
index 0000000000..140ff320ce
--- /dev/null
+++ b/comm/suite/editor/components/prefs/content/pref-editing.xhtml
@@ -0,0 +1,181 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://editor/locale/pref-editing.dtd">
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+ <prefpane id="editing_pane"
+ label="&pref.editing.title;"
+ script="chrome://editor/content/pref-editing.js">
+
+ <preferences id="editing_preferences">
+ <preference id="editor.author"
+ name="editor.author"
+ type="string"/>
+ <preference id="editor.use_custom_colors"
+ name="editor.use_custom_colors"
+ type="bool"
+ onchange="UpdateDependent(this.value);"/>
+ <preference id="editor.text_color"
+ name="editor.text_color"
+ type="string"
+ onchange="UpdateColors('textCW', 'normalText', this.value);"/>
+ <preference id="editor.link_color"
+ name="editor.link_color"
+ type="string"
+ onchange="UpdateColors('linkCW', 'linkText', this.value);"/>
+ <preference id="editor.active_link_color"
+ name="editor.active_link_color"
+ type="string"
+ onchange="UpdateColors('activeCW', 'activeLinkText', this.value);"/>
+ <preference id="editor.followed_link_color"
+ name="editor.followed_link_color"
+ type="string"
+ onchange="UpdateColors('visitedCW', 'visitedLinkText', this.value);"/>
+ <preference id="editor.background_color"
+ name="editor.background_color"
+ type="string"
+ onchange="UpdateColors('backgroundCW', 'ColorPreview', this.value);"/>
+ <preference id="editor.default_background_image"
+ name="editor.default_background_image"
+ type="string"
+ onchange="UpdateBgImagePreview(this.value);"/>
+ </preferences>
+
+ <stringbundle id="bundle_editor"
+ src="chrome://editor/locale/editor.properties"/>
+
+ <vbox>
+ <label value="&authorName.label;"
+ accesskey="&authorName.accesskey;"
+ control="editorAuthor">
+ </label>
+ <hbox>
+ <textbox id="editorAuthor"
+ flex="1"
+ preference="editor.author"/>
+ <spacer flex="1"/>
+ </hbox>
+ </vbox>
+ <spacer class="smallspacer"/>
+ <groupbox align="start">
+ <label class="header">&pageColorHeader;</label>
+ <radiogroup id="useCustomColors"
+ preference="editor.use_custom_colors">
+ <radio id="defaultColorsRadio"
+ value="false"
+ label="&defaultColors.label;"
+ accesskey="&defaultColors.accesskey;"/>
+ <radio id="customColorsRadio"
+ value="true"
+ label="&customColors.label;"
+ accesskey="&customColors.accesskey;"/>
+ </radiogroup>
+ <hbox class="indent">
+ <grid>
+ <columns><column/><column/></columns>
+ <rows id="color-rows">
+ <row align="center">
+ <label id="textLabel"
+ value="&normalText.label;&colon.character;"
+ accesskey="&normalText.accesskey;"
+ control="text"/>
+ <button id="text"
+ class="color-button"
+ oncommand="GetColorAndUpdatePref('Text', 'text');">
+ <spacer id="textCW" class="color-well"/>
+ </button>
+ </row>
+ <row align="center">
+ <label id="linkLabel"
+ value="&linkText.label;&colon.character;"
+ accesskey="&linkText.accesskey;"
+ control="link"/>
+ <button id="link"
+ class="color-button"
+ oncommand="GetColorAndUpdatePref('Link', 'link');">
+ <spacer id="linkCW" class="color-well"/>
+ </button>
+ </row>
+ <row align="center">
+ <label id="activeLinkLabel"
+ value="&activeLinkText.label;&colon.character;"
+ accesskey="&activeLinkText.accesskey;"
+ control="active_link"/>
+ <button id="active_link"
+ class="color-button"
+ oncommand="GetColorAndUpdatePref('ActiveLink', 'active_link');">
+ <spacer id="activeCW" class="color-well"/>
+ </button>
+ </row>
+ <row align="center">
+ <label id="visitedLinkLabel"
+ value ="&visitedLinkText.label;&colon.character;"
+ accesskey="&visitedLinkText.accesskey;"
+ control="followed_link"/>
+ <button id="followed_link"
+ class="color-button"
+ oncommand="GetColorAndUpdatePref('VisitedLink', 'followed_link');">
+ <spacer id="visitedCW" class="color-well"/>
+ </button>
+ </row>
+ <row align="center">
+ <label id="backgroundLabel"
+ value="&background.label;"
+ accesskey="&background.accesskey;"
+ control="background"/>
+ <button id="background"
+ class="color-button"
+ oncommand="GetColorAndUpdatePref('Page', 'background');">
+ <spacer id="backgroundCW" class="color-well"/>
+ </button>
+ </row>
+ </rows>
+ </grid>
+ <vbox id="ColorPreview"
+ flex="1">
+ <spacer flex="1"/>
+ <label id="normalText"
+ class="larger"
+ value="&normalText.label;"/>
+ <spacer flex="1"/>
+ <label id="linkText"
+ class="larger"
+ value="&linkText.label;"/>
+ <spacer flex="1"/>
+ <label id="activeLinkText"
+ class="larger"
+ value="&activeLinkText.label;"/>
+ <spacer flex="1"/>
+ <label id="visitedLinkText"
+ class="larger"
+ value="&visitedLinkText.label;"/>
+ <spacer flex="1"/>
+ </vbox>
+ <spacer flex="1"/>
+ </hbox>
+ <spacer class="spacer"/>
+ <label id="backgroundImageLabel"
+ value="&backgroundImage.label;"
+ accesskey="&backgroundImage.accesskey;"
+ control="backgroundImageInput">
+ </label>
+ <hbox align="center">
+ <textbox id="backgroundImageInput"
+ class="uri-element"
+ preference="editor.default_background_image"
+ style="min-width: 23em;"
+ flex="1"/>
+ <button label="&chooseFile.label;"
+ accesskey="&chooseFile.accesskey;"
+ oncommand="ChooseImageFile();">
+ <observes element="backgroundImageInput" attribute="disabled"/>
+ </button>
+ </hbox>
+ </groupbox>
+ </prefpane>
+</overlay>
diff --git a/comm/suite/editor/components/prefs/jar.mn b/comm/suite/editor/components/prefs/jar.mn
new file mode 100644
index 0000000000..308da7340c
--- /dev/null
+++ b/comm/suite/editor/components/prefs/jar.mn
@@ -0,0 +1,11 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+comm.jar:
+% overlay chrome://communicator/content/pref/preferences.xhtml chrome://editor/content/editorPrefsOverlay.xhtml
+% overlay chrome://communicator/content/pref/pref-appearance.xhtml chrome://editor/content/editorPrefsOverlay.xhtml
+ content/editor/editorPrefsOverlay.xhtml (content/editorPrefsOverlay.xhtml)
+ content/editor/pref-composer.xhtml (content/pref-composer.xhtml)
+ content/editor/pref-editing.js (content/pref-editing.js)
+ content/editor/pref-editing.xhtml (content/pref-editing.xhtml)
diff --git a/comm/suite/editor/components/prefs/moz.build b/comm/suite/editor/components/prefs/moz.build
new file mode 100644
index 0000000000..de5cd1bf81
--- /dev/null
+++ b/comm/suite/editor/components/prefs/moz.build
@@ -0,0 +1,6 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ["jar.mn"]
diff --git a/comm/suite/editor/components/texzilla/content/TeXZilla.js b/comm/suite/editor/components/texzilla/content/TeXZilla.js
new file mode 100644
index 0000000000..0f8e3e5b29
--- /dev/null
+++ b/comm/suite/editor/components/texzilla/content/TeXZilla.js
@@ -0,0 +1,339 @@
+/* THIS IS A GENERATED FILE. DO NOT EDIT THIS DIRECTLY. */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+(function() {
+"using strict";
+var nb=void 0,tb=!0,xb=null,yb=!1,zb=function(){function c(b,a,c){var $a;c=c||{};for($a=b.length;$a--;c[b[$a]]=a);return c}function Fb(b){return b.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function sb(b){b="negativeveryverythinmathspace negativeverythinmathspace negativemediummathspace negativethickmathspace negativeverythickmathspace negativeveryverythickmathspace veryverythinmathspace verythinmathspace thinmathspace mediummathspace thickmathspace verythickmathspace veryverythickmathspace".split(" ").indexOf(b);
+return(-1===b?0:b-6)/18}function tc(b){b=b.trim();var a=/(-?[0-9]*(?:[0-9]\.?|\.[0-9])[0-9]*)(e[mx]|in|cm|mm|p[xtc]|%)?/.exec(b);return a?(a[1]=parseFloat(a[1]),a[2]||(a[1]*=100,a[2]="%"),{i:a[1],k:a[2]}):{i:sb(b),k:"em"}}function Pb(b){var a="<"+b.tag,c;for(c in b.attributes)b.attributes[c]!==nb&&(a+=" "+c+'="'+b.attributes[c]+'"');b.content?(a+=">",Array.isArray(b.content)?b.content.forEach(function(b){a+=Pb(b)}):a+=b.content,a+="</"+b.tag+">"):a+="/>";return a}function e(b,a,c){return{tag:b,content:a,
+attributes:c}}function ab(b,a,c){return e("mo",Fb(b),{lspace:a!==nb?a+"em":nb,rspace:c!==nb?c+"em":nb})}function Ub(b,a){return e("mi",Fb(b),a?{mathvariant:"normal"}:nb)}function Gb(b){return e("mspace",xb,{width:b+"em"})}function uc(b,a){var c="bold italic bold-italic script bold-script fraktur double-struck bold-fraktur sans-serif bold-sans-serif sans-serif-italic sans-serif-bold-italic monospace initial tailed looped stretched".split(" ").indexOf(a);if(930==b)return b;if(988==b)return 0==c?120778:
+b;if(989==b)return 0==c?120779:b;if(305==b)return 1==c?120484:b;if(567==b)return 1==c?120485:b;var $a;if(65<=b&&90>=b||97<=b&&122>=b){if(12<c)return b;c=(90>=b?b-65:26+b-97)+119808+52*c;$a={119893:8462,119965:8492,119968:8496,119969:8497,119971:8459,119972:8464,119975:8466,119976:8499,119981:8475,119994:8495,119996:8458,120004:8500,120070:8493,120075:8460,120076:8465,120085:8476,120093:8488,120122:8450,120127:8461,120133:8469,120135:8473,120136:8474,120137:8477,120145:8484};return $a[c]?$a[c]:c}if(48<=
+b&&57>=b){switch(c){case 0:c=0;break;case 6:c=1;break;case 8:c=2;break;case 9:c=3;break;case 12:c=4;break;default:return b}return b-48+10*c+120782}if(1536<=b&&1791>=b){switch(c){case 13:$a={1576:126497,1578:126517,1579:126518,1580:126498,1581:126503,1582:126519,1587:126510,1588:126516,1589:126513,1590:126521,1593:126511,1594:126523,1601:126512,1602:126514,1603:126506,1604:126507,1605:126508,1606:126509,1607:126500,1610:126505};break;case 14:$a={1580:126530,1581:126535,1582:126551,1587:126542,1588:126548,
+1589:126545,1590:126553,1593:126543,1594:126555,1602:126546,1604:126539,1606:126541,1610:126537,1647:126559,1722:126557};break;case 16:$a={1576:126561,1578:126581,1579:126582,1580:126562,1581:126567,1582:126583,1587:126574,1588:126580,1589:126577,1590:126585,1591:126568,1592:126586,1593:126575,1594:126587,1601:126576,1602:126578,1603:126570,1605:126572,1606:126573,1607:126564,1610:126569,1646:126588,1697:126590};break;case 15:$a={1575:126592,1576:126593,1578:126613,1579:126614,1580:126594,1581:126599,
+1582:126615,1583:126595,1584:126616,1585:126611,1586:126598,1587:126606,1588:126612,1589:126609,1590:126617,1591:126600,1592:126618,1593:126607,1594:126619,1601:126608,1602:126610,1604:126603,1605:126604,1606:126605,1607:126596,1608:126597,1610:126601};break;case 6:$a={1576:126625,1578:126645,1579:126646,1580:126626,1581:126631,1582:126647,1583:126627,1584:126648,1585:126643,1586:126630,1587:126638,1588:126644,1589:126641,1590:126649,1591:126632,1592:126650,1593:126639,1594:126651,1601:126640,1602:126642,
+1604:126635,1605:126636,1606:126637,1608:126629,1610:126633};break;default:return b}return $a[b]?$a[b]:b}if(913<=b&&937>=b)$a=b-913;else if(945<=b&&969>=b)$a=26+b-945;else switch(b){case 1012:$a=17;break;case 8711:$a=25;break;case 8706:$a=51;break;case 1013:$a=52;break;case 977:$a=53;break;case 1008:$a=54;break;case 981:$a=55;break;case 1009:$a=56;break;case 982:$a=57;break;default:return b}switch(c){case 0:c=0;break;case 1:c=1;break;case 2:c=2;break;case 9:c=3;break;case 11:c=4;break;default:return b}return $a+
+120488+58*c}function vc(b,a){var c=tb,$a;for($a in a)-1!==["mathcolor","mathbackground","mathvariant"].indexOf($a)?"mathvariant"!==$a&&1!=b.length?c=yb:b.forEach(function(b){if(-1!==["mi","mn","mo","mtext","ms"].indexOf(b.tag)){if(b.attributes||(b.attributes={}),!b.attributes[$a])if("mathvariant"===$a){var d;if(!(d="normal"!==a[$a])){if("mi"!==b.tag)d=yb;else{d=b.content;var e=d.codePointAt(0);d=1===d.length&&65535>=e||2===d.length&&65535<e}d=!d}if(d){if(d=a[$a],"normal"!==d){for(var e=b.content,
+g="",m=0;m<e.length;m++){var s=e.codePointAt(m);65535<s?(g+=e[m],m++,g+=e[m]):g+=String.fromCodePoint(uc(s,d))}b.content=g}}else b.attributes[$a]=a[$a]}else b.attributes[$a]=a[$a]}else c=yb}):c=yb;return c}function db(b,a,c){a=a||"mrow";if("mstyle"===a){if(1==b.length&&"mrow"===b[0].tag&&!b[0].attributes)return db(b[0].content,a,c);if(vc(b,c))return db(b)}return 1==b.length&&"mrow"===a&&!c?b[0]:e(a,b,c)}function Ib(b,a,c,$a){return e("math",[e("semantics",[db(b),e("annotation",Fb($a),{encoding:"TeX"})])],
+{xmlns:Qb,display:a?"block":nb,dir:c?"rtl":nb})}function Vb(b){if(!b||b.namespaceURI!==Qb)return xb;if("semantics"===b.tagName)for(b=b.firstElementChild;b;b=b.nextElementSibling){if(b.namespaceURI===Qb&&"annotation"===b.localName&&-1!==wc.indexOf(b.getAttribute("encoding")))return b.textContent}else if(1===b.childElementCount)return Vb(b.firstElementChild);return xb}function xc(b){for(var a="",c,$a,e=0;e<b.length;e++)c=b.charCodeAt(e),128>c?a+=b.charAt(e):55296<=c&&56319>=c?(e++,$a=b.charCodeAt(e),
+a+="&#x"+(1024*(c-55296)+$a-56320+65536).toString(16)+";"):a+="&#x"+c.toString(16)+";";return a}function Rb(){this.e={}}var Wb=[1,4],Xb=[1,6],Yb=[1,7],Zb=[1,8],$b=[1,9],Ab=[68,195,198,200,202,204],m=[1,27],s=[1,124],v=[1,52],x=[1,48],h=[1,28],q=[1,29],p=[1,30],y=[1,31],f=[1,32],u=[1,33],n=[1,34],k=[1,35],r=[1,37],t=[1,38],l=[1,39],w=[1,40],z=[1,41],A=[1,42],B=[1,43],C=[1,44],D=[1,45],E=[1,46],F=[1,47],G=[1,49],H=[1,50],I=[1,51],J=[1,53],K=[1,54],L=[1,55],M=[1,56],N=[1,57],O=[1,58],P=[1,59],Q=[1,60],
+R=[1,61],S=[1,62],T=[1,63],U=[1,64],V=[1,65],W=[1,66],X=[1,67],Y=[1,68],Z=[1,69],$=[1,70],aa=[1,71],ba=[1,72],ca=[1,73],da=[1,74],ea=[1,75],fa=[1,76],ga=[1,77],ha=[1,78],ia=[1,79],ja=[1,80],ka=[1,81],la=[1,82],ma=[1,83],na=[1,84],oa=[1,85],pa=[1,86],qa=[1,87],ra=[1,88],sa=[1,89],ta=[1,90],ua=[1,91],va=[1,92],wa=[1,93],xa=[1,94],ya=[1,95],za=[1,96],Aa=[1,97],Ba=[1,98],Ca=[1,99],Da=[1,100],Ea=[1,101],Fa=[1,102],Ga=[1,103],Ha=[1,104],Ia=[1,105],Ja=[1,106],Ka=[1,107],eb=[1,24],La=[1,108],Ma=[1,109],Na=
+[1,110],Oa=[1,111],Pa=[1,112],Qa=[1,113],Ra=[1,114],Sa=[1,115],Ta=[1,116],Ua=[1,117],Va=[1,118],Wa=[1,119],Xa=[1,120],Ya=[1,121],bb=[1,122],cb=[1,123],fb=[1,16],gb=[1,17],hb=[1,18],ib=[1,19],jb=[1,20],kb=[1,21],lb=[1,22],ac=[6,10,53,64,65,66,144,146,148,150,152,154,156,158,160,162,164,189,192,199,201,203,205],Db=[8,49,50,51,56,57,58,59,60,61,62,63,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,
+114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,139,141,142,145,147,149,151,153,155,157,159,161,163,165,166,173,174,179,180,181,182,183,184,185],mb=[1,134],ub=[6,8,10,49,50,51,53,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,139,
+141,142,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,173,174,189,192,199,201,203,205],Za=[1,137],g=[6,8,10,49,50,51,53,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,138,139,141,142,144,145,146,147,148,149,150,151,152,153,154,
+155,156,157,158,159,160,161,162,163,164,165,166,169,170,171,173,174,189,192,199,201,203,205],Sb=[1,161],pb=[2,197],qb=[1,217],vb=[1,214],Eb=[6,8,10,49,50,51,53,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,139,141,142,144,145,146,147,148,149,150,151,152,153,154,155,156,
+157,158,159,160,161,162,163,164,165,166,169,170,173,174,189,192,199,201,203,205],bc=[1,241],Bb=[1,243],Cb=[1,244],Mb=[1,259],cc=[4,8],dc=[1,275],ec=[8,49,50,51,56,57,58,59,60,61,62,63,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,138,139,141,142,145,147,149,151,153,155,157,159,161,163,165,166],wb=[1,286],
+Nb=[10,144,146,148,150,152,154,156,158,160,162,164,192],fc=[1,288],Jb=[10,144,146,148,150,152,154,156,158,160,162,164,189,192],gc=[164,189,192],Tb=[10,189,192],Kb=[1,343],Lb=[1,344],hc=[1,352],ic=[1,353],jc=[4,8,49,50,51,56,57,58,59,60,61,62,63,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,139,141,142,
+145,147,149,151,153,155,157,159,161,163,165,166],Ob=[10,21,23],Hb=[10,21,23,25,27],kc=[1,399],lc=[1,400],mc=[1,401],nc=[1,402],oc=[1,403],pc=[1,404],qc=[1,405],rc=[1,406],sc=[10,19,21,23,25,27,29,31,33,35,37,39,41],ob=[10,19,21,23,29,31,33,35,37,39,41],rb={trace:function(){},e:{},la:{error:2,textOptArg:3,"[":4,TEXTOPTARG:5,"]":6,textArg:7,"{":8,TEXTARG:9,"}":10,lengthOptArg:11,lengthArg:12,attrOptArg:13,attrArg:14,tokenContent:15,arrayAlign:16,columnAlign:17,collayout:18,COLLAYOUT:19,colalign:20,
+COLALIGN:21,rowalign:22,ROWALIGN:23,rowspan:24,ROWSPAN:25,colspan:26,COLSPAN:27,align:28,ALIGN:29,eqrows:30,EQROWS:31,eqcols:32,EQCOLS:33,rowlines:34,ROWLINES:35,collines:36,COLLINES:37,frame:38,FRAME:39,padding:40,PADDING:41,cellopt:42,celloptList:43,rowopt:44,arrayopt:45,arrayoptList:46,rowoptList:47,left:48,LEFT:49,OPFS:50,".":51,right:52,RIGHT:53,closedTerm:54,styledExpression:55,BIG:56,BBIG:57,BIGG:58,BBIGG:59,BIGL:60,BBIGL:61,BIGGL:62,BBIGGL:63,TEXATOP:64,TEXOVER:65,TEXCHOOSE:66,NUM:67,TEXT:68,
+A:69,AILL:70,AIUL:71,AILG:72,AIUG:73,F:74,MI:75,MN:76,MO:77,OP:78,OPS:79,OPAS:80,MS:81,MTEXT:82,HIGH_SURROGATE:83,LOW_SURROGATE:84,BMP_CHARACTER:85,OPERATORNAME:86,MATHOP:87,MATHBIN:88,MATHREL:89,FRAC:90,ROOT:91,SQRT:92,UNDERSET:93,OVERSET:94,UNDEROVERSET:95,XARROW:96,MATHRLAP:97,MATHLLAP:98,MATHCLAP:99,PHANTOM:100,TFRAC:101,BINOM:102,TBINOM:103,PMOD:104,UNDERBRACE:105,UNDERLINE:106,OVERBRACE:107,ACCENT:108,ACCENTNS:109,BOXED:110,SLASH:111,QUAD:112,QQUAD:113,NEGSPACE:114,NEGMEDSPACE:115,NEGTHICKSPACE:116,
+THINSPACE:117,MEDSPACE:118,THICKSPACE:119,SPACE:120,MATHRAISEBOX:121,MATHBB:122,MATHBF:123,MATHBIT:124,MATHSCR:125,MATHBSCR:126,MATHSF:127,MATHFRAK:128,MATHIT:129,MATHTT:130,MATHRM:131,HREF:132,STATUSLINE:133,TOOLTIP:134,TOGGLE:135,BTOGGLE:136,closedTermList:137,ETOGGLE:138,TENSOR:139,subsupList:140,MULTI:141,BMATRIX:142,tableRowList:143,EMATRIX:144,BGATHERED:145,EGATHERED:146,BPMATRIX:147,EPMATRIX:148,BBMATRIX:149,EBMATRIX:150,BVMATRIX:151,EVMATRIX:152,BBBMATRIX:153,EBBMATRIX:154,BVVMATRIX:155,EVVMATRIX:156,
+BSMALLMATRIX:157,ESMALLMATRIX:158,BCASES:159,ECASES:160,BALIGNED:161,EALIGNED:162,BARRAY:163,EARRAY:164,SUBSTACK:165,ARRAY:166,ARRAYOPTS:167,compoundTerm:168,_:169,"^":170,OPP:171,opm:172,OPM:173,FM:174,compoundTermList:175,subsupTermScript:176,subsupTerm:177,textstyle:178,DISPLAYSTYLE:179,TEXTSTYLE:180,TEXTSIZE:181,SCRIPTSIZE:182,SCRIPTSCRIPTSIZE:183,COLOR:184,BGCOLOR:185,tableCell:186,CELLOPTS:187,tableCellList:188,COLSEP:189,tableRow:190,ROWOPTS:191,ROWSEP:192,document:193,documentItemList:194,
+EOF:195,documentItem:196,mathItem:197,STARTMATH0:198,ENDMATH0:199,STARTMATH1:200,ENDMATH1:201,STARTMATH2:202,ENDMATH2:203,STARTMATH3:204,ENDMATH3:205,$accept:0,$end:1},z:{2:"error",4:"[",5:"TEXTOPTARG",6:"]",8:"{",9:"TEXTARG",10:"}",19:"COLLAYOUT",21:"COLALIGN",23:"ROWALIGN",25:"ROWSPAN",27:"COLSPAN",29:"ALIGN",31:"EQROWS",33:"EQCOLS",35:"ROWLINES",37:"COLLINES",39:"FRAME",41:"PADDING",49:"LEFT",50:"OPFS",51:".",53:"RIGHT",56:"BIG",57:"BBIG",58:"BIGG",59:"BBIGG",60:"BIGL",61:"BBIGL",62:"BIGGL",63:"BBIGGL",
+64:"TEXATOP",65:"TEXOVER",66:"TEXCHOOSE",67:"NUM",68:"TEXT",69:"A",70:"AILL",71:"AIUL",72:"AILG",73:"AIUG",74:"F",75:"MI",76:"MN",77:"MO",78:"OP",79:"OPS",80:"OPAS",81:"MS",82:"MTEXT",83:"HIGH_SURROGATE",84:"LOW_SURROGATE",85:"BMP_CHARACTER",86:"OPERATORNAME",87:"MATHOP",88:"MATHBIN",89:"MATHREL",90:"FRAC",91:"ROOT",92:"SQRT",93:"UNDERSET",94:"OVERSET",95:"UNDEROVERSET",96:"XARROW",97:"MATHRLAP",98:"MATHLLAP",99:"MATHCLAP",100:"PHANTOM",101:"TFRAC",102:"BINOM",103:"TBINOM",104:"PMOD",105:"UNDERBRACE",
+106:"UNDERLINE",107:"OVERBRACE",108:"ACCENT",109:"ACCENTNS",110:"BOXED",111:"SLASH",112:"QUAD",113:"QQUAD",114:"NEGSPACE",115:"NEGMEDSPACE",116:"NEGTHICKSPACE",117:"THINSPACE",118:"MEDSPACE",119:"THICKSPACE",120:"SPACE",121:"MATHRAISEBOX",122:"MATHBB",123:"MATHBF",124:"MATHBIT",125:"MATHSCR",126:"MATHBSCR",127:"MATHSF",128:"MATHFRAK",129:"MATHIT",130:"MATHTT",131:"MATHRM",132:"HREF",133:"STATUSLINE",134:"TOOLTIP",135:"TOGGLE",136:"BTOGGLE",138:"ETOGGLE",139:"TENSOR",141:"MULTI",142:"BMATRIX",144:"EMATRIX",
+145:"BGATHERED",146:"EGATHERED",147:"BPMATRIX",148:"EPMATRIX",149:"BBMATRIX",150:"EBMATRIX",151:"BVMATRIX",152:"EVMATRIX",153:"BBBMATRIX",154:"EBBMATRIX",155:"BVVMATRIX",156:"EVVMATRIX",157:"BSMALLMATRIX",158:"ESMALLMATRIX",159:"BCASES",160:"ECASES",161:"BALIGNED",162:"EALIGNED",163:"BARRAY",164:"EARRAY",165:"SUBSTACK",166:"ARRAY",167:"ARRAYOPTS",169:"_",170:"^",171:"OPP",173:"OPM",174:"FM",179:"DISPLAYSTYLE",180:"TEXTSTYLE",181:"TEXTSIZE",182:"SCRIPTSIZE",183:"SCRIPTSCRIPTSIZE",184:"COLOR",185:"BGCOLOR",
+187:"CELLOPTS",189:"COLSEP",191:"ROWOPTS",192:"ROWSEP",195:"EOF",198:"STARTMATH0",199:"ENDMATH0",200:"STARTMATH1",201:"ENDMATH1",202:"STARTMATH2",203:"ENDMATH2",204:"STARTMATH3",205:"ENDMATH3"},W:[0,[3,3],[7,3],[11,3],[12,3],[13,1],[14,1],[15,1],[16,1],[17,1],[18,2],[20,2],[22,2],[24,2],[26,2],[28,2],[30,2],[32,2],[34,2],[36,2],[38,2],[40,2],[42,1],[42,1],[42,1],[42,1],[43,1],[43,2],[44,1],[44,1],[45,1],[45,1],[45,1],[45,1],[45,1],[45,1],[45,1],[45,1],[45,1],[45,1],[46,1],[46,2],[47,1],[47,2],[48,
+2],[48,2],[52,2],[52,2],[54,2],[54,3],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,3],[54,5],[54,5],[54,5],[54,5],[54,5],[54,5],[54,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,2],[54,2],[54,2],[54,1],[54,1],[54,1],[54,1],[54,1],[54,2],[54,4],[54,2],[54,2],[54,1],[54,2],[54,2],[54,2],[54,2],[54,3],[54,3],[54,2],[54,5],[54,3],[54,3],[54,4],[54,5],[54,2],[54,2],[54,2],[54,2],[54,2],[54,3],[54,3],[54,3],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,1],[54,1],[54,
+1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,4],[54,5],[54,4],[54,3],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,2],[54,3],[54,3],[54,3],[54,3],[54,3],[54,5],[54,8],[54,7],[54,7],[54,3],[54,3],[54,3],[54,3],[54,3],[54,3],[54,3],[54,3],[54,3],[54,3],[54,5],[54,4],[54,4],[54,4],[54,8],[137,1],[137,2],[168,3],[168,5],[168,4],[168,5],[168,4],[168,3],[168,3],[168,2],[168,1],[168,5],[168,5],[168,3],[168,3],[168,1],[172,1],[172,1],[175,1],[175,2],[176,1],[176,1],[177,4],[177,2],[177,
+2],[177,3],[140,1],[140,2],[178,1],[178,1],[178,1],[178,1],[178,1],[178,2],[178,2],[55,2],[55,1],[186,0],[186,5],[186,1],[188,1],[188,3],[190,5],[190,1],[143,1],[143,3],[193,2],[194,1],[194,2],[196,1],[196,1],[197,2],[197,3],[197,2],[197,3],[197,3],[197,3]],H:function(b,a,c,$a,g,d){b=d.length-1;switch(g){case 1:this.b=d[b-1].replace(/\\[\\\]]/g,function(a){return a.slice(1)});this.b=Fb(this.b);break;case 2:this.b=d[b-1].replace(/\\[\\\}]/g,function(a){return a.slice(1)});this.b=Fb(this.b);break;case 3:case 4:this.b=
+tc(d[b-1]);break;case 5:case 6:this.b=d[b].replace(/"/g,"&#x22;");break;case 7:this.b=d[b].replace(/\s+/g," ").replace(/^ | $/g," ");break;case 8:d[b]=d[b].trim();if("t"===d[b])this.b="axis 1";else if("c"===d[b])this.b="center";else if("b"===d[b])this.b="axis -1";else throw"Unknown array alignment";break;case 9:this.b="";d[b]=d[b].replace(/\s+/g,"");for($a=0;$a<d[b].length;$a++)"c"===d[b][$a]?this.b+=" center":"l"===d[b][$a]?this.b+=" left":"r"===d[b][$a]&&(this.b+=" right");if(this.b.length)this.b=
+this.b.slice(1);else throw"Invalid column alignments";break;case 10:case 11:this.b={columnalign:d[b]};break;case 12:this.b={rowalign:d[b]};break;case 13:this.b={rowspan:d[b]};break;case 14:this.b={colspan:d[b]};break;case 15:this.b={align:d[b]};break;case 16:this.b={equalrows:d[b]};break;case 17:this.b={equalcolumns:d[b]};break;case 18:this.b={rowlines:d[b]};break;case 19:this.b={columnlines:d[b]};break;case 20:this.b={frame:d[b]};break;case 21:this.b={rowspacing:d[b],columnspacing:d[b]};break;case 22:case 23:case 24:case 25:case 26:case 28:case 29:case 30:case 31:case 32:case 33:case 34:case 35:case 36:case 37:case 38:case 39:case 40:case 42:case 170:case 175:case 180:case 181:case 186:case 196:case 207:case 209:this.b=
+d[b];break;case 27:case 41:case 43:this.b=Object.assign(d[b-1],d[b]);break;case 44:case 46:this.b=ab(d[b]);break;case 45:case 47:this.b="";break;case 48:this.b=e("mrow");break;case 49:this.b=db(d[b-1]);break;case 50:case 54:this.b=e("mo",d[b],{maxsize:"1.2em",minsize:"1.2em"});break;case 51:case 55:this.b=e("mo",d[b],{maxsize:"1.8em",minsize:"1.8em"});break;case 52:case 56:this.b=e("mo",d[b],{maxsize:"2.4em",minsize:"2.4em"});break;case 53:case 57:this.b=e("mo",d[b],{maxsize:"3em",minsize:"3em"});
+break;case 58:this.b=e("mrow",[d[b-2],db(d[b-1]),d[b]]);break;case 59:this.b=e("mfrac",[db(d[b-3]),db(d[b-1])],{linethickness:"0px"});break;case 60:this.b=e("mfrac",[db(d[b-3]),db(d[b-1])],{linethickness:"0px"});this.b=e("mrow",[d[b-4],this.b,d[b]]);break;case 61:this.b=e("mfrac",[db(d[b-3]),db(d[b-1])]);break;case 62:this.b=e("mfrac",[db(d[b-3]),db(d[b-1])]);this.b=e("mrow",[d[b-4],this.b,d[b]]);break;case 63:this.b=e("mfrac",[db(d[b-3]),db(d[b-1])],{linethickness:"0px"});this.b=e("mrow",[ab("("),
+this.b,ab(")")]);break;case 64:this.b=e("mfrac",[db(d[b-3]),db(d[b-1])],{linethickness:"0px"});this.b=e("mrow",[d[b-4],this.b,d[b]]);this.b=e("mrow",[ab("("),this.b,ab(")")]);break;case 65:case 74:this.b=e("mn",d[b]);break;case 66:case 83:case 85:this.b=e("mtext",d[b]);break;case 67:case 68:case 69:case 70:this.b=Ub(d[b]);break;case 71:this.b=Ub(d[b],tb);break;case 72:case 177:this.b=ab(d[b],0,0);break;case 73:this.b=e("mi",d[b]);break;case 75:case 76:case 77:case 176:this.b=ab(d[b]);break;case 78:case 79:case 80:this.b=
+e("mo",d[b],{stretchy:"false"});break;case 81:this.b=e("ms",d[b]);break;case 82:this.b=e("ms",d[b],{lquote:d[b-2],rquote:d[b-1]});break;case 84:this.b=e("mtext",d[b-1]+d[b]);break;case 86:this.b=ab(d[b],0,sb("thinmathspace"));break;case 87:this.b=ab(d[b],sb("thinmathspace"),sb("thinmathspace"));break;case 88:this.b=ab(d[b],sb("mediummathspace"),sb("mediummathspace"));break;case 89:this.b=ab(d[b],sb("thickmathspace"),sb("thickmathspace"));break;case 90:this.b=e("mfrac",[d[b-1],d[b]]);break;case 91:this.b=
+e("mroot",[d[b],d[b-1]]);break;case 92:this.b=e("msqrt",[d[b]]);break;case 93:this.b=e("mroot",[d[b],db(d[b-2])]);break;case 94:this.b=e("munder",[d[b],d[b-1]]);break;case 95:this.b=e("mover",[d[b],d[b-1]]);break;case 96:this.b=e("munderover",[d[b],d[b-2],d[b-1]]);break;case 97:this.b="mrow"===d[b].tag&&!d[b].content&&!d[b].attributes?e("munder",[ab(d[b-4]),db(d[b-2])]):e("munderover",[ab(d[b-4]),db(d[b-2]),d[b]]);break;case 98:this.b=e("mover",[ab(d[b-1]),d[b]]);break;case 99:this.b=e("mpadded",
+[d[b]],{width:"0em"});break;case 100:this.b=e("mpadded",[d[b]],{width:"0em",lspace:"-100%width"});break;case 101:this.b=e("mpadded",[d[b]],{width:"0em",lspace:"-50%width"});break;case 102:this.b=e("mphantom",[d[b]]);break;case 103:this.b=e("mfrac",[d[b-1],d[b]]);this.b=db([this.b],"mstyle",{displaystyle:"false"});break;case 104:this.b=e("mfrac",[d[b-1],d[b]],{linethickness:"0px"});this.b=e("mrow",[ab("("),this.b,ab(")")]);break;case 105:this.b=e("mfrac",[d[b-1],d[b]],{linethickness:"0px"});this.b=
+db([this.b],"mstyle",{displaystyle:"false"});this.b=e("mrow",[ab("("),this.b,ab(")")]);break;case 106:this.b=e("mrow",[ab("(",sb("mediummathspace")),ab("mod",nb,sb("thinmathspace")),d[b],ab(")",nb,sb("mediummathspace"))]);break;case 107:this.b=e("munder",[d[b],ab("⏟")]);break;case 108:this.b=e("munder",[d[b],ab("_")]);break;case 109:this.b=e("mover",[d[b],ab("⏞")]);break;case 110:this.b=e("mover",[d[b],ab(d[b-1])]);break;case 111:this.b=e("mover",[d[b],e("mo",d[b-1],{stretchy:"false"})]);break;case 112:this.b=
+e("menclose",[d[b]],{notation:"box"});break;case 113:this.b=e("menclose",[d[b]],{notation:"updiagonalstrike"});break;case 114:this.b=Gb(1);break;case 115:this.b=Gb(2);break;case 116:this.b=Gb(sb("negativethinmathspace"));break;case 117:this.b=Gb(sb("negativemediummathspace"));break;case 118:this.b=Gb(sb("negativethickmathspace"));break;case 119:this.b=Gb(sb("thinmathspace"));break;case 120:this.b=Gb(sb("mediummathspace"));break;case 121:this.b=Gb(sb("thickmathspace"));break;case 122:this.b=e("mspace",
+xb,{height:"."+d[b-2]+"ex",depth:"."+d[b-1]+"ex",width:"."+d[b]+"em"});break;case 123:this.b=e("mpadded",[d[b]],{voffset:d[b-3].i+d[b-3].k,height:d[b-2].i+d[b-2].k,depth:d[b-1].i+d[b-1].k});break;case 124:this.b=e("mpadded",[d[b]],{voffset:d[b-2].i+d[b-2].k,height:d[b-1].i+d[b-1].k,depth:0>d[b-2].i?"+"+-d[b-2].i+d[b-2].k:"depth"});break;case 125:$a={voffset:d[b-1].i+d[b-1].k};0<=d[b-1].i?$a.height="+"+d[b-1].i+d[b-1].k:($a.height="0pt",$a.depth="+"+-d[b-1].i+d[b-1].k);this.b=e("mpadded",[d[b]],$a);
+break;case 126:this.b=db([d[b]],"mstyle",{mathvariant:"double-struck"});break;case 127:this.b=db([d[b]],"mstyle",{mathvariant:"bold"});break;case 128:this.b=db([d[b]],"mstyle",{mathvariant:"bold-italic"});break;case 129:this.b=db([d[b]],"mstyle",{mathvariant:"script"});break;case 130:this.b=db([d[b]],"mstyle",{mathvariant:"bold-script"});break;case 131:this.b=db([d[b]],"mstyle",{mathvariant:"sans-serif"});break;case 132:this.b=db([d[b]],"mstyle",{mathvariant:"fraktur"});break;case 133:this.b=db([d[b]],
+"mstyle",{mathvariant:"italic"});break;case 134:this.b=db([d[b]],"mstyle",{mathvariant:"monospace"});break;case 135:this.b=db([d[b]],"mstyle",{mathvariant:"normal"});break;case 136:this.b=e("mrow",[d[b]],$a.v?xb:{href:d[b-1]});break;case 137:this.b=$a.v?d[b]:e("maction",[d[b],e("mtext",d[b-1])],{actiontype:"statusline"});break;case 138:this.b=$a.v?d[b]:e("maction",[d[b],e("mtext",d[b-1])],{actiontype:"tooltip"});break;case 139:this.b=$a.v?d[b]:e("maction",[d[b-1],d[b]],{actiontype:"toggle",selection:"2"});
+break;case 140:this.b=$a.v?e("mrow",d[b-1]):e("maction",d[b-1],{actiontype:"toggle"});break;case 141:case 144:this.b=e("mmultiscripts",[d[b-3]].concat(d[b-1]));break;case 142:this.b=e("mmultiscripts",[d[b-3]].concat(d[b-1]).concat(e("mprescripts")).concat(d[b-5]));break;case 143:this.b=e("mmultiscripts",[d[b-2],e("mprescripts")].concat(d[b-4]));break;case 145:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex"});break;case 146:this.b=e("mtable",d[b-1],{displaystyle:"true",rowspacing:"1.0ex"});
+break;case 147:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex"});this.b=e("mrow",[ab("("),this.b,ab(")")]);break;case 148:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex"});this.b=e("mrow",[ab("["),this.b,ab("]")]);break;case 149:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex"});this.b=e("mrow",[ab("|"),this.b,ab("|")]);break;case 150:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex"});this.b=e("mrow",[ab("{"),this.b,ab("}")]);break;
+case 151:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex"});this.b=e("mrow",[ab("‖"),this.b,ab("‖")]);break;case 152:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex"});this.b=db([this.b],"mstyle",{scriptlevel:"2"});break;case 153:this.b=e("mtable",d[b-1],{displaystyle:"false",columnalign:"left left"});this.b=e("mrow",[ab("{"),this.b]);break;case 154:this.b=e("mtable",d[b-1],{displaystyle:"true",columnalign:"right left right left right left right left right left",
+columnspacing:"0em"});break;case 155:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex",align:d[b-3],columnalign:d[b-2]});break;case 156:this.b=e("mtable",d[b-1],{displaystyle:"false",rowspacing:"0.5ex",columnalign:d[b-2]});break;case 157:this.b=e("mtable",d[b-1],{displaystyle:"false",columnalign:"center",rowspacing:"0.5ex"});break;case 158:this.b=e("mtable",d[b-1],{displaystyle:"false"});break;case 159:this.b=e("mtable",d[b-1],Object.assign(d[b-3],{displaystyle:"false"}));break;case 160:this.b=
+[d[b]];break;case 161:this.b=d[b-1].concat([d[b]]);break;case 162:this.b=e("mmultiscripts",[d[b-1]].concat(d[b]));break;case 163:this.b=e("msubsup",[d[b-4],d[b-2],d[b]]);break;case 164:this.b=e("msubsup",[d[b-3],d[b-1],ab(d[b])]);break;case 165:this.b=e("msubsup",[d[b-4],d[b],d[b-2]]);break;case 166:this.b=e("msubsup",[d[b-3],d[b],ab(d[b-2])]);break;case 167:this.b=e("msub",[d[b-2],d[b]]);break;case 168:this.b=e("msup",[d[b-2],d[b]]);break;case 169:this.b=e("msup",[d[b-1],ab(d[b])]);break;case 171:this.b=
+e("munderover",[d[b-4],d[b-2],d[b]]);break;case 172:this.b=e("munderover",[d[b-4],d[b],d[b-2]]);break;case 173:this.b=e("munder",[d[b-2],d[b]]);break;case 174:this.b=e("mover",[d[b-2],d[b]]);break;case 178:case 200:case 204:this.b=[d[b]];break;case 179:this.b=d[b-1].concat([d[b]]);break;case 182:this.b=[d[b-2],d[b]];break;case 183:this.b=[d[b],e("none")];break;case 184:case 185:this.b=[e("none"),d[b]];break;case 187:this.b=d[b-1].concat(d[b]);break;case 188:this.b={displaystyle:"true"};break;case 189:this.b=
+{displaystyle:"false"};break;case 190:this.b={scriptlevel:"0"};break;case 191:this.b={scriptlevel:"1"};break;case 192:this.b={scriptlevel:"2"};break;case 193:this.b={mathcolor:d[b]};break;case 194:this.b={mathbackground:d[b]};break;case 195:this.b=[db(d[b],"mstyle",d[b-1])];break;case 197:this.b=e("mtd",[]);break;case 198:this.b=db(d[b],"mtd",d[b-2]);break;case 199:this.b=db(d[b],"mtd");break;case 201:case 205:this.b=d[b-2].concat([d[b]]);break;case 202:this.b=this.b=e("mtr",d[b],d[b-2]);break;case 203:this.b=
+e("mtr",d[b]);break;case 206:return this.b=d[b-1];case 208:this.b=d[b-1]+d[b];break;case 210:this.b=Pb(d[b]);break;case 211:this.b=Ib([e("mrow")],yb,yb,$a.t);break;case 212:this.b=Ib(d[b-1],yb,yb,$a.t);break;case 213:this.b=Ib([e("mrow")],tb,yb,$a.t);break;case 214:this.b=Ib(d[b-1],tb,yb,$a.t);break;case 215:this.b=Ib(d[b-1],yb,yb,$a.t);break;case 216:this.b=Ib(d[b-1],tb,yb,$a.t)}},ma:[{68:Wb,193:1,194:2,196:3,197:5,198:Xb,200:Yb,202:Zb,204:$b},{1:[3]},{68:Wb,195:[1,10],196:11,197:5,198:Xb,200:Yb,
+202:Zb,204:$b},c(Ab,[2,207]),c(Ab,[2,209]),c(Ab,[2,210]),{8:m,48:36,49:s,50:v,51:x,54:25,55:13,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,
+130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,199:[1,12]},{8:m,48:36,49:s,50:v,51:x,54:25,55:126,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,
+101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,201:[1,125]},{8:m,48:36,49:s,50:v,51:x,54:25,55:127,56:h,
+57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,
+153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},{8:m,48:36,49:s,50:v,51:x,54:25,55:128,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,
+117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},{1:[2,206]},c(Ab,[2,208]),c(Ab,[2,211]),{199:[1,129]},{8:m,48:36,49:s,50:v,51:x,54:25,55:130,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,
+74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,
+173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},c(ac,[2,196],{54:25,172:26,48:36,168:131,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,
+124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb}),c(Db,[2,188]),c(Db,[2,189]),c(Db,[2,190]),c(Db,[2,191]),c(Db,[2,192]),{7:133,8:mb,14:132},{7:133,8:mb,14:135},c(ub,[2,178]),{8:m,48:36,49:s,50:v,51:x,54:136,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,
+87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(ub,[2,170],{169:[1,138],170:[1,139],171:[1,140]}),c(ub,[2,175],{169:[1,
+141],170:[1,142]}),{8:m,10:[1,143],48:36,49:s,50:v,51:x,54:25,55:144,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,
+134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},{50:[1,145]},{50:[1,146]},{50:[1,147]},{50:[1,148]},{50:[1,149]},{50:[1,150]},{50:[1,151]},{50:[1,152]},{8:m,48:36,49:s,50:v,51:x,54:25,55:153,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,
+88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,
+184:kb,185:lb},c(g,[2,65]),c(g,[2,66]),c(g,[2,67]),c(g,[2,68]),c(g,[2,69]),c(g,[2,70]),c(g,[2,71]),c(g,[2,72]),{7:155,8:mb,15:154},{7:155,8:mb,15:156},{7:155,8:mb,15:157},c(g,[2,76]),c(g,[2,77]),c(g,[2,78]),c(g,[2,79]),c(g,[2,80]),{3:160,4:Sb,7:155,8:mb,13:159,15:158},{7:155,8:mb,15:162},{84:[1,163]},c(g,[2,85]),{7:164,8:mb},{7:165,8:mb},{7:166,8:mb},{7:167,8:mb},{8:m,48:36,49:s,50:v,51:x,54:168,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,
+80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:169,56:h,57:q,
+58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,
+155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{4:[1,171],8:m,48:36,49:s,50:v,51:x,54:170,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,
+130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:172,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,
+117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:173,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,
+104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:174,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,
+87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{4:[1,175],8:m,48:36,49:s,50:v,51:x,54:176,56:h,57:q,58:p,59:y,60:f,61:u,
+62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,
+161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:177,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,
+135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:178,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,
+122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:179,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,
+109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:180,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,
+94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:181,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,
+74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,
+49:s,50:v,51:x,54:182,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,
+145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:183,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,
+127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:184,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,
+114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:185,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,
+101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:186,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,
+81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:187,56:h,57:q,58:p,
+59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,
+157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:188,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,
+133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:189,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,
+120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:190,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,
+107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:191,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,
+91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,114]),c(g,[2,115]),c(g,[2,116]),c(g,[2,117]),c(g,[2,118]),c(g,[2,119]),c(g,[2,120]),
+c(g,[2,121]),{7:192,8:mb},{8:[1,194],12:193},{8:m,48:36,49:s,50:v,51:x,54:195,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,
+132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:196,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,
+119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:197,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,
+106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:198,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,
+89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:199,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,
+69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,
+166:Ya},{8:m,48:36,49:s,50:v,51:x,54:200,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,
+141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:201,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,
+125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:202,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,
+112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:203,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,
+98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:204,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,
+78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{7:133,8:mb,14:205},{7:206,8:mb},
+{7:207,8:mb},{8:m,48:36,49:s,50:v,51:x,54:208,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,
+139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:210,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,
+124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,137:209,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:[1,211]},c([144,189,192],pb,{178:14,175:15,168:23,54:25,172:26,48:36,143:212,190:213,188:215,186:216,55:218,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,
+94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c([146,189,192],pb,{178:14,175:15,
+168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:219,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,
+131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c([148,189,192],pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:220,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,
+92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c([150,189,192],pb,
+{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:221,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,
+129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c([152,189,192],pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:222,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,
+89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c([154,
+189,192],pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:223,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,
+128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c([156,189,192],pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:224,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,
+88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),
+c([158,189,192],pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:225,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,
+127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c([160,189,192],pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:226,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,
+86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,
+191:vb}),c([162,189,192],pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:227,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,
+125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),{3:230,4:Sb,7:231,8:mb,16:228,17:229},{8:[1,232]},{8:[1,233]},c(Eb,[2,176]),c(Eb,[2,177]),{50:[1,234],51:[1,235]},c(Ab,[2,213]),{201:[1,236]},{203:[1,237]},{205:[1,238]},c(Ab,[2,212]),c(ac,[2,195]),c(ub,[2,179]),c(Db,[2,193]),c([8,10,
+19,21,23,25,27,29,31,33,35,37,39,41,49,50,51,56,57,58,59,60,61,62,63,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,139,141,142,145,147,149,151,153,155,157,159,161,163,165,166,173,174,179,180,181,182,183,184,185],[2,6]),{9:[1,239]},c(Db,[2,194]),{8:bc,140:240,169:Bb,170:Cb,177:242},{8:m,48:36,49:s,50:v,
+51:x,54:245,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,
+149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:246,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,
+129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:247,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,
+116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(ub,[2,169],{169:[1,248]}),{8:m,48:36,49:s,50:v,51:x,54:249,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,
+98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:250,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,
+78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,48]),{10:[1,251],64:[1,252],
+65:[1,253],66:[1,254]},c(g,[2,50]),c(g,[2,51]),c(g,[2,52]),c(g,[2,53]),c(g,[2,54]),c(g,[2,55]),c(g,[2,56]),c(g,[2,57]),{52:255,53:Mb,64:[1,256],65:[1,257],66:[1,258]},c(g,[2,73]),c(g,[2,7]),c(g,[2,74]),c(g,[2,75]),c(g,[2,81]),{3:160,4:Sb,13:260},c(cc,[2,5]),{5:[1,261]},c(g,[2,83]),c(g,[2,84]),c(g,[2,86]),c(g,[2,87]),c(g,[2,88]),c(g,[2,89]),{8:m,48:36,49:s,50:v,51:x,54:262,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,
+86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:263,56:h,57:q,58:p,59:y,60:f,61:u,62:n,
+63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,
+161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,92]),{8:m,48:36,49:s,50:v,51:x,54:25,55:264,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,
+132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},{8:m,48:36,49:s,50:v,51:x,54:265,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,
+106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:266,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,
+89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:267,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,
+69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,
+166:Ya},{8:m,48:36,49:s,50:v,51:x,54:25,55:268,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,
+139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},c(g,[2,98]),c(g,[2,99]),c(g,[2,100]),c(g,[2,101]),c(g,[2,102]),{8:m,48:36,49:s,50:v,51:x,54:269,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,
+102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:270,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,
+83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:271,56:h,57:q,58:p,59:y,60:f,
+61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,
+159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,106]),c(g,[2,107]),c(g,[2,108]),c(g,[2,109]),c(g,[2,110]),c(g,[2,111]),c(g,[2,112]),c(g,[2,113]),{7:272,8:mb},{4:dc,8:m,11:273,48:36,49:s,50:v,51:x,54:274,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,
+115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{9:[1,276]},c(g,[2,126]),c(g,[2,127]),c(g,[2,128]),c(g,[2,129]),c(g,[2,130]),c(g,[2,131]),c(g,[2,132]),c(g,[2,133]),c(g,[2,134]),c(g,[2,135]),{8:m,48:36,49:s,50:v,51:x,54:277,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,
+73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,
+49:s,50:v,51:x,54:278,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,
+145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:279,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,
+127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:280,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,
+114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:282,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,
+101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,138:[1,281],139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(ec,[2,160]),{10:[1,284],140:283,169:Bb,170:Cb,177:242},{144:[1,285],192:wb},c(Nb,[2,204]),{8:[1,287]},c(Nb,[2,203],{189:fc}),c(Jb,
+[2,200]),{8:[1,289]},c(Jb,[2,199]),{146:[1,290],192:wb},{148:[1,291],192:wb},{150:[1,292],192:wb},{152:[1,293],192:wb},{154:[1,294],192:wb},{156:[1,295],192:wb},{158:[1,296],192:wb},{160:[1,297],192:wb},{162:[1,298],192:wb},{7:231,8:mb,17:299},c(gc,pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:300,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,
+90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),{8:[2,8]},
+c([8,49,50,51,56,57,58,59,60,61,62,63,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,139,141,142,145,147,149,151,153,155,157,159,161,163,164,165,166,173,174,179,180,181,182,183,184,185,187,189,191,192],[2,9]),c(Tb,pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:301,8:m,49:s,
+50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,
+149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c(Tb,pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:302,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,
+107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,167:[1,303],173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c(Db,[2,44]),c(Db,[2,45]),c(Ab,[2,214]),c(Ab,[2,215]),c(Ab,[2,216]),{10:[1,304]},c(ub,[2,162],{177:305,
+169:Bb,170:Cb}),{140:306,169:Bb,170:Cb,177:242},c(Eb,[2,186]),{8:m,48:36,49:s,50:v,51:x,54:309,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,
+130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,170:[1,308],172:310,173:bb,174:cb,176:307},{8:m,48:36,49:s,50:v,51:x,54:309,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,
+111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,172:310,173:bb,174:cb,176:311},{8:bc},c(ub,[2,167],{170:[1,312],171:[1,313]}),c(ub,[2,168],{169:[1,314]}),{8:m,48:36,49:s,50:v,51:x,54:315,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,
+74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(ub,[2,173],
+{170:[1,316]}),c(ub,[2,174],{169:[1,317]}),c(g,[2,49]),{8:m,48:36,49:s,50:v,51:x,54:25,55:318,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,
+130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},{8:m,48:36,49:s,50:v,51:x,54:25,55:319,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,
+103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},{8:m,48:36,49:s,50:v,51:x,54:25,55:320,56:h,57:q,58:p,59:y,60:f,61:u,
+62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,
+161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},c(g,[2,58]),{8:m,48:36,49:s,50:v,51:x,54:25,55:321,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,
+119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},{8:m,48:36,49:s,50:v,51:x,54:25,55:322,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,
+88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,
+184:kb,185:lb},{8:m,48:36,49:s,50:v,51:x,54:25,55:323,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,
+136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},{50:[1,324],51:[1,325]},{7:155,8:mb,15:326},{6:[1,327]},c(g,[2,90]),c(g,[2,91]),{6:[1,328]},c(g,[2,94]),c(g,[2,95]),{8:m,48:36,49:s,50:v,51:x,54:329,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,
+90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{6:[1,330]},c(g,[2,103]),c(g,[2,104]),c(g,[2,105]),{7:331,8:mb},{4:dc,8:m,11:332,48:36,
+49:s,50:v,51:x,54:333,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,
+145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,125]),{5:[1,334]},{10:[1,335]},c(g,[2,136]),c(g,[2,137]),c(g,[2,138]),c(g,[2,139]),c(g,[2,140]),c(ec,[2,161]),{10:[1,336],169:Bb,170:Cb,177:305},{8:m,48:36,49:s,50:v,51:x,54:337,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,
+105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,145]),c(Jb,pb,{178:14,175:15,168:23,54:25,172:26,48:36,188:215,186:216,55:218,190:338,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,
+71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,
+173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),{20:341,21:Kb,22:342,23:Lb,44:340,47:339},c(Jb,pb,{178:14,175:15,168:23,54:25,172:26,48:36,55:218,186:345,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,
+114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb}),{20:348,21:Kb,22:349,23:Lb,24:350,25:hc,26:351,27:ic,42:347,43:346},c(g,[2,146]),c(g,[2,147]),c(g,[2,148]),c(g,[2,149]),c(g,[2,150]),c(g,[2,151]),c(g,[2,152]),c(g,
+[2,153]),c(g,[2,154]),c(gc,pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:354,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,
+125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),{164:[1,355],192:wb},{10:[1,356],192:wb},{10:[1,357],192:wb},{8:[1,358]},c([6,8,10,19,21,23,25,27,29,31,33,35,37,39,41,49,50,51,53,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,
+91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,138,139,141,142,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,169,170,171,173,174,179,180,181,182,183,184,185,187,189,191,192,199,201,203,205],[2,2]),c(Eb,[2,187]),{10:[1,359],169:Bb,170:Cb,177:305},c([6,8,10,49,50,51,53,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,
+76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,139,141,142,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,169,173,174,189,192,199,201,203,205],[2,183],{170:[1,360]}),{8:m,48:36,49:s,50:v,51:x,54:309,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,
+77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,172:310,173:bb,174:cb,176:361},
+c(Eb,[2,180]),c(Eb,[2,181]),c(Eb,[2,184]),{8:m,48:36,49:s,50:v,51:x,54:362,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,
+133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(ub,[2,164]),{8:m,48:36,49:s,50:v,51:x,54:363,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,
+118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(ub,[2,166]),{8:m,48:36,49:s,50:v,51:x,54:364,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,
+103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{8:m,48:36,49:s,50:v,51:x,54:365,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,
+85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},{10:[1,366]},{10:[1,367]},{10:[1,368]},{52:369,53:Mb},{52:370,
+53:Mb},{52:371,53:Mb},c(g,[2,46]),c(g,[2,47]),c(g,[2,82]),c(cc,[2,1]),{8:m,48:36,49:s,50:v,51:x,54:372,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,
+129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,96]),{8:m,48:36,49:s,50:v,51:x,54:373,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,
+114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,122]),{8:m,48:36,49:s,50:v,51:x,54:374,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,
+98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},c(g,[2,124]),{6:[1,375]},c(jc,[2,4]),{8:m,48:36,49:s,50:v,51:x,54:376,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,
+71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya},
+{8:[1,377]},c(Nb,[2,205]),{10:[1,378],20:341,21:Kb,22:342,23:Lb,44:379},c(Ob,[2,42]),c(Ob,[2,28]),c(Ob,[2,29]),{7:133,8:mb,14:380},{7:133,8:mb,14:381},c(Jb,[2,201]),{10:[1,382],20:348,21:Kb,22:349,23:Lb,24:350,25:hc,26:351,27:ic,42:383},c(Hb,[2,26]),c(Hb,[2,22]),c(Hb,[2,23]),c(Hb,[2,24]),c(Hb,[2,25]),{7:133,8:mb,14:384},{7:133,8:mb,14:385},{164:[1,386],192:wb},c(g,[2,156]),c(g,[2,157]),c(g,[2,158]),{18:389,19:kc,20:390,21:Kb,22:391,23:Lb,28:392,29:lc,30:393,31:mc,32:394,33:nc,34:395,35:oc,36:396,
+37:pc,38:397,39:qc,40:398,41:rc,45:388,46:387},c(g,[2,141]),{8:m,48:36,49:s,50:v,51:x,54:309,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,
+130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:Za,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,172:310,173:bb,174:cb,176:407},c(Eb,[2,185]),c(ub,[2,163]),c(ub,[2,165]),c(ub,[2,171]),c(ub,[2,172]),c(g,[2,59]),c(g,[2,61]),c(g,[2,63]),c(g,[2,60]),c(g,[2,62]),c(g,[2,64]),c(g,[2,93]),c(g,[2,97]),c(g,[2,123]),c(jc,[2,3]),{8:[1,408]},{140:409,169:Bb,170:Cb,177:242},c(Jb,pb,{178:14,175:15,168:23,54:25,172:26,48:36,186:216,55:218,188:410,8:m,49:s,
+50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,
+149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb}),c(Ob,[2,43]),c(sc,[2,11]),c(sc,[2,12]),{8:m,48:36,49:s,50:v,51:x,54:25,55:411,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,
+112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,168:23,172:26,173:bb,174:cb,175:15,178:14,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb},c(Hb,[2,27]),c(Hb,[2,13]),c(Hb,[2,14]),c(g,[2,155]),{10:[1,412],18:389,19:kc,20:390,21:Kb,22:391,23:Lb,28:392,29:lc,30:393,31:mc,
+32:394,33:nc,34:395,35:oc,36:396,37:pc,38:397,39:qc,40:398,41:rc,45:413},c(ob,[2,40]),c(ob,[2,30]),c(ob,[2,31]),c(ob,[2,32]),c(ob,[2,33]),c(ob,[2,34]),c(ob,[2,35]),c(ob,[2,36]),c(ob,[2,37]),c(ob,[2,38]),c(ob,[2,39]),{7:133,8:mb,14:414},{7:133,8:mb,14:415},{7:133,8:mb,14:416},{7:133,8:mb,14:417},{7:133,8:mb,14:418},{7:133,8:mb,14:419},{7:133,8:mb,14:420},{7:133,8:mb,14:421},c(Eb,[2,182]),{10:[1,423],140:422,169:Bb,170:Cb,177:242},{10:[1,424],169:Bb,170:Cb,177:305},c(Nb,[2,202],{189:fc}),c(Jb,[2,198]),
+c(Tb,pb,{178:14,175:15,168:23,54:25,172:26,48:36,190:213,188:215,186:216,55:218,143:425,8:m,49:s,50:v,51:x,56:h,57:q,58:p,59:y,60:f,61:u,62:n,63:k,67:r,68:t,69:l,70:w,71:z,72:A,73:B,74:C,75:D,76:E,77:F,78:G,79:H,80:I,81:J,82:K,83:L,85:M,86:N,87:O,88:P,89:Q,90:R,91:S,92:T,93:U,94:V,95:W,96:X,97:Y,98:Z,99:$,100:aa,101:ba,102:ca,103:da,104:ea,105:fa,106:ga,107:ha,108:ia,109:ja,110:ka,111:la,112:ma,113:na,114:oa,115:pa,116:qa,117:ra,118:sa,119:ta,120:ua,121:va,122:wa,123:xa,124:ya,125:za,126:Aa,127:Ba,
+128:Ca,129:Da,130:Ea,131:Fa,132:Ga,133:Ha,134:Ia,135:Ja,136:Ka,139:eb,141:La,142:Ma,145:Na,147:Oa,149:Pa,151:Qa,153:Ra,155:Sa,157:Ta,159:Ua,161:Va,163:Wa,165:Xa,166:Ya,173:bb,174:cb,179:fb,180:gb,181:hb,182:ib,183:jb,184:kb,185:lb,187:qb,191:vb}),c(ob,[2,41]),c(ob,[2,10]),c(ob,[2,15]),c(ob,[2,16]),c(ob,[2,17]),c(ob,[2,18]),c(ob,[2,19]),c(ob,[2,20]),c(ob,[2,21]),{10:[1,426],169:Bb,170:Cb,177:305},c(g,[2,143]),c(g,[2,144]),{10:[1,427],192:wb},c(g,[2,142]),c(g,[2,159])],N:{10:[2,206],230:[2,8]},parseError:function(b,
+a){if(a.va)this.trace(b);else{var c=Error(b);c.hash=a;throw c;}},parse:function(b){var a=[0],c=[xb],e=[],g=this.ma,d="",m=0,s=0,v=0,x=e.slice.call(arguments,1),h=Object.create(this.S),q={},p;for(p in this.e)Object.prototype.hasOwnProperty.call(this.e,p)&&(q[p]=this.e[p]);h.ga(b,q);q.S=h;q.V=this;"undefined"==typeof h.c&&(h.c={});p=h.c;e.push(p);var y=h.options&&h.options.w;this.parseError="function"===typeof q.parseError?q.parseError:Object.getPrototypeOf(this).parseError;for(var f,u,n,k,r={},t,l;;){n=
+a[a.length-1];if(this.N[n])k=this.N[n];else{if(f===xb||"undefined"==typeof f)f=nb,f=h.R()||1,"number"!==typeof f&&(f=this.la[f]||f);k=g[n]&&g[n][f]}if("undefined"===typeof k||!k.length||!k[0]){var w="";l=[];for(t in g[n])this.z[t]&&2<t&&l.push("'"+this.z[t]+"'");w=h.D?"Parse error on line "+(m+1)+":\n"+h.D()+"\nExpecting "+l.join(", ")+", got '"+(this.z[f]||f)+"'":"Parse error on line "+(m+1)+": Unexpected "+(1==f?"end of input":"'"+(this.z[f]||f)+"'");this.parseError(w,{text:h.match,$:this.z[f]||
+f,T:h.f,ta:p,qa:l})}if(k[0]instanceof Array&&1<k.length)throw Error("Parse Error: multiple actions possible at state: "+n+", token: "+f);switch(k[0]){case 1:a.push(f);c.push(h.a);e.push(h.c);a.push(k[1]);f=xb;u?(f=u,u=xb):(s=h.q,d=h.a,m=h.f,p=h.c,0<v&&v--);break;case 2:l=this.W[k[1]][1];r.b=c[c.length-l];r.K={r:e[e.length-(l||1)].r,o:e[e.length-1].o,l:e[e.length-(l||1)].l,m:e[e.length-1].m};y&&(r.K.n=[e[e.length-(l||1)].n[0],e[e.length-1].n[1]]);n=this.H.apply(r,[d,s,m,q,k[1],c,e].concat(x));if("undefined"!==
+typeof n)return n;l&&(a=a.slice(0,-2*l),c=c.slice(0,-1*l),e=e.slice(0,-1*l));a.push(this.W[k[1]][0]);c.push(r.b);e.push(r.K);k=g[a[a.length-2]][a[a.length-1]];a.push(k);break;case 3:return tb}}return tb}},Qb="http://www.w3.org/1998/Math/MathML",wc="TeX LaTeX text/x-tex text/x-latex application/x-tex application/x-latex".split(" ");try{rb.C=new DOMParser}catch(yc){rb.C={parseFromString:function(){throw"DOMParser undefined. Did you call TeXZilla.setDOMParser?";}}}rb.fa=function(b){this.C=b};try{rb.G=
+new XMLSerializer}catch(zc){rb.G={serializeToString:function(){throw"XMLSerializer undefined. Did you call TeXZilla.setXMLSerializer?";}}}rb.ja=function(b){this.G=b};rb.U=function(b){return this.C.parseFromString(b,"application/xml").documentElement};rb.ia=function(b){this.e.v=b};rb.ha=function(b){this.e.da=b};rb.ca=function(b){"string"===typeof b&&(b=this.U(b));return Vb(b)};rb.Z=function(b,a,c,g){var f;try{f=this.parse("\\("+b+"\\)"),c&&(f=f.replace(/^<math/,'<math dir="rtl"')),a&&(f=f.replace(/^<math/,
+'<math display="block"'))}catch(d){if(g)throw d;f=Pb(Ib([e("merror",[e("mtext",Fb(d.message))])],a,c,b))}return f};rb.Y=function(b,a,c,e){return this.U(this.Z(b,a,c,e))};rb.na=function(b,a,c,e,f){var d,g;e===nb&&(e=64);f===nb&&(f=window.document);a=this.Y(b,tb,a);a.setAttribute("mathsize",e+"px");e=document.createElement("div");e.style.visibility="hidden";e.style.position="absolute";e.appendChild(a);f.body.appendChild(e);d=a.getBoundingClientRect();f.body.removeChild(e);e.removeChild(a);c?(c=Math.pow(2,
+Math.ceil(Math.log(d.width)/Math.LN2)),f=Math.pow(2,Math.ceil(Math.log(d.height)/Math.LN2))):(c=Math.ceil(d.width),f=Math.ceil(d.height));g=document.createElementNS("http://www.w3.org/2000/svg","svg");g.setAttribute("width",c+"px");g.setAttribute("height",f+"px");e=document.createElementNS("http://www.w3.org/2000/svg","g");e.setAttribute("transform","translate("+(c-d.width)/2+","+(f-d.height)/2+")");g.appendChild(e);e=document.createElementNS("http://www.w3.org/2000/svg","foreignObject");e.setAttribute("width",
+d.width);e.setAttribute("height",d.height);e.appendChild(a);g.firstChild.appendChild(e);a=new Image;a.src="data:image/svg+xml;base64,"+window.btoa(xc(this.G.serializeToString(g)));a.width=c;a.height=f;a.alt=Fb(b);return a};rb.Q=function(b,a){try{return this.parse(b)}catch(c){if(a)throw c;return b}};rb.P=function(b,a){var c,e,f;for(f=b.firstChild;f;f=f.nextSibling)switch(f.nodeType){case 1:this.P(f,a);break;case 3:this.e.O=tb;c=this.C.parseFromString("<root>"+zb.Q(f.data,a)+"</root>","application/xml").documentElement;
+for(this.e.O=yb;e=c.firstChild;)b.insertBefore(c.removeChild(e),f);e=f.previousSibling;b.removeChild(f);f=e}};rb.S=function(){return{J:1,parseError:function(b,a){if(this.e.V)this.e.V.parseError(b,a);else throw Error(b);},ga:function(b,a){this.e=a||this.e||{};this.g=b;this.u=this.B=this.s=yb;this.f=this.q=0;this.a=this.h=this.match="";this.d=["INITIAL"];this.c={r:1,l:0,o:1,m:0};this.options.w&&(this.c.n=[0,0]);this.offset=0;return this},input:function(){var b=this.g[0];this.a+=b;this.q++;this.offset++;
+this.match+=b;this.h+=b;b.match(/(?:\r\n?|\n).*/g)?(this.f++,this.c.o++):this.c.m++;this.options.w&&this.c.n[1]++;this.g=this.g.slice(1);return b},I:function(b){var a=b.length,c=b.split(/(?:\r\n?|\n)/g);this.g=b+this.g;this.a=this.a.substr(0,this.a.length-a);this.offset-=a;b=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1);this.h=this.h.substr(0,this.h.length-1);c.length-1&&(this.f-=c.length-1);var e=this.c.n;this.c={r:this.c.r,o:this.f+1,l:this.c.l,m:c?(c.length===
+b.length?this.c.l:0)+b[b.length-c.length].length-c[0].length:this.c.l-a};this.options.w&&(this.c.n=[e[0],e[0]+this.q-a]);this.q=this.a.length;return this},ua:function(){this.u=tb;return this},wa:function(){if(this.options.L)this.B=tb;else return this.parseError("Lexical error on line "+(this.f+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.D(),{text:"",$:xb,T:this.f});return this},sa:function(b){this.I(this.match.slice(b))},
+ea:function(){var b=this.h.substr(0,this.h.length-this.match.length);return(20<b.length?"...":"")+b.substr(-20).replace(/\n/g,"")},oa:function(){var b=this.match;20>b.length&&(b+=this.g.substr(0,20-b.length));return(b.substr(0,20)+(20<b.length?"...":"")).replace(/\n/g,"")},D:function(){var b=this.ea(),a=Array(b.length+1).join("-");return b+this.oa()+"\n"+a+"^"},X:function(b,a){var c,e;this.options.L&&(e={f:this.f,c:{r:this.c.r,o:this.o,l:this.c.l,m:this.c.m},a:this.a,match:this.match,matches:this.matches,
+h:this.h,q:this.q,offset:this.offset,u:this.u,g:this.g,e:this.e,d:this.d.slice(0),s:this.s},this.options.w&&(e.c.n=this.c.n.slice(0)));if(c=b[0].match(/(?:\r\n?|\n).*/g))this.f+=c.length;this.c={r:this.c.o,o:this.f+1,l:this.c.m,m:c?c[c.length-1].length-c[c.length-1].match(/\r?\n?/)[0].length:this.c.m+b[0].length};this.a+=b[0];this.match+=b[0];this.matches=b;this.q=this.a.length;this.options.w&&(this.c.n=[this.offset,this.offset+=this.q]);this.B=this.u=yb;this.g=this.g.slice(b[0].length);this.h+=b[0];
+c=this.H.call(this,this.e,this,a,this.d[this.d.length-1]);this.s&&this.g&&(this.s=yb);if(c)return c;if(this.B)for(var f in e)this[f]=e[f];return yb},next:function(){if(this.s)return this.J;this.g||(this.s=tb);var b,a,c;this.u||(this.match=this.a="");for(var e=this.aa(),f=0;f<e.length;f++)if((a=this.g.match(this.rules[e[f]]))&&(!b||a[0].length>b[0].length))if(b=a,c=f,this.options.L){b=this.X(a,e[f]);if(b!==yb)return b;if(this.B)b=yb;else return yb}else if(!this.options.ra)break;return b?(b=this.X(b,
+e[c]),b!==yb?b:yb):""===this.g?this.J:this.parseError("Lexical error on line "+(this.f+1)+". Unrecognized text.\n"+this.D(),{text:"",$:xb,T:this.f})},R:function(){var b=this.next();return b?b:this.R()},j:function(b){this.d.push(b)},p:function(){return 0<this.d.length-1?this.d.pop():this.d[0]},aa:function(){return this.d.length&&this.d[this.d.length-1]?this.M[this.d[this.d.length-1]].rules:this.M.INITIAL.rules},ya:function(b){b=this.d.length-1-Math.abs(b||0);return 0<=b?this.d[b]:"INITIAL"},pushState:function(b){this.j(b)},
+xa:function(){return this.d.length},options:{},H:function(b,a,c){switch(c){case 0:this.I(a.a);this.pushState("DOCUMENT");break;case 1:return this.pushState("MATH"+(0+!!b.da)),b.ka=this.h.length,"STARTMATH"+(2*("$"==a.a[0])+("$"==a.a[1]||"["==a.a[1]));case 2:return this.p(),"EOF";case 3:return a.a=a.a[1],"TEXT";case 4:return b.O&&(a.a=Fb(a.a)),"TEXT";case 5:return"TEXT";case 6:return this.p(),"[";case 7:this.I(a.a);this.p();this.p();break;case 8:return"TEXTOPTARG";case 9:return this.p(),"]";case 10:return"{";
+case 11:return"TEXTARG";case 12:return this.p(),"}";case 13:return this.p(),"]";case 15:return this.p(),b.ba=this.h.length-this.match.length,b.t=this.h.substring(b.ka,b.ba),"ENDMATH"+(2*("$"==a.a[0])+("$"==a.a[1]||"]"==a.a[1]));case 16:return"{";case 17:return"}";case 18:return"^";case 19:return"_";case 20:return".";case 21:return"COLSEP";case 22:return"ROWSEP";case 23:return"NUM";case 24:return"A";case 25:return a.a="Ζ","AIUG";case 26:return a.a="ζ","AILG";case 27:return this.pushState("OPTARG"),
+this.pushState("TRYOPTARG"),a.a="⇌","XARROW";case 28:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="⇒","XARROW";case 29:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="→","XARROW";case 30:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="↦","XARROW";case 31:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="⇋","XARROW";case 32:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="⇔","XARROW";case 33:return this.pushState("OPTARG"),
+this.pushState("TRYOPTARG"),a.a="↔","XARROW";case 34:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="⇐","XARROW";case 35:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="←","XARROW";case 36:return a.a="Ξ","AIUG";case 37:return a.a="ξ","AILG";case 38:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="↪","XARROW";case 39:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),a.a="↩","XARROW";case 40:return a.a="≀","OP";case 41:return a.a="℘","A";case 42:return a.a=
+"⇀","ACCENT";case 43:return a.a="˜","ACCENT";case 44:return a.a="^","ACCENT";case 45:return a.a="ˇ","ACCENT";case 46:return a.a="¯","ACCENT";case 47:return a.a="≙","OP";case 48:return a.a="⋀","OPM";case 49:return a.a="∧","OP";case 50:return a.a="⦀","OPFS";case 51:return a.a="⊪","OP";case 52:return a.a="‖","OPFS";case 53:return a.a="|","OPFS";case 54:return a.a="⊻","OP";case 55:return a.a="⋁","OPM";case 56:return a.a="∨","OP";case 57:return a.a="⇀","ACCENTNS";case 58:return a.a="⋮","OP";case 59:return a.a=
+"⊫","OP";case 60:return a.a="⊩","OP";case 61:return a.a="⊨","OP";case 62:return a.a="⊢","OP";case 63:return a.a="⫫","OP";case 64:return a.a="⊳","OP";case 65:return a.a="⊲","OP";case 66:return a.a="▵","OP";case 67:return a.a="ϑ","AILG";case 68:return a.a="⫌︀","OP";case 69:return a.a="⊋︀","OP";case 70:return a.a="⫋︀","OP";case 71:return a.a="⊊︀","OP";case 72:return a.a="⊊︀","OP";case 73:return a.a="ς","A";case 74:return a.a="ϱ","AILG";case 75:return a.a="∝","OP";case 76:return a.a="ϖ","AILG";case 77:return a.a=
+"φ","AILG";case 78:return a.a="∅","A";case 79:return a.a="ϰ","AILG";case 80:return a.a="ε","AILG";case 81:return a.a="⤊","OPS";case 82:return a.a="⇈","OPS";case 83:return a.a="ϒ","A";case 84:return a.a="υ","AILG";case 85:return a.a="ϒ","A";case 86:return a.a="⊎","OP";case 87:return a.a="⨛","OP";case 88:return a.a="↿","OPS";case 89:return a.a="↾","OPS";case 90:return a.a="⇕","OPS";case 91:return a.a="↕","OPS";case 92:return a.a="↕","OPS";case 93:return a.a="⇑","OPS";case 94:return a.a="↑","OPS";case 95:return a.a=
+"↑","OPS";case 96:return a.a="⊵","OP";case 97:return a.a="⊴","OP";case 98:return a.a="⋃","OPM";case 99:return a.a="∪","OP";case 100:return"UNDERSET";case 101:return"UNDEROVERSET";case 102:return"UNDERLINE";case 103:return"UNDERBRACE";case 104:return a.a="⋰","OP";case 105:return"OP";case 106:return"OP";case 107:return"OP";case 108:return"OP";case 109:return"OP";case 110:return"OP";case 111:return"OP";case 112:return"OP";case 113:return"OP";case 114:return"OP";case 115:return"OP";case 116:return"OP";
+case 117:return"OP";case 118:return"OP";case 119:return"OP";case 120:return"OP";case 121:return"OP";case 122:return"OP";case 123:return"OP";case 124:return"OP";case 125:return"OP";case 126:return"OP";case 127:return"OP";case 128:return"OP";case 129:return"OP";case 130:return"OP";case 131:return"OP";case 132:return"OP";case 133:return"OP";case 134:return"OP";case 135:return"OP";case 136:return"OP";case 137:return"OP";case 138:return"OPFS";case 139:return"OPFS";case 140:return"OP";case 141:return"OP";
+case 142:return"OP";case 143:return"OP";case 144:return"OP";case 145:return"OP";case 146:return"OP";case 147:return"OP";case 148:return"OP";case 149:return"OP";case 150:return"OP";case 151:return"OP";case 152:return"OP";case 153:return"OP";case 154:return"OP";case 155:return"OP";case 156:return"OP";case 157:return"OP";case 158:return"OP";case 159:return"OP";case 160:return"OP";case 161:return a.a="⤖","OP";case 162:return a.a="↠","OPS";case 163:return a.a="↞","OPS";case 164:return a.a="∭","OP";case 165:return a.a=
+"⊵","OP";case 166:return a.a="▹","OP";case 167:return a.a="≜","OP";case 168:return a.a="⊴","OP";case 169:return a.a="◃","OP";case 170:return a.a="▿","OP";case 171:return a.a="▵","OP";case 172:return a.a="⤪","OP";case 173:return a.a="⤩","OP";case 174:return a.a="⊤","OP";case 175:return this.pushState("TEXTARG"),"TOOLTIP";case 176:return a.a="⤧","OP";case 177:return"TOGGLE";case 178:return a.a="⤨","OP";case 179:return a.a="→","OPS";case 180:return a.a="⊠","OP";case 181:return a.a="×","OP";case 182:return a.a=
+"˜","ACCENTNS";case 183:return"THINSPACE";case 184:return"THICKSPACE";case 185:return a.a="∼","OP";case 186:return a.a="≈","OP";case 187:return a.a="Θ","AIUG";case 188:return a.a="θ","AILG";case 189:return a.a="∴","OP";case 190:return"TFRAC";case 191:return"TEXTSTYLE";case 192:return"TEXTSIZE";case 193:return a.a="”","OPF";case 194:return a.a="“","OPF";case 195:return a.a="~","OPS";case 196:return a.a="`","OP";case 197:return a.a="^","OPS";case 198:return a.a="´","OP";case 199:return this.j("TEXTARG"),
+"MTEXT";case 200:return"TENSOR";case 201:return"TBINOM";case 202:return a.a="Τ","AIUG";case 203:return a.a="τ","AILG";case 204:return a.a="⇙","OPS";case 205:return a.a="↙","OPS";case 206:return a.a="⇙","OPS";case 207:return a.a="↙","OPS";case 208:return a.a="√","OPS";case 209:return a.a="⫌","OP";case 210:return a.a="⊋","OP";case 211:return a.a="⫆","OP";case 212:return a.a="⊇","OP";case 213:return a.a="⋑","OP";case 214:return a.a="⊃","OP";case 215:return a.a="∑","OPM";case 216:return a.a="≿","OP";
+case 217:return a.a="⋩","OP";case 218:return a.a="⪶","OP";case 219:return a.a="⪺","OP";case 220:return a.a="⪰","OP";case 221:return a.a="≽","OP";case 222:return a.a="⪸","OP";case 223:return a.a="≻","OP";case 224:return"SUBSTACK";case 225:return a.a="⫋","OP";case 226:return a.a="⊊","OP";case 227:return a.a="⫅","OP";case 228:return a.a="⊆","OP";case 229:return a.a="⋐","OP";case 230:return a.a="⊂","OP";case 231:return this.pushState("TEXTARG"),"STATUSLINE";case 232:return a.a="⋆","OP";case 233:return"OVERSET";
+case 234:return a.a="⫽","OP";case 235:return a.a="□","OP";case 236:return a.a="⊒","OP";case 237:return a.a="⊐","OP";case 238:return a.a="⊑","OP";case 239:return a.a="⊏","OP";case 240:return this.pushState("OPTARG"),this.pushState("TRYOPTARG"),"SQRT";case 241:return a.a="⊔","OP";case 242:return a.a="⊓","OP";case 243:return a.a="∢","OP";case 244:return a.a="♠","OP";case 245:return this.pushState("TEXTARG"),this.pushState("TEXTARG"),this.pushState("TEXTARG"),"SPACE";case 246:return a.a="⌣","OP";case 247:return a.a=
+"⌣","OP";case 248:return a.a="∖","OP";case 249:return a.a="⌢","OP";case 250:return"SLASH";case 251:return a.a="≃","OP";case 252:return a.a="∼","OP";case 253:return a.a="Σ","AIUG";case 254:return a.a="σ","AILG";case 255:return a.a="⧢","OP";case 256:return a.a="∥","OP";case 257:return a.a="∣","OP";case 258:return a.a="♯","OP";case 259:return a.a="∖","OP";case 260:return a.a="⤭","OP";case 261:return a.a="⇘","OPS";case 262:return a.a="↘","OPS";case 263:return a.a="⇘","OPS";case 264:return a.a="↘","OPS";
+case 265:return"SCRIPTSIZE";case 266:return"SCRIPTSCRIPTSIZE";case 267:return a.a="⋊","OP";case 268:return a.a="↱","OPS";case 269:return a.a="⇛","OPS";case 270:return a.a="⟫","OPFS";case 271:return a.a="’","OPF";case 272:return this.j("TEXTARG"),"ROWSPAN";case 273:return"ROWOPTS";case 274:return this.pushState("TEXTARG"),"ROWLINES";case 275:return this.j("TEXTARG"),"ROWALIGN";case 276:return"ROOT";case 277:return a.a="⎱","OP";case 278:return a.a="≓","OP";case 279:return a.a="⟲","OP";case 280:return a.a=
+"⋌","OP";case 281:return a.a="↝","OPS";case 282:return a.a="⇉","OPS";case 283:return a.a="⇌","OPS";case 284:return a.a="⇄","OPS";case 285:return a.a="⇀","OPS";case 286:return a.a="⇁","OPS";case 287:return a.a="⇾","OPS";case 288:return a.a="↣","OPS";case 289:return a.a="⇒","OPS";case 290:return a.a="→","OPS";case 291:return"RIGHT";case 292:return a.a="Ρ","AIUG";case 293:return a.a="ρ","AILG";case 294:return a.a="⊳","OP";case 295:return a.a="⌋","OPFS";case 296:return a.a="ℜ","A";case 297:return a.a=
+"⤰","OP";case 298:return a.a="⤫","OP";case 299:return a.a="⌉","OPFS";case 300:return a.a="]","OPFS";case 301:return a.a="}","OPFS";case 302:return a.a="⟩","OPFS";case 303:return a.a="⟩","OPFS";case 304:return a.a="≟","OP";case 305:return a.a="⨌","OP";case 306:return"QUAD";case 307:return"QQUAD";case 308:return a.a="▪","OP";case 309:return a.a="Ψ","AIUG";case 310:return a.a="ψ","AILG";case 311:return a.a="∝","OP";case 312:return a.a="∏","OPM";case 313:return a.a="∏","OPM";case 314:return a.a="′","OPP";
+case 315:return a.a="≾","OP";case 316:return a.a="⋨","OP";case 317:return a.a="⪵","OP";case 318:return a.a="⪹","OP";case 319:return a.a="⪯","OP";case 320:return a.a="≼","OP";case 321:return a.a="⪷","OP";case 322:return a.a="≺","OP";case 323:return"PMOD";case 324:return a.a="±","OP";case 325:return a.a="⨥","OP";case 326:return a.a="⊞","OP";case 327:return a.a="⋔","OP";case 328:return a.a="Π","AIUG";case 329:return a.a="π","AILG";case 330:return a.a="Φ","AIUG";case 331:return a.a="ϕ","AILG";case 332:return"PHANTOM";
+case 333:return a.a="⫫","OP";case 334:return a.a="⊥","OP";case 335:return a.a="⪣","OP";case 336:return a.a="∂","OP";case 337:return a.a="⅋","OP";case 338:return a.a="∥","OP";case 339:return this.pushState("TEXTARG"),"PADDING";case 340:return"OVERSET";case 341:return a.a="¯","ACCENT";case 342:return"OVERBRACE";case 343:return"TEXOVER";case 344:return a.a="⨴","OP";case 345:return a.a="⊗","OP";case 346:return a.a="⊘","OP";case 347:return"OPS";case 348:return"OPP";case 349:return"OPM";case 350:return a.a=
+"⨭","OP";case 351:return a.a="⊕","OP";case 352:return"OPFS";case 353:return"OPF";case 354:return this.j("TEXTARG"),"OPERATORNAME";case 355:return"OP";case 356:return a.a="⊖","OP";case 357:return a.a="ℴ","A";case 358:return a.a="Ω","AIUG";case 359:return a.a="ω","AILG";case 360:return a.a="∮","OP";case 361:return a.a="∯","OP";case 362:return a.a="∰","OP";case 363:return a.a="⊙","OP";case 364:return a.a="⊝","OP";case 365:return a.a="⦸","OP";case 366:return a.a="⤲","OP";case 367:return a.a="⇖","OPS";
+case 368:return a.a="↖","OPS";case 369:return a.a="⇖","OPS";case 370:return a.a="↖","OPS";case 371:return a.a="⊯","OP";case 372:return a.a="⊮","OP";case 373:return a.a="⊭","OP";case 374:return a.a="⊬","OP";case 375:return"NUM";case 376:return a.a="Ν","AIUG";case 377:return a.a="ν","AILG";case 378:return a.a="⋭","OP";case 379:return a.a="⋫","OP";case 380:return a.a="⋬","OP";case 381:return a.a="⋪","OP";case 382:return a.a="⊉","OP";case 383:return a.a="⊅","OP";case 384:return a.a="≿̸","OP";case 385:return a.a=
+"⪰̸","OP";case 386:return a.a="⊁","OP";case 387:return a.a="⊈","OP";case 388:return a.a="⊈","OP";case 389:return a.a="⊄","OP";case 390:return a.a="≄","OP";case 391:return a.a="≁","OP";case 392:return a.a="∦","OP";case 393:return a.a="∤","OP";case 394:return a.a="⇏","OP";case 395:return a.a="↛","OP";case 396:return a.a="⪯̸","OP";case 397:return a.a="⊀","OP";case 398:return a.a="∦","OP";case 399:return a.a="∌","OP";case 400:return a.a="∉","OP";case 401:return a.a="¬","OP";case 402:return a.a="∤","OP";
+case 403:return a.a="≮","OP";case 404:return a.a="⩽̸","OP";case 405:return a.a="⩽̸","OP";case 406:return a.a="≰","OP";case 407:return a.a="⇎","OP";case 408:return a.a="↮","OP";case 409:return a.a="⇍","OP";case 410:return a.a="↚","OP";case 411:return a.a="∋","OP";case 412:return a.a="≯","OP";case 413:return a.a="⩾̸","OP";case 414:return a.a="⩾̸","OP";case 415:return a.a="≱","OP";case 416:return a.a="∄","OP";case 417:return a.a="≢","OP";case 418:return a.a="≂̸","OP";case 419:return a.a="≠","OP";case 420:return a.a=
+"⤮","OP";case 421:return a.a="⤱","OP";case 422:return"NEGTHICKSPACE";case 423:return"NEGSPACE";case 424:return"NEGMEDSPACE";case 425:return a.a="¬","OP";case 426:return a.a="⇗","OPS";case 427:return a.a="↗","OPS";case 428:return a.a="⇗","OPS";case 429:return a.a="↗","OPS";case 430:return a.a="≠","OP";case 431:return a.a="≇","OP";case 432:return a.a="≎̸","OP";case 433:return a.a="≏̸","OP";case 434:return a.a="♮","OP";case 435:return a.a="≉","OP";case 436:return a.a="∇","OP";case 437:return"MULTI";
+case 438:return a.a="⊸","OP";case 439:return a.a="Μ","AIUG";case 440:return a.a="μ","AILG";case 441:return this.j("TEXTARG"),"MTEXT";case 442:return this.pushState("TEXTARG"),this.pushState("TEXTOPTARG"),this.pushState("TRYOPTARG"),this.pushState("TEXTOPTARG"),this.pushState("TRYOPTARG"),"MS";case 443:return a.a="∓","OP";case 444:return a.a="⊧","OP";case 445:return a.a="mod","MO";case 446:return this.pushState("TEXTARG"),"MO";case 447:return this.pushState("TEXTARG"),"MN";case 448:return a.a="⫛",
+"OP";case 449:return a.a="⨪","OP";case 450:return a.a="⊟","OP";case 451:return a.a="−","OP";case 452:return a.a=a.a.slice(1),"FM";case 453:return a.a="∣","OP";case 454:return this.pushState("TEXTARG"),"MI";case 455:return a.a="℧","A";case 456:return a.a="℧","A";case 457:return"MEDSPACE";case 458:return a.a="∡","OP";case 459:return"MATHTT";case 460:return"MATHSF";case 461:return"MATHSCR";case 462:return"MATHRM";case 463:return"MATHRLAP";case 464:return this.j("TEXTARG"),"MATHREL";case 465:return this.pushState("TEXTOPTARG"),
+this.pushState("TRYOPTARG"),this.pushState("TEXTOPTARG"),this.pushState("TRYOPTARG"),this.pushState("TEXTARG"),"MATHRAISEBOX";case 466:return this.j("TEXTARG"),"MATHOP";case 467:return"MATHIT";case 468:return"MATHLLAP";case 469:return"MATHIT";case 470:return"MATHFRAK";case 471:return"MATHFRAK";case 472:return"MATHCLAP";case 473:return"MATHSCR";case 474:return"MATHBSCR";case 475:return"MATHBIT";case 476:return this.j("TEXTARG"),"MATHBIN";case 477:return"MATHBF";case 478:return"MATHBSCR";case 479:return"MATHBB";
+case 480:return a.a="⤇","OP";case 481:return a.a="↦","OPS";case 482:return a.a="⤆","OP";case 483:return a.a="↦","OPS";case 484:return a.a="≨︀","OP";case 485:return a.a="≨︀","OP";case 486:return a.a="⋉","OP";case 487:return a.a="<","OP";case 488:return a.a="↰","OPS";case 489:return a.a="‘","OPF";case 490:return a.a="◊","OP";case 491:return a.a="⨜","OP";case 492:return a.a="↬","OPS";case 493:return a.a="↫","OPS";case 494:return a.a="⟹","OPS";case 495:return a.a="⟶","OPS";case 496:return a.a="⟼","OPS";
+case 497:return a.a="⟺","OPS";case 498:return a.a="⟷","OPS";case 499:return a.a="⟸","OPS";case 500:return a.a="⟵","OPS";case 501:return a.a="⋦","OP";case 502:return a.a="≨","OP";case 503:return a.a="⪇","OP";case 504:return a.a="⪉","OP";case 505:return a.a="⎰","OP";case 506:return a.a="⋘","OP";case 507:return a.a="⇚","OPS";case 508:return a.a="⟪","OPFS";case 509:return a.a="≪","OP";case 510:return a.a="⊲","OP";case 511:return a.a="⌊","OPFS";case 512:return a.a="≲","OP";case 513:return a.a="≶","OP";
+case 514:return a.a="⪋","OP";case 515:return a.a="⋚","OP";case 516:return a.a="⋖","OP";case 517:return a.a="⪅","OP";case 518:return a.a="<","OP";case 519:return a.a="⩽","OP";case 520:return a.a="≦","OP";case 521:return a.a="≤","OP";case 522:return a.a="⟳","OP";case 523:return a.a="⋋","OP";case 524:return a.a="↜","OPS";case 525:return a.a="↭","OPS";case 526:return a.a="⇋","OPS";case 527:return a.a="⇿","OPS";case 528:return a.a="⇆","OPS";case 529:return a.a="⇔","OPS";case 530:return a.a="↔","OPS";case 531:return a.a=
+"⇇","OPS";case 532:return a.a="↼","OPS";case 533:return a.a="↽","OPS";case 534:return a.a="⇽","OPS";case 535:return a.a="↢","OPS";case 536:return a.a="⇐","OPS";case 537:return a.a="←","OPS";case 538:return"LEFT";case 539:return a.a="≤","OP";case 540:return a.a="…","OP";case 541:return a.a="⌈","OPFS";case 542:return a.a="[","OPFS";case 543:return a.a="{","OPFS";case 544:return a.a="⟨","OPFS";case 545:return a.a="⟨","OPFS";case 546:return a.a="Λ","AIUG";case 547:return a.a="λ","AILG";case 548:return a.a=
+"∻","OP";case 549:return a.a="Κ","AIUG";case 550:return a.a="κ","AILG";case 551:return a.a="ȷ","AILL";case 552:return this.pushState("TEXTARG"),"MN";case 553:return a.a="Ι","AIUG";case 554:return a.a="ι","AILG";case 555:return a.a="⅋","OP";case 556:return a.a="⨘","OP";case 557:return a.a="⨽","OP";case 558:return a.a="⨼","OP";case 559:return a.a="⋂","OPM";case 560:return a.a="∩","OP";case 561:return a.a="⫴","OP";case 562:return a.a="⊺","OP";case 563:return a.a="∫","OP";case 564:return a.a="⨚","OP";
+case 565:return a.a="⨙","OP";case 566:return a.a="⨎","OP";case 567:return a.a="⨍","OP";case 568:return a.a="∫","OP";case 569:return a.a="∞","NUM";case 570:return a.a="∞","NUM";case 571:return a.a=a.a.slice(1),"FM";case 572:return a.a="∊","OP";case 573:return a.a="⇒","OPS";case 574:return a.a="⇐","OPS";case 575:return a.a="ı","AILL";case 576:return a.a="ℑ","A";case 577:return a.a="∬","OP";case 578:return a.a="∭","OP";case 579:return a.a="⨌","OP";case 580:return a.a="⟺","OPS";case 581:return a.a="ℏ",
+"A";case 582:return this.pushState("TEXTARG"),"HREF";case 583:return a.a="↪","OPS";case 584:return a.a="↩","OPS";case 585:return a.a="⤦","OP";case 586:return a.a="⤥","OP";case 587:return a.a="♡","OP";case 588:return a.a="ℏ","A";case 589:return a.a="^","ACCENTNS";case 590:return a.a="≩︀","OP";case 591:return a.a="≩︀","OP";case 592:return a.a="≳","OP";case 593:return a.a="≷","OP";case 594:return a.a="⪌","OP";case 595:return a.a="⋛","OP";case 596:return a.a="⋗","OP";case 597:return a.a="⪆","OP";case 598:return a.a=
+">","OP";case 599:return a.a=">","OP";case 600:return a.a="⋧","OP";case 601:return a.a="≩","OP";case 602:return a.a="⪈","OP";case 603:return a.a="⪊","OP";case 604:return a.a="ℷ","A";case 605:return a.a="⋙","OP";case 606:return a.a="≫","OP";case 607:return a.a="⩾","OP";case 608:return a.a="≧","OP";case 609:return a.a="≥","OP";case 610:return a.a="≥","OP";case 611:return a.a="Γ","AIUG";case 612:return a.a="γ","AILG";case 613:return a.a="⌢","OP";case 614:return this.pushState("TEXTARG"),"FRAME";case 615:return"FRAC";
+case 616:return a.a="⫝","OP";case 617:return a.a="⫝̸","OP";case 618:return a.a="∀","OP";case 619:return a.a="♭","OP";case 620:return a.a="⤬","OP";case 621:return a.a="⤯","OP";case 622:return a.a="≒","OP";case 623:return a.a="∃","OP";case 624:return a.a="ð","A";case 625:return a.a="ð","A";case 626:return a.a="Η","AIUG";case 627:return a.a="η","AILG";case 628:return a.a="≡","OP";case 629:return this.pushState("TEXTARG"),"EQROWS";case 630:return this.pushState("TEXTARG"),"EQCOLS";case 631:return a.a=
+"⪕","OP";case 632:return a.a="⪖","OP";case 633:return a.a="≂","OP";case 634:return a.a="=∷","OP";case 635:return a.a="≕","OP";case 636:return a.a="−∷","OP";case 637:return a.a="=∷","OP";case 638:return a.a="=∷","OP";case 639:return a.a="=∷","OP";case 640:return a.a="≕","OP";case 641:return a.a="≖","OP";case 642:return a.a="ϵ","AILG";case 643:return"EVVMATRIX";case 644:return"EVMATRIX";case 645:return"ETOGGLE";case 646:return"EALIGNED";case 647:return"ESMALLMATRIX";case 648:return"EPMATRIX";case 649:return"EMATRIX";
+case 650:return"EGATHERED";case 651:return"ECASES";case 652:return"EBBMATRIX";case 653:return"EBMATRIX";case 654:return"EARRAY";case 655:return"EALIGNED";case 656:return a.a="∅","A";case 657:return a.a="∅","A";case 658:return a.a="↪","OPS";case 659:return a.a="ℓ","A";case 660:return a.a="↕","OPS";case 661:return a.a="⧟","OP";case 662:return a.a="⤐","OPS";case 663:return a.a="↕","OPS";case 664:return a.a="⇂","OPS";case 665:return a.a="⇃","OPS";case 666:return a.a="⇊","OPS";case 667:return a.a="⇓",
+"OPS";case 668:return a.a="↓","OPS";case 669:return a.a="∬","OP";case 670:return a.a="⩞","OP";case 671:return a.a="⌆","OP";case 672:return a.a="…","OP";case 673:return a.a="∔","OP";case 674:return a.a="∸","OP";case 675:return a.a="≑","OP";case 676:return a.a="≑","OP";case 677:return a.a="≐","OP";case 678:return a.a="˙","ACCENT";case 679:return a.a="⋇","OP";case 680:return a.a="÷","OP";case 681:return"DISPLAYSTYLE";case 682:return a.a="⨈","OPM";case 683:return a.a="ϝ","A";case 684:return a.a="♢","OP";
+case 685:return a.a="⋄","OP";case 686:return a.a="⋄","OP";case 687:return a.a=a.a.slice(1),"FM";case 688:return a.a="Δ","AIUG";case 689:return a.a="δ","AILG";case 690:return a.a="∇","OP";case 691:return a.a="°","OP";case 692:return a.a="⤋","OPS";case 693:return a.a="⩷","OP";case 694:return a.a="⋱","OP";case 695:return a.a="̈","ACCENT";case 696:return a.a="⃛","OP";case 697:return a.a="⃛","ACCENT";case 698:return a.a="⃜","OP";case 699:return a.a="⃜","ACCENT";case 700:return a.a="‡","OP";case 701:return a.a=
+"∷","OP";case 702:return a.a="⤏","OPS";case 703:return a.a="⫤","OP";case 704:return a.a="⫣","OP";case 705:return a.a="⊣","OP";case 706:return a.a="⤏","OPS";case 707:return a.a="⤎","OPS";case 708:return a.a="↓","OPS";case 709:return a.a="ℸ","A";case 710:return a.a="†","OP";case 711:return a.a="↷","OP";case 712:return a.a="↶","OP";case 713:return a.a="⤻","OP";case 714:return a.a="⋏","OP";case 715:return a.a="⋎","OP";case 716:return a.a="⋟","OP";case 717:return a.a="⋞","OP";case 718:return a.a="⊍","OP";
+case 719:return a.a="⋓","OP";case 720:return a.a="∪","OP";case 721:return a.a="∐","OPM";case 722:return a.a="∐","OPM";case 723:return a.a="∮","OP";case 724:return a.a="⨇","OPM";case 725:return a.a="∮","OP";case 726:return a.a="≅","OP";case 727:return a.a="∁","OP";case 728:return this.j("TEXTARG"),"COLSPAN";case 729:return this.pushState("TEXTARG"),"COLOR";case 730:return a.a="∷∼","OP";case 731:return a.a="∶∼","OP";case 732:return a.a="⩴","OP";case 733:return a.a="≔","OP";case 734:return a.a="∷−",
+"OP";case 735:return a.a="≔","OP";case 736:return a.a="∷≈","OP";case 737:return a.a="∶≈","OP";case 738:return a.a="∷","OP";case 739:return a.a=":","OP";case 740:return this.pushState("TEXTARG"),"COLLINES";case 741:return this.pushState("TEXTARG"),"COLLAYOUT";case 742:return this.j("TEXTARG"),"COLALIGN";case 743:return a.a="♣","OP";case 744:return a.a="¯","ACCENT";case 745:return a.a="⊝","OP";case 746:return a.a="⊚","OP";case 747:return a.a="⊛","OP";case 748:return a.a="⥁","OP";case 749:return a.a=
+"⥀","OP";case 750:return a.a="≗","OP";case 751:return a.a="∘","OP";case 752:return"TEXCHOOSE";case 753:return a.a="χ","AILG";case 754:return a.a="ˇ","ACCENTNS";case 755:return"CELLOPTS";case 756:return a.a="⋯","OP";case 757:return a.a="·","OP";case 758:return a.a="⋅","OP";case 759:return a.a="⋒","OP";case 760:return a.a="∩","OP";case 761:return a.a="⪮","OP";case 762:return a.a="≎","OP";case 763:return a.a="≏","OP";case 764:return a.a="•","OP";case 765:return a.a="⨲","OP";case 766:return a.a="⊠","OP";
+case 767:return a.a="⊞","OP";case 768:return a.a="⊟","OP";case 769:return"BOXED";case 770:return a.a="⊡","OP";case 771:return a.a="⧄","OP";case 772:return a.a="⧇","OP";case 773:return a.a="⧅","OP";case 774:return a.a="⧆","OP";case 775:return a.a="□","OP";case 776:return a.a="⋈","OP";case 777:return a.a="⊥","OP";case 778:return a.a="⊥","OP";case 779:return"MATHBF";case 780:return a.a="▸","OP";case 781:return a.a="◂","OP";case 782:return a.a="▾","OP";case 783:return a.a="▴","OP";case 784:return a.a=
+"■","OP";case 785:return a.a="⧫","OP";case 786:return a.a="⤍","OPS";case 787:return"BINOM";case 788:return a.a="⋀","OPM";case 789:return a.a="⋁","OPM";case 790:return a.a="⨄","OPM";case 791:return a.a="△","OP";case 792:return a.a="▽","OP";case 793:return a.a="⨉","OPM";case 794:return a.a="★","OP";case 795:return a.a="⨆","OPM";case 796:return a.a="⨅","OPM";case 797:return"BBIG";case 798:return"BIG";case 799:return a.a="⨂","OPM";case 800:return a.a="⨁","OPM";case 801:return a.a="⨀","OPM";case 802:return"BBIGL";
+case 803:return"BIGL";case 804:return a.a="⫼","OPM";case 805:return"BBIGG";case 806:return"BIGG";case 807:return"BBIGGL";case 808:return"BIGGL";case 809:return"BBIGG";case 810:return"BIGG";case 811:return a.a="⨃","OPM";case 812:return a.a="⋃","OPM";case 813:return a.a="○","OP";case 814:return a.a="⋂","OPM";case 815:return"BBIG";case 816:return"BIG";case 817:return this.pushState("TEXTARG"),"BGCOLOR";case 818:return a.a="≬","OP";case 819:return a.a="ℶ","A";case 820:return a.a="Β","AIUG";case 821:return a.a=
+"β","AILG";case 822:return"BVVMATRIX";case 823:return"BVMATRIX";case 824:return"BTOGGLE";case 825:return"BALIGNED";case 826:return"BSMALLMATRIX";case 827:return"BPMATRIX";case 828:return"BMATRIX";case 829:return"BGATHERED";case 830:return"BCASES";case 831:return"BBBMATRIX";case 832:return"BBMATRIX";case 833:return this.pushState("TEXTARG"),this.pushState("TEXTOPTARG"),this.pushState("TRYOPTARG"),"BARRAY";case 834:return"BALIGNED";case 835:return a.a="∵","OP";case 836:return a.a="ℿ","A";case 837:return a.a=
+"⌅","OP";case 838:return a.a="¯","ACCENTNS";case 839:return a.a="\\","OP";case 840:return a.a="⋍","OP";case 841:return a.a="∽","OP";case 842:return a.a="‵","OPP";case 843:return a.a="϶","OP";case 844:return"TEXATOP";case 845:return a.a="≍","OP";case 846:return a.a="∗","OP";case 847:return"ARRAYOPTS";case 848:return"ARRAY";case 849:return a.a=a.a.slice(1),"F";case 850:return a.a="≊","OP";case 851:return a.a="≈","OP";case 852:return a.a="∠","OP";case 853:return a.a="⨿","OP";case 854:return a.a="Α",
+"AIUG";case 855:return a.a="α","AILG";case 856:return this.pushState("TEXTARG"),"ALIGN";case 857:return a.a="ℵ","A";case 858:return"AIUL";case 859:return"AIUG";case 860:return"AILL";case 861:return"AILG";case 862:return a.a="⋰","OP";case 863:return a.a="Å","A";case 864:return"A";case 865:return a.a="$","A";case 866:return a.a="}","OPFS";case 867:return a.a="‖","OPFS";case 868:return a.a="{","OPFS";case 869:return"THICKSPACE";case 870:return"MEDSPACE";case 871:return"THINSPACE";case 872:return a.a=
+"&","A";case 873:return a.a="%","A";case 874:return a.a="#","OP";case 875:return"NEGSPACE";case 876:return a.a="−","OP";case 877:return a.a="⁗","OPP";case 878:return a.a="‴","OPP";case 879:return a.a="″","OPP";case 880:return a.a="′","OPP";case 881:return"HIGH_SURROGATE";case 882:return"LOW_SURROGATE";case 883:return"BMP_CHARACTER"}},rules:[/^(?:.)/,/^(?:\$\$|\\\[|\$|\\\()/,/^(?:$)/,/^(?:\\[$\\])/,/^(?:[<&>])/,/^(?:[^])/,/^(?:\s*\[)/,/^(?:.)/,/^(?:([^\\\]]|(\\[\\\]]))+)/,/^(?:\])/,/^(?:\s*\{)/,/^(?:([^\\\}]|(\\[\\\}]))+)/,
+/^(?:\})/,/^(?:\])/,/^(?:\s+)/,/^(?:\$\$|\\\]|\$|\\\))/,/^(?:\{)/,/^(?:\})/,/^(?:\^)/,/^(?:_)/,/^(?:\.)/,/^(?:&)/,/^(?:\\\\)/,/^(?:[0-9]+(?:\.[0-9]+)?|[\u0660-\u0669]+(?:\u066B[\u0660-\u0669]+)?|(?:\uD835[\uDFCE-\uDFD7])+|(?:\uD835[\uDFD8-\uDFE1])+|(?:\uD835[\uDFE2-\uDFEB])+|(?:\uD835[\uDFEC-\uDFF5])+|(?:\uD835[\uDFF6-\uDFFF])+)/,/^(?:[a-zA-Z]+)/,/^(?:\\Zeta)/,/^(?:\\zeta)/,/^(?:\\xrightleftharpoons)/,/^(?:\\xRightarrow)/,/^(?:\\xrightarrow)/,/^(?:\\xmapsto)/,/^(?:\\xleftrightharpoons)/,/^(?:\\xLeftrightarrow)/,
+/^(?:\\xleftrightarrow)/,/^(?:\\xLeftarrow)/,/^(?:\\xleftarrow)/,/^(?:\\Xi)/,/^(?:\\xi)/,/^(?:\\xhookrightarrow)/,/^(?:\\xhookleftarrow)/,/^(?:\\wr)/,/^(?:\\wp)/,/^(?:\\widevec)/,/^(?:\\widetilde)/,/^(?:\\widehat)/,/^(?:\\widecheck)/,/^(?:\\widebar)/,/^(?:\\wedgeq)/,/^(?:\\Wedge)/,/^(?:\\wedge)/,/^(?:\\Vvert)/,/^(?:\\Vvdash)/,/^(?:\\Vert)/,/^(?:\\vert)/,/^(?:\\veebar)/,/^(?:\\Vee)/,/^(?:\\vee)/,/^(?:\\vec)/,/^(?:\\vdots)/,/^(?:\\VDash)/,/^(?:\\Vdash)/,/^(?:\\vDash)/,/^(?:\\vdash)/,/^(?:\\Vbar)/,/^(?:\\vartriangleright)/,
+/^(?:\\vartriangleleft)/,/^(?:\\vartriangle)/,/^(?:\\vartheta)/,/^(?:\\varsupsetneqq)/,/^(?:\\varsupsetneq)/,/^(?:\\varsubsetneqq)/,/^(?:\\varsubsetneqq)/,/^(?:\\varsubsetneq)/,/^(?:\\varsigma)/,/^(?:\\varrho)/,/^(?:\\varpropto)/,/^(?:\\varpi)/,/^(?:\\varphi)/,/^(?:\\varnothing)/,/^(?:\\varkappa)/,/^(?:\\varepsilon)/,/^(?:\\Uuparrow)/,/^(?:\\upuparrows)/,/^(?:\\Upsilon)/,/^(?:\\upsilon)/,/^(?:\\Upsi)/,/^(?:\\uplus)/,/^(?:\\upint)/,/^(?:\\upharpoonright)/,/^(?:\\upharpoonleft)/,/^(?:\\Updownarrow)/,
+/^(?:\\updownarrow)/,/^(?:\\updarr)/,/^(?:\\Uparrow)/,/^(?:\\uparrow)/,/^(?:\\uparr)/,/^(?:\\unrhd)/,/^(?:\\unlhd)/,/^(?:\\Union)/,/^(?:\\union)/,/^(?:\\underset)/,/^(?:\\underoverset)/,/^(?:\\underline)/,/^(?:\\underbrace)/,/^(?:\\udots)/,/^(?:\u2ADD\u0338)/,/^(?:\u2ACC\uFE00)/,/^(?:\u2ACB\uFE00)/,/^(?:\u2AB0\u0338)/,/^(?:\u2AAF\u0338)/,/^(?:\u2AA2\u0338)/,/^(?:\u2AA1\u0338)/,/^(?:\u2A7E\u0338)/,/^(?:\u2A7D\u0338)/,/^(?:\u29D0\u0338)/,/^(?:\u29CF\u0338)/,/^(?:\u2290\u0338)/,/^(?:\u228F\u0338)/,/^(?:\u228B\uFE00)/,
+/^(?:\u228A\uFE00)/,/^(?:\u2283\u20D2)/,/^(?:\u2282\u20D2)/,/^(?:\u227F\u0338)/,/^(?:\u226B\u0338)/,/^(?:\u226A\u0338)/,/^(?:\u2269\uFE00)/,/^(?:\u2268\uFE00)/,/^(?:\u2266\u0338)/,/^(?:\u224F\u0338)/,/^(?:\u224E\u0338)/,/^(?:\u2242\u0338)/,/^(?:\u223D\u0331)/,/^(?:\u2237\u2248)/,/^(?:\u2237\u223C)/,/^(?:\u2237\u2212)/,/^(?:\u2236\u2248)/,/^(?:\u2236\u223C)/,/^(?:\u2212\u2237)/,/^(?:\u007C\u007C\u007C)/,/^(?:\u007C\u007C)/,/^(?:\u003E\u003D)/,/^(?:\u003D\u2237)/,/^(?:\u003D\u2237)/,/^(?:\u003D\u003D)/,
+/^(?:\u003C\u003E)/,/^(?:\u003C\u003D)/,/^(?:\u003A\u003D)/,/^(?:\u002F\u003D)/,/^(?:\u002F\u002F)/,/^(?:\u002E\u002E\u002E)/,/^(?:\u002E\u002E)/,/^(?:\u002D\u003E)/,/^(?:\u002D\u003D)/,/^(?:\u002D\u002D)/,/^(?:\u002B\u003D)/,/^(?:\u002B\u002B)/,/^(?:\u002A\u003D)/,/^(?:\u002A\u002A)/,/^(?:\u0026\u0026)/,/^(?:\u0021\u003D)/,/^(?:\u0021\u0021)/,/^(?:\\twoheadrightarrowtail)/,/^(?:\\twoheadrightarrow)/,/^(?:\\twoheadleftarrow)/,/^(?:\\tripleintegral)/,/^(?:\\trianglerighteq)/,/^(?:\\triangleright)/,
+/^(?:\\triangleq)/,/^(?:\\trianglelefteq)/,/^(?:\\triangleleft)/,/^(?:\\triangledown)/,/^(?:\\triangle)/,/^(?:\\towa)/,/^(?:\\tosa)/,/^(?:\\top)/,/^(?:\\tooltip)/,/^(?:\\tona)/,/^(?:\\toggle)/,/^(?:\\toea)/,/^(?:\\to)/,/^(?:\\timesb)/,/^(?:\\times)/,/^(?:\\tilde)/,/^(?:\\thinspace)/,/^(?:\\thickspace)/,/^(?:\\thicksim)/,/^(?:\\thickapprox)/,/^(?:\\Theta)/,/^(?:\\theta)/,/^(?:\\therefore)/,/^(?:\\tfrac)/,/^(?:\\textstyle)/,/^(?:\\textsize)/,/^(?:\\textquotedblright)/,/^(?:\\textquotedblleft)/,/^(?:\\textasciitilde)/,
+/^(?:\\textasciigrave)/,/^(?:\\textasciicircumflex)/,/^(?:\\textasciiacute)/,/^(?:\\text)/,/^(?:\\tensor)/,/^(?:\\tbinom)/,/^(?:\\Tau)/,/^(?:\\tau)/,/^(?:\\swArrow)/,/^(?:\\swarrow)/,/^(?:\\swArr)/,/^(?:\\swarr)/,/^(?:\\surd)/,/^(?:\\supsetneqq)/,/^(?:\\supsetneq)/,/^(?:\\supseteqq)/,/^(?:\\supseteq)/,/^(?:\\Supset)/,/^(?:\\supset)/,/^(?:\\sum)/,/^(?:\\succsim)/,/^(?:\\succnsim)/,/^(?:\\succneqq)/,/^(?:\\succnapprox)/,/^(?:\\succeq)/,/^(?:\\succcurlyeq)/,/^(?:\\succapprox)/,/^(?:\\succ)/,/^(?:\\substack)/,
+/^(?:\\subsetneqq)/,/^(?:\\subsetneq)/,/^(?:\\subseteqq)/,/^(?:\\subseteq)/,/^(?:\\Subset)/,/^(?:\\subset)/,/^(?:\\statusline)/,/^(?:\\star)/,/^(?:\\stackrel)/,/^(?:\\sslash)/,/^(?:\\square)/,/^(?:\\sqsupseteq)/,/^(?:\\sqsupset)/,/^(?:\\sqsubseteq)/,/^(?:\\sqsubset)/,/^(?:\\sqrt)/,/^(?:\\sqcup)/,/^(?:\\sqcap)/,/^(?:\\sphericalangle)/,/^(?:\\spadesuit)/,/^(?:\\space)/,/^(?:\\smile)/,/^(?:\\smallsmile)/,/^(?:\\smallsetminus)/,/^(?:\\smallfrown)/,/^(?:\\slash)/,/^(?:\\simeq)/,/^(?:\\sim)/,/^(?:\\Sigma)/,
+/^(?:\\sigma)/,/^(?:\\shuffle)/,/^(?:\\shortparallel)/,/^(?:\\shortmid)/,/^(?:\\sharp)/,/^(?:\\setminus)/,/^(?:\\seovnearrow)/,/^(?:\\seArrow)/,/^(?:\\searrow)/,/^(?:\\seArr)/,/^(?:\\searr)/,/^(?:\\scriptsize)/,/^(?:\\scriptscriptsize)/,/^(?:\\rtimes)/,/^(?:\\Rsh)/,/^(?:\\Rrightarrow)/,/^(?:\\rrangle)/,/^(?:\\rq)/,/^(?:\\rowspan)/,/^(?:\\rowopts)/,/^(?:\\rowlines)/,/^(?:\\rowalign)/,/^(?:\\root)/,/^(?:\\rmoustache)/,/^(?:\\risingdotseq)/,/^(?:\\righttoleftarrow)/,/^(?:\\rightthreetimes)/,/^(?:\\rightsquigarrow)/,
+/^(?:\\rightrightarrows)/,/^(?:\\rightleftharpoons)/,/^(?:\\rightleftarrows)/,/^(?:\\rightharpoonup)/,/^(?:\\rightharpoondown)/,/^(?:\\rightarrowtriangle)/,/^(?:\\rightarrowtail)/,/^(?:\\Rightarrow)/,/^(?:\\rightarrow)/,/^(?:\\right)/,/^(?:\\Rho)/,/^(?:\\rho)/,/^(?:\\rhd)/,/^(?:\\rfloor)/,/^(?:\\Re)/,/^(?:\\rdiagovsearrow)/,/^(?:\\rdiagovfdiag)/,/^(?:\\rceil)/,/^(?:\\rbrack)/,/^(?:\\rbrace)/,/^(?:\\rangle)/,/^(?:\\rang)/,/^(?:\\questeq)/,/^(?:\\quadrupleintegral)/,/^(?:\\quad)/,/^(?:\\qquad)/,/^(?:\\qed)/,
+/^(?:\\Psi)/,/^(?:\\psi)/,/^(?:\\propto)/,/^(?:\\product)/,/^(?:\\prod)/,/^(?:\\prime)/,/^(?:\\precsim)/,/^(?:\\precnsim)/,/^(?:\\precneqq)/,/^(?:\\precnapprox)/,/^(?:\\preceq)/,/^(?:\\preccurlyeq)/,/^(?:\\precapprox)/,/^(?:\\prec)/,/^(?:\\pmod)/,/^(?:\\pm)/,/^(?:\\plusdot)/,/^(?:\\plusb)/,/^(?:\\pitchfork)/,/^(?:\\Pi)/,/^(?:\\pi)/,/^(?:\\Phi)/,/^(?:\\phi)/,/^(?:\\phantom)/,/^(?:\\Perp)/,/^(?:\\perp)/,/^(?:\\partialmeetcontraction)/,/^(?:\\partial)/,/^(?:\\parr)/,/^(?:\\parallel)/,/^(?:\\padding)/,
+/^(?:\\overset)/,/^(?:\\overline)/,/^(?:\\overbrace)/,/^(?:\\over)/,/^(?:\\Otimes)/,/^(?:\\otimes)/,/^(?:\\oslash)/,/^(?:[\u007E\u00AF\u02C6\u02C7\u02C9\u02CD\u02DC\u02F7\u0302\u203E\u2044\u2190-\u2199\u219C-\u21AD\u21AF-\u21B5\u21B9\u21BC-\u21CC\u21D0-\u21DD\u21E0-\u21F0\u21F3\u21F5\u21F6\u21FD-\u21FF\u2215\u221A\u23B4\u23B5\u23DC-\u23E1\u27F0\u27F1\u27F5-\u27FF\u290A-\u2910\u2912\u2913\u2921\u2922\u294E-\u2961\u296E\u296F\u2B45\u2B46])/,/^(?:[\u2032-\u2035\u2057])/,/^(?:[\u220F-\u2211\u22C0-\u22C3\u2A00-\u2A0A\u2A10-\u2A14\u2AFC\u2AFF])/,
+/^(?:\\Oplus)/,/^(?:\\oplus)/,/^(?:[\u0028\u0029\u005B\u005D\u007C\u2016\u2308-\u230B\u2329\u232A\u2772\u2773\u27E6-\u27EF\u2980\u2983-\u2998\u29FC\u29FD])/,/^(?:[\u2018\u2019\u201C\u201D])/,/^(?:\\operatorname)/,/^(?:[\u0021-\u0023\u002A-\u002C\u002F\u003A-\u0040\u0060\u00A8\u00AA\u00AC\u00B0-\u00B4\u00B7-\u00BA\u00D7\u00F7\u02CA\u02CB\u02D8-\u02DA\u02DD\u0311\u03F6\u201A\u201B\u201E-\u2022\u2026\u2036\u2037\u2043\u2061-\u2064\u20DB\u20DC\u2145\u2146\u214B\u219A\u219B\u21AE\u21B6-\u21B8\u21BA\u21BB\u21CD-\u21CF\u21DE\u21DF\u21F1\u21F2\u21F4\u21F7-\u21FC\u2200-\u2204\u2206-\u220E\u2212-\u2214\u2216-\u2219\u221B-\u221D\u221F-\u22BF\u22C4-\u22FF\u2305\u2306\u2322\u2323\u23B0\u23B1\u25A0\u25A1\u25AA\u25AB\u25AD-\u25B9\u25BC-\u25CF\u25D6\u25D7\u25E6\u2605\u2660-\u2663\u266D-\u266F\u2758\u27F2\u27F3\u2900-\u2909\u2911\u2914-\u2920\u2923-\u294D\u2962-\u296D\u2970-\u297F\u2981\u2982\u2999-\u29D9\u29DB-\u29FB\u29FE\u29FF\u2A0B-\u2A0F\u2A15-\u2ADB\u2ADD-\u2AFB\u2AFD\u2AFE])/,
+/^(?:\\ominus)/,/^(?:\\omicron)/,/^(?:\\Omega)/,/^(?:\\omega)/,/^(?:\\oint)/,/^(?:\\oiint)/,/^(?:\\oiiint)/,/^(?:\\odot)/,/^(?:\\odash)/,/^(?:\\obslash)/,/^(?:\\nwovnearrow)/,/^(?:\\nwArrow)/,/^(?:\\nwarrow)/,/^(?:\\nwArr)/,/^(?:\\nwarr)/,/^(?:\\nVDash)/,/^(?:\\nVdash)/,/^(?:\\nvDash)/,/^(?:\\nvdash)/,/^(?:\u221E)/,/^(?:\\Nu)/,/^(?:\\nu)/,/^(?:\\ntrianglerighteq)/,/^(?:\\ntriangleright)/,/^(?:\\ntrianglelefteq)/,/^(?:\\ntriangleleft)/,/^(?:\\nsupseteq)/,/^(?:\\nsupset)/,/^(?:\\nsuccsim)/,/^(?:\\nsucceq)/,
+/^(?:\\nsucc)/,/^(?:\\nsubseteqq)/,/^(?:\\nsubseteq)/,/^(?:\\nsubset)/,/^(?:\\nsime)/,/^(?:\\nsim)/,/^(?:\\nshortparallel)/,/^(?:\\nshortmid)/,/^(?:\\nRightarrow)/,/^(?:\\nrightarrow)/,/^(?:\\npreceq)/,/^(?:\\nprec)/,/^(?:\\nparallel)/,/^(?:\\notni)/,/^(?:\\notin)/,/^(?:\\not)/,/^(?:\\nmid)/,/^(?:\\nless)/,/^(?:\\nleqslant)/,/^(?:\\nleqq)/,/^(?:\\nleq)/,/^(?:\\nLeftrightarrow)/,/^(?:\\nleftrightarrow)/,/^(?:\\nLeftarrow)/,/^(?:\\nleftarrow)/,/^(?:\\ni)/,/^(?:\\ngtr)/,/^(?:\\ngeqslant)/,/^(?:\\ngeqq)/,
+/^(?:\\ngeq)/,/^(?:\\nexists)/,/^(?:\\nequiv)/,/^(?:\\neqsim)/,/^(?:\\neq)/,/^(?:\\neovsearrow)/,/^(?:\\neovnwarrow)/,/^(?:\\negthickspace)/,/^(?:\\negspace)/,/^(?:\\negmedspace)/,/^(?:\\neg)/,/^(?:\\neArrow)/,/^(?:\\nearrow)/,/^(?:\\neArr)/,/^(?:\\nearr)/,/^(?:\\ne)/,/^(?:\\ncong)/,/^(?:\\nBumpeq)/,/^(?:\\nbumpeq)/,/^(?:\\natural)/,/^(?:\\napprox)/,/^(?:\\nabla)/,/^(?:\\multiscripts)/,/^(?:\\multimap)/,/^(?:\\Mu)/,/^(?:\\mu)/,/^(?:\\mtext)/,/^(?:\\ms)/,/^(?:\\mp)/,/^(?:\\models)/,/^(?:\\mod)/,/^(?:\\mo)/,
+/^(?:\\mn)/,/^(?:\\mlcp)/,/^(?:\\minusdot)/,/^(?:\\minusb)/,/^(?:\\minus)/,/^(?:\\min)/,/^(?:\\mid)/,/^(?:\\mi)/,/^(?:\\mho)/,/^(?:\\mho)/,/^(?:\\medspace)/,/^(?:\\measuredangle)/,/^(?:\\mathtt)/,/^(?:\\mathsf)/,/^(?:\\mathscr)/,/^(?:\\mathrm)/,/^(?:\\mathrlap)/,/^(?:\\mathrel)/,/^(?:\\mathraisebox)/,/^(?:\\mathop)/,/^(?:\\mathmit)/,/^(?:\\mathllap)/,/^(?:\\mathit)/,/^(?:\\mathfrak)/,/^(?:\\mathfr)/,/^(?:\\mathclap)/,/^(?:\\mathcal)/,/^(?:\\mathbscr)/,/^(?:\\mathbit)/,/^(?:\\mathbin)/,/^(?:\\mathbf)/,
+/^(?:\\mathbcal)/,/^(?:\\mathbb)/,/^(?:\\Mapsto)/,/^(?:\\mapsto)/,/^(?:\\Mapsfrom)/,/^(?:\\map)/,/^(?:\\lvertneqq)/,/^(?:\\lvertneqq)/,/^(?:\\ltimes)/,/^(?:\\lt)/,/^(?:\\Lsh)/,/^(?:\\lq)/,/^(?:\\lozenge)/,/^(?:\\lowint)/,/^(?:\\looparrowright)/,/^(?:\\looparrowleft)/,/^(?:\\Longrightarrow)/,/^(?:\\longrightarrow)/,/^(?:\\longmapsto)/,/^(?:\\Longleftrightarrow)/,/^(?:\\longleftrightarrow)/,/^(?:\\Longleftarrow)/,/^(?:\\longleftarrow)/,/^(?:\\lnsim)/,/^(?:\\lneqq)/,/^(?:\\lneq)/,/^(?:\\lnapprox)/,/^(?:\\lmoustache)/,
+/^(?:\\lll)/,/^(?:\\Lleftarrow)/,/^(?:\\llangle)/,/^(?:\\ll)/,/^(?:\\lhd)/,/^(?:\\lfloor)/,/^(?:\\lesssim)/,/^(?:\\lessgtr)/,/^(?:\\lesseqqgtr)/,/^(?:\\lesseqgtr)/,/^(?:\\lessdot)/,/^(?:\\lessapprox)/,/^(?:\\less)/,/^(?:\\leqslant)/,/^(?:\\leqq)/,/^(?:\\leq)/,/^(?:\\lefttorightarrow)/,/^(?:\\leftthreetimes)/,/^(?:\\leftsquigarrow)/,/^(?:\\leftrightsquigarrow)/,/^(?:\\leftrightharpoons)/,/^(?:\\leftrightarrowtria\*)/,/^(?:\\leftrightarrows)/,/^(?:\\Leftrightarrow)/,/^(?:\\leftrightarrow)/,/^(?:\\leftleftarrows)/,
+/^(?:\\leftharpoonup)/,/^(?:\\leftharpoondown)/,/^(?:\\leftarrowtriangle)/,/^(?:\\leftarrowtail)/,/^(?:\\Leftarrow)/,/^(?:\\leftarrow)/,/^(?:\\left)/,/^(?:\\le)/,/^(?:\\ldots)/,/^(?:\\lceil)/,/^(?:\\lbrack)/,/^(?:\\lbrace)/,/^(?:\\langle)/,/^(?:\\lang)/,/^(?:\\Lambda)/,/^(?:\\lambda)/,/^(?:\\kernelcontraction)/,/^(?:\\Kappa)/,/^(?:\\kappa)/,/^(?:\\jmath)/,/^(?:\\itexnum)/,/^(?:\\Iota)/,/^(?:\\iota)/,/^(?:\\invamp)/,/^(?:\\intx)/,/^(?:\\intprodr)/,/^(?:\\intprod)/,/^(?:\\Intersection)/,/^(?:\\intersection)/,
+/^(?:\\interleave)/,/^(?:\\intercal)/,/^(?:\\integral)/,/^(?:\\intcup)/,/^(?:\\intcap)/,/^(?:\\intBar)/,/^(?:\\intbar)/,/^(?:\\int)/,/^(?:\\infty)/,/^(?:\\infinity)/,/^(?:\\inf)/,/^(?:\\in)/,/^(?:\\implies)/,/^(?:\\impliedby)/,/^(?:\\imath)/,/^(?:\\Im)/,/^(?:\\iint)/,/^(?:\\iiint)/,/^(?:\\iiiint)/,/^(?:\\iff)/,/^(?:\\hslash)/,/^(?:\\href)/,/^(?:\\hookrightarrow)/,/^(?:\\hookleftarrow)/,/^(?:\\hkswarow)/,/^(?:\\hksearow)/,/^(?:\\heartsuit)/,/^(?:\\hbar)/,/^(?:\\hat)/,/^(?:\\gvertneqq)/,/^(?:\\gvertneqq)/,
+/^(?:\\gtrsim)/,/^(?:\\gtrless)/,/^(?:\\gtreqqless)/,/^(?:\\gtreqless)/,/^(?:\\gtrdot)/,/^(?:\\gtrapprox)/,/^(?:\\gt)/,/^(?:\\greater)/,/^(?:\\gnsim)/,/^(?:\\gneqq)/,/^(?:\\gneq)/,/^(?:\\gnapprox)/,/^(?:\\gimel)/,/^(?:\\ggg)/,/^(?:\\gg)/,/^(?:\\geqslant)/,/^(?:\\geqq)/,/^(?:\\geq)/,/^(?:\\ge)/,/^(?:\\Gamma)/,/^(?:\\gamma)/,/^(?:\\frown)/,/^(?:\\frame)/,/^(?:\\frac)/,/^(?:\\forksnot)/,/^(?:\\forks)/,/^(?:\\forall)/,/^(?:\\flat)/,/^(?:\\fdiagovrdiag)/,/^(?:\\fdiagovnearrow)/,/^(?:\\fallingdotseq)/,
+/^(?:\\exists)/,/^(?:\\eth)/,/^(?:\\eth)/,/^(?:\\Eta)/,/^(?:\\eta)/,/^(?:\\equiv)/,/^(?:\\equalrows)/,/^(?:\\equalcols)/,/^(?:\\eqslantless)/,/^(?:\\eqslantgtr)/,/^(?:\\eqsim)/,/^(?:\\Eqqcolon)/,/^(?:\\eqqcolon)/,/^(?:\\Eqcolon)/,/^(?:\\Eqcolon)/,/^(?:\\Eqcolon)/,/^(?:\\Eqcolon)/,/^(?:\\eqcolon)/,/^(?:\\eqcirc)/,/^(?:\\epsilon)/,/^(?:\\end\{Vmatrix\})/,/^(?:\\end\{vmatrix\})/,/^(?:\\endtoggle)/,/^(?:\\end\{split\})/,/^(?:\\end\{smallmatrix\})/,/^(?:\\end\{pmatrix\})/,/^(?:\\end\{matrix\})/,/^(?:\\end\{gathered\})/,
+/^(?:\\end\{cases\})/,/^(?:\\end\{Bmatrix\})/,/^(?:\\end\{bmatrix\})/,/^(?:\\end\{array\})/,/^(?:\\end\{aligned\})/,/^(?:\\emptyset)/,/^(?:\\empty)/,/^(?:\\embedsin)/,/^(?:\\ell)/,/^(?:\\duparr)/,/^(?:\\dualmap)/,/^(?:\\drbkarrow)/,/^(?:\\downuparrow)/,/^(?:\\downharpoonright)/,/^(?:\\downharpoonleft)/,/^(?:\\downdownarrows)/,/^(?:\\Downarrow)/,/^(?:\\downarrow)/,/^(?:\\doubleintegral)/,/^(?:\\doublebarwedge)/,/^(?:\\doublebarwedge)/,/^(?:\\dots)/,/^(?:\\dotplus)/,/^(?:\\dotminus)/,/^(?:\\doteqdot)/,
+/^(?:\\Doteq)/,/^(?:\\doteq)/,/^(?:\\dot)/,/^(?:\\divideontimes)/,/^(?:\\div)/,/^(?:\\displaystyle)/,/^(?:\\disjquant)/,/^(?:\\digamma)/,/^(?:\\diamondsuit)/,/^(?:\\Diamond)/,/^(?:\\diamond)/,/^(?:\\det|\\gcd|\\liminf|\\limsup|\\lim|\\max|\\Pr|\\sup)/,/^(?:\\Delta)/,/^(?:\\delta)/,/^(?:\\Del)/,/^(?:\\degree)/,/^(?:\\Ddownarrow)/,/^(?:\\ddotseq)/,/^(?:\\ddots)/,/^(?:\\ddot)/,/^(?:\\dddot)/,/^(?:\\dddot)/,/^(?:\\ddddot)/,/^(?:\\ddddot)/,/^(?:\\ddagger)/,/^(?:\\dblcolon)/,/^(?:\\dbkarow)/,/^(?:\\Dashv)/,
+/^(?:\\dashV)/,/^(?:\\dashv)/,/^(?:\\dashrightarrow)/,/^(?:\\dashleftarrow)/,/^(?:\\darr)/,/^(?:\\daleth)/,/^(?:\\dagger)/,/^(?:\\curvearrowright)/,/^(?:\\curvearrowleft)/,/^(?:\\curvearrowbotright)/,/^(?:\\curlywedge)/,/^(?:\\curlyvee)/,/^(?:\\curlyeqsucc)/,/^(?:\\curlyeqprec)/,/^(?:\\cupdot)/,/^(?:\\Cup)/,/^(?:\\cup)/,/^(?:\\coproduct)/,/^(?:\\coprod)/,/^(?:\\contourintegral)/,/^(?:\\conjquant)/,/^(?:\\conint)/,/^(?:\\cong)/,/^(?:\\complement)/,/^(?:\\colspan)/,/^(?:\\color)/,/^(?:\\Colonsim)/,
+/^(?:\\colonsim)/,/^(?:\\Coloneqq)/,/^(?:\\coloneqq)/,/^(?:\\Coloneq)/,/^(?:\\coloneq)/,/^(?:\\Colonapprox)/,/^(?:\\colonapprox)/,/^(?:\\Colon)/,/^(?:\\colon)/,/^(?:\\collines)/,/^(?:\\collayout)/,/^(?:\\colalign)/,/^(?:\\clubsuit)/,/^(?:\\closure)/,/^(?:\\circleddash)/,/^(?:\\circledcirc)/,/^(?:\\circledast)/,/^(?:\\circlearrowright)/,/^(?:\\circlearrowleft)/,/^(?:\\circeq)/,/^(?:\\circ)/,/^(?:\\choose)/,/^(?:\\chi)/,/^(?:\\check)/,/^(?:\\cellopts)/,/^(?:\\cdots)/,/^(?:\\cdotp)/,/^(?:\\cdot)/,/^(?:\\Cap)/,
+/^(?:\\cap)/,/^(?:\\bumpeqq)/,/^(?:\\Bumpeq)/,/^(?:\\bumpeq)/,/^(?:\\bullet)/,/^(?:\\btimes)/,/^(?:\\boxtimes)/,/^(?:\\boxplus)/,/^(?:\\boxminus)/,/^(?:\\boxed)/,/^(?:\\boxdot)/,/^(?:\\boxdiag)/,/^(?:\\boxcircle)/,/^(?:\\boxbslash)/,/^(?:\\boxast)/,/^(?:\\Box)/,/^(?:\\bowtie)/,/^(?:\\bottom)/,/^(?:\\bot)/,/^(?:\\boldsymbol)/,/^(?:\\blacktriangleright)/,/^(?:\\blacktriangleleft)/,/^(?:\\blacktriangledown)/,/^(?:\\blacktriangle)/,/^(?:\\blacksquare)/,/^(?:\\blacklozenge)/,/^(?:\\bkarow)/,/^(?:\\binom)/,
+/^(?:\\bigwedge)/,/^(?:\\bigvee)/,/^(?:\\biguplus)/,/^(?:\\bigtriangleup)/,/^(?:\\bigtriangledown)/,/^(?:\\bigtimes)/,/^(?:\\bigstar)/,/^(?:\\bigsqcup)/,/^(?:\\bigsqcap)/,/^(?:\\Bigr)/,/^(?:\\bigr)/,/^(?:\\bigotimes)/,/^(?:\\bigoplus)/,/^(?:\\bigodot)/,/^(?:\\Bigl)/,/^(?:\\bigl)/,/^(?:\\biginterleave)/,/^(?:\\Biggr)/,/^(?:\\biggr)/,/^(?:\\Biggl)/,/^(?:\\biggl)/,/^(?:\\Bigg)/,/^(?:\\bigg)/,/^(?:\\bigcupdot)/,/^(?:\\bigcup)/,/^(?:\\bigcirc)/,/^(?:\\bigcap)/,/^(?:\\Big)/,/^(?:\\big)/,/^(?:\\bgcolor)/,
+/^(?:\\between)/,/^(?:\\beth)/,/^(?:\\Beta)/,/^(?:\\beta)/,/^(?:\\begin\{Vmatrix\})/,/^(?:\\begin\{vmatrix\})/,/^(?:\\begintoggle)/,/^(?:\\begin\{split\})/,/^(?:\\begin\{smallmatrix\})/,/^(?:\\begin\{pmatrix\})/,/^(?:\\begin\{matrix\})/,/^(?:\\begin\{gathered\})/,/^(?:\\begin\{cases\})/,/^(?:\\begin\{Bmatrix\})/,/^(?:\\begin\{bmatrix\})/,/^(?:\\begin\{array\})/,/^(?:\\begin\{aligned\})/,/^(?:\\because)/,/^(?:\\BbbPi)/,/^(?:\\barwedge)/,/^(?:\\bar)/,/^(?:\\backslash)/,/^(?:\\backsimeq)/,/^(?:\\backsim)/,
+/^(?:\\backprime)/,/^(?:\\backepsilon)/,/^(?:\\atop)/,/^(?:\\asymp)/,/^(?:\\ast)/,/^(?:\\arrayopts)/,/^(?:\\array)/,/^(?:\\arccos|\\arcsin|\\arctan|\\arg|\\cosh|\\cos|\\coth|\\cot|\\csc|\\deg|\\dim|\\exp|\\hom|\\ker|\\lg|\\ln|\\log|\\sec|\\sinh|\\sin|\\tanh|\\tan)/,/^(?:\\approxeq)/,/^(?:\\approx)/,/^(?:\\angle)/,/^(?:\\amalg)/,/^(?:\\Alpha)/,/^(?:\\alpha)/,/^(?:\\align)/,/^(?:\\aleph)/,/^(?:[\u0041-\u005A])/,/^(?:[\u0391-\u03A1\u03A3\u03A4\u03A6-\u03A9])/,/^(?:[\u0061-\u007A\u0131\u0237])/,/^(?:[\u03B1-\u03C1\u03C3-\u03C9\u03D1\u03D5\u03D6\u03F0\u03F1\u03F4\u03F5])/,
+/^(?:\\adots)/,/^(?:\\AA)/,/^(?:[\u00F0\u03C2\u03D0\u03D2\u03DA-\u03DD\u03E0\u03E1\u0428\u0608\u0627-\u063A\u2102\u210A-\u210D\u210F-\u2113\u2115\u2118-\u211D\u2124\u2127\u2128\u212B-\u212D\u212F-\u2131\u2133-\u2138\u213C\u213D\u213F\u2205]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB\uDEF0\uDEF1]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDFCB])/,
+/^(?:\\\$)/,/^(?:\\\})/,/^(?:\\\|)/,/^(?:\\\{)/,/^(?:\\;)/,/^(?:\\:)/,/^(?:\\,)/,/^(?:\\&)/,/^(?:\\%)/,/^(?:\\#)/,/^(?:\\!)/,/^(?:-)/,/^(?:'''')/,/^(?:''')/,/^(?:'')/,/^(?:')/,/^(?:[\uD800-\uDBFF])/,/^(?:[\uDC00-\uDFFF])/,/^(?:.)/],M:{MATH0:{rules:[14,15,16,17,18,19,20,21,22,23,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,
+99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,
+225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,
+351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,
+477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,
+603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,
+729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,
+855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883],inclusive:tb},MATH1:{rules:[14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,
+125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,
+251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,
+377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,
+503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,
+629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,
+755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,
+881,882,883],inclusive:tb},OPTARG:{rules:[13,14,15,16,17,18,19,20,21,22,23,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,
+151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,
+277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,
+403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,
+529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,
+655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,
+781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883],inclusive:tb},DOCUMENT:{rules:[1,2,3,4,5],inclusive:yb},TRYOPTARG:{rules:[6,7],inclusive:yb},
+TEXTOPTARG:{rules:[8,9],inclusive:yb},TEXTARG:{rules:[10,11,12],inclusive:yb},INITIAL:{rules:[0,14,15,16,17,18,19,20,21,22,23,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,
+138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,
+264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,
+390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,
+516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,
+642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,
+768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883],inclusive:tb}}}}();Rb.prototype=rb;rb.pa=
+Rb;return new Rb}();window.TeXZilla=zb;window.TeXZilla.setDOMParser=zb.fa;window.TeXZilla.setXMLSerializer=zb.ja;window.TeXZilla.setSafeMode=zb.ia;window.TeXZilla.setItexIdentifierMode=zb.ha;window.TeXZilla.getTeXSource=zb.ca;window.TeXZilla.toMathMLString=zb.Z;window.TeXZilla.toMathML=zb.Y;window.TeXZilla.toImage=zb.na;window.TeXZilla.filterString=zb.Q;window.TeXZilla.filterElement=zb.P;
+})();
diff --git a/comm/suite/editor/components/texzilla/jar.mn b/comm/suite/editor/components/texzilla/jar.mn
new file mode 100644
index 0000000000..28d5a4cc3e
--- /dev/null
+++ b/comm/suite/editor/components/texzilla/jar.mn
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+comm.jar:
+ content/editor/TeXZilla.js (content/TeXZilla.js)
diff --git a/comm/suite/editor/components/texzilla/moz.build b/comm/suite/editor/components/texzilla/moz.build
new file mode 100644
index 0000000000..de5cd1bf81
--- /dev/null
+++ b/comm/suite/editor/components/texzilla/moz.build
@@ -0,0 +1,6 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ["jar.mn"]
diff --git a/comm/suite/editor/modules/editorUtilities.jsm b/comm/suite/editor/modules/editorUtilities.jsm
new file mode 100644
index 0000000000..877df7d0eb
--- /dev/null
+++ b/comm/suite/editor/modules/editorUtilities.jsm
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var EXPORTED_SYMBOLS = ["GetNextUntitledValue"];
+
+var sUntitledCount = 1;
+
+function GetNextUntitledValue() {
+ return sUntitledCount++;
+}
diff --git a/comm/suite/editor/moz.build b/comm/suite/editor/moz.build
new file mode 100644
index 0000000000..d3b471b23d
--- /dev/null
+++ b/comm/suite/editor/moz.build
@@ -0,0 +1,22 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ "base",
+ "components",
+]
+
+EXTRA_COMPONENTS += [
+ "nsComposerCmdLineHandler.js",
+ "nsComposerCmdLineHandler.manifest",
+]
+
+EXTRA_JS_MODULES += [
+ "modules/editorUtilities.jsm",
+]
+
+JS_PREFERENCE_PP_FILES += [
+ "profile/composer.js",
+]
diff --git a/comm/suite/editor/nsComposerCmdLineHandler.js b/comm/suite/editor/nsComposerCmdLineHandler.js
new file mode 100644
index 0000000000..2f27207b82
--- /dev/null
+++ b/comm/suite/editor/nsComposerCmdLineHandler.js
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+const nsICommandLineHandler = Ci.nsICommandLineHandler;
+const nsISupportsString = Ci.nsISupportsString;
+const nsIWindowWatcher = Ci.nsIWindowWatcher;
+
+function nsComposerCmdLineHandler() {}
+nsComposerCmdLineHandler.prototype = {
+ get wrappedJSObject() {
+ return this;
+ },
+
+ /* nsISupports */
+ QueryInterface: ChromeUtils.generateQI([nsICommandLineHandler]),
+
+ /* nsICommandLineHandler */
+ handle(cmdLine) {
+ var args = Cc["@mozilla.org/supports-string;1"].createInstance(
+ nsISupportsString
+ );
+ try {
+ var uristr = cmdLine.handleFlagWithParam("edit", false);
+ if (uristr == null) {
+ // Try the editor flag (used for general.startup.* prefs)
+ uristr = cmdLine.handleFlagWithParam("editor", false);
+ if (uristr == null) {
+ return;
+ }
+ }
+
+ try {
+ args.data = cmdLine.resolveURI(uristr).spec;
+ } catch (e) {
+ return;
+ }
+ } catch (e) {
+ // One of the flags is present but no data, so set default arg.
+ args.data = "about:blank";
+ }
+
+ Services.ww.openWindow(
+ null,
+ "chrome://editor/content",
+ "_blank",
+ "chrome,dialog=no,all",
+ args
+ );
+ cmdLine.preventDefault = true;
+ },
+
+ helpInfo: " -edit <url> Open Composer.\n",
+
+ /* XPCOMUtils */
+ classID: Components.ID("{f7d8db95-ab5d-4393-a796-9112fe758cfa}"),
+};
+
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsComposerCmdLineHandler]);
diff --git a/comm/suite/editor/nsComposerCmdLineHandler.manifest b/comm/suite/editor/nsComposerCmdLineHandler.manifest
new file mode 100644
index 0000000000..8a27624af1
--- /dev/null
+++ b/comm/suite/editor/nsComposerCmdLineHandler.manifest
@@ -0,0 +1,3 @@
+component {f7d8db95-ab5d-4393-a796-9112fe758cfa} nsComposerCmdLineHandler.js
+contract @mozilla.org/commandlinehandler/general-startup;1?type=editor {f7d8db95-ab5d-4393-a796-9112fe758cfa}
+category command-line-handler m-edit @mozilla.org/commandlinehandler/general-startup;1?type=editor
diff --git a/comm/suite/editor/profile/composer.js b/comm/suite/editor/profile/composer.js
new file mode 100644
index 0000000000..e123fc7771
--- /dev/null
+++ b/comm/suite/editor/profile/composer.js
@@ -0,0 +1,70 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Default preferences for seamonkey composer. This file
+ * was copied from mozilla/modules/libpref/src/init/editor.js
+ *
+ * If you're looking for the default prefs of standalone
+ * composer, see mozilla/composer/app/profile/all.js
+ */
+
+pref("editor.author", "");
+
+pref("editor.text_color", "#000000");
+pref("editor.link_color", "#0000FF");
+pref("editor.active_link_color", "#000088");
+pref("editor.followed_link_color", "#FF0000");
+pref("editor.background_color", "#FFFFFF");
+pref("editor.use_background_image", false);
+pref("editor.default_background_image", "");
+pref("editor.use_custom_default_colors", 1);
+
+pref("editor.hrule.height", 2);
+pref("editor.hrule.width", 100);
+pref("editor.hrule.width_percent", true);
+pref("editor.hrule.shading", true);
+pref("editor.hrule.align", 1); // center
+
+pref("editor.table.maintain_structure", true);
+
+pref("editor.prettyprint", true);
+
+pref("editor.history.url_maximum", 10);
+
+pref("editor.publish.", "");
+pref("editor.lastFileLocation.image", "");
+pref("editor.lastFileLocation.html", "");
+pref("editor.save_associated_files", true);
+pref("editor.always_show_publish_dialog", false);
+
+/*
+ * What are the entities that you want Mozilla to save using mnemonic
+ * names rather than numeric codes? E.g. If set, we'll output &nbsp;
+ * otherwise, we may output 0xa0 depending on the charset.
+ *
+ * "none" : don't use any entity names; only use numeric codes.
+ * "basic" : use entity names just for &nbsp; &amp; &lt; &gt; &quot; for
+ * interoperability/exchange with products that don't support more
+ * than that.
+ * "latin1" : use entity names for 8bit accented letters and other special
+ * symbols between 128 and 255.
+ * "html" : use entity names for 8bit accented letters, greek letters, and
+ * other special markup symbols as defined in HTML4.
+ */
+//pref("editor.encode_entity", "html");
+
+#ifndef XP_MACOSX
+#ifdef XP_UNIX
+pref("editor.disable_spell_checker", false);
+pref("editor.dont_lock_spell_files", true);
+#endif
+#endif
+
+pref("editor.CR_creates_new_p", false);
+
+// Pasting images from the clipboard, order of encoding preference:
+// JPEG-PNG-GIF=0, PNG-JPEG-GIF=1, GIF-JPEG-PNG=2
+pref("clipboard.paste_image_type", 1);