From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001
From: Daniel Baumann
Date: Sun, 7 Apr 2024 19:32:43 +0200
Subject: Adding upstream version 1:115.7.0.
Signed-off-by: Daniel Baumann
---
comm/suite/editor/base/content/ComposerCommands.js | 4051 ++++++++++++++++++++
comm/suite/editor/base/content/EditorAllTags.css | 802 ++++
comm/suite/editor/base/content/EditorContent.css | 62 +
.../suite/editor/base/content/EditorContextMenu.js | 122 +
.../base/content/EditorContextMenuOverlay.xhtml | 171 +
.../editor/base/content/StructBarContextMenu.js | 179 +
.../editor/base/content/composerOverlay.xhtml | 28 +
comm/suite/editor/base/content/editingOverlay.js | 387 ++
.../suite/editor/base/content/editingOverlay.xhtml | 247 ++
comm/suite/editor/base/content/editor.js | 3383 ++++++++++++++++
comm/suite/editor/base/content/editor.xhtml | 402 ++
.../base/content/editorApplicationOverlay.js | 161 +
comm/suite/editor/base/content/editorOverlay.xhtml | 1504 ++++++++
.../editor/base/content/editorTasksOverlay.xhtml | 31 +
comm/suite/editor/base/content/editorUtilities.js | 1014 +++++
.../base/content/images/bringtofront-disabled.png | Bin 0 -> 155 bytes
.../editor/base/content/images/bringtofront.png | Bin 0 -> 155 bytes
.../base/content/images/sendtoback-disabled.png | Bin 0 -> 156 bytes
.../editor/base/content/images/sendtoback.png | Bin 0 -> 156 bytes
comm/suite/editor/base/content/images/tag-a.png | Bin 0 -> 185 bytes
comm/suite/editor/base/content/images/tag-abr.png | Bin 0 -> 243 bytes
comm/suite/editor/base/content/images/tag-acr.png | Bin 0 -> 290 bytes
comm/suite/editor/base/content/images/tag-adr.png | Bin 0 -> 259 bytes
.../editor/base/content/images/tag-anchor.png | Bin 0 -> 171 bytes
comm/suite/editor/base/content/images/tag-app.png | Bin 0 -> 251 bytes
comm/suite/editor/base/content/images/tag-ara.png | Bin 0 -> 246 bytes
comm/suite/editor/base/content/images/tag-b.png | Bin 0 -> 177 bytes
comm/suite/editor/base/content/images/tag-bas.png | Bin 0 -> 240 bytes
comm/suite/editor/base/content/images/tag-bdo.png | Bin 0 -> 211 bytes
comm/suite/editor/base/content/images/tag-big.png | Bin 0 -> 213 bytes
comm/suite/editor/base/content/images/tag-blq.png | Bin 0 -> 294 bytes
comm/suite/editor/base/content/images/tag-body.png | Bin 0 -> 247 bytes
comm/suite/editor/base/content/images/tag-br.png | Bin 0 -> 204 bytes
comm/suite/editor/base/content/images/tag-bsf.png | Bin 0 -> 274 bytes
comm/suite/editor/base/content/images/tag-btn.png | Bin 0 -> 259 bytes
comm/suite/editor/base/content/images/tag-cit.png | Bin 0 -> 214 bytes
comm/suite/editor/base/content/images/tag-clg.png | Bin 0 -> 261 bytes
comm/suite/editor/base/content/images/tag-cod.png | Bin 0 -> 214 bytes
comm/suite/editor/base/content/images/tag-col.png | Bin 0 -> 201 bytes
comm/suite/editor/base/content/images/tag-cpt.png | Bin 0 -> 273 bytes
comm/suite/editor/base/content/images/tag-ctr.png | Bin 0 -> 249 bytes
comm/suite/editor/base/content/images/tag-dd.png | Bin 0 -> 188 bytes
comm/suite/editor/base/content/images/tag-del.png | Bin 0 -> 191 bytes
comm/suite/editor/base/content/images/tag-dfn.png | Bin 0 -> 210 bytes
comm/suite/editor/base/content/images/tag-dir.png | Bin 0 -> 200 bytes
comm/suite/editor/base/content/images/tag-div.png | Bin 0 -> 218 bytes
comm/suite/editor/base/content/images/tag-dl.png | Bin 0 -> 185 bytes
comm/suite/editor/base/content/images/tag-dt.png | Bin 0 -> 187 bytes
comm/suite/editor/base/content/images/tag-em.png | Bin 0 -> 196 bytes
comm/suite/editor/base/content/images/tag-fld.png | Bin 0 -> 239 bytes
comm/suite/editor/base/content/images/tag-fnt.png | Bin 0 -> 228 bytes
comm/suite/editor/base/content/images/tag-for.png | Bin 0 -> 237 bytes
comm/suite/editor/base/content/images/tag-frm.png | Bin 0 -> 240 bytes
comm/suite/editor/base/content/images/tag-fst.png | Bin 0 -> 261 bytes
comm/suite/editor/base/content/images/tag-h1.png | Bin 0 -> 184 bytes
comm/suite/editor/base/content/images/tag-h2.png | Bin 0 -> 194 bytes
comm/suite/editor/base/content/images/tag-h3.png | Bin 0 -> 196 bytes
comm/suite/editor/base/content/images/tag-h4.png | Bin 0 -> 196 bytes
comm/suite/editor/base/content/images/tag-h5.png | Bin 0 -> 197 bytes
comm/suite/editor/base/content/images/tag-h6.png | Bin 0 -> 195 bytes
comm/suite/editor/base/content/images/tag-hed.png | Bin 0 -> 241 bytes
comm/suite/editor/base/content/images/tag-hr.png | Bin 0 -> 194 bytes
comm/suite/editor/base/content/images/tag-html.png | Bin 0 -> 222 bytes
comm/suite/editor/base/content/images/tag-i.png | Bin 0 -> 154 bytes
comm/suite/editor/base/content/images/tag-ifr.png | Bin 0 -> 255 bytes
comm/suite/editor/base/content/images/tag-img.png | Bin 0 -> 214 bytes
comm/suite/editor/base/content/images/tag-inp.png | Bin 0 -> 235 bytes
comm/suite/editor/base/content/images/tag-ins.png | Bin 0 -> 207 bytes
comm/suite/editor/base/content/images/tag-isx.png | Bin 0 -> 278 bytes
comm/suite/editor/base/content/images/tag-kbd.png | Bin 0 -> 223 bytes
comm/suite/editor/base/content/images/tag-lbl.png | Bin 0 -> 238 bytes
comm/suite/editor/base/content/images/tag-lgn.png | Bin 0 -> 253 bytes
comm/suite/editor/base/content/images/tag-li.png | Bin 0 -> 167 bytes
comm/suite/editor/base/content/images/tag-lnk.png | Bin 0 -> 219 bytes
comm/suite/editor/base/content/images/tag-lst.png | Bin 0 -> 263 bytes
comm/suite/editor/base/content/images/tag-map.png | Bin 0 -> 228 bytes
comm/suite/editor/base/content/images/tag-men.png | Bin 0 -> 236 bytes
comm/suite/editor/base/content/images/tag-met.png | Bin 0 -> 230 bytes
comm/suite/editor/base/content/images/tag-nbr.png | Bin 0 -> 301 bytes
comm/suite/editor/base/content/images/tag-nfr.png | Bin 0 -> 297 bytes
comm/suite/editor/base/content/images/tag-nsc.png | Bin 0 -> 253 bytes
comm/suite/editor/base/content/images/tag-obj.png | Bin 0 -> 253 bytes
comm/suite/editor/base/content/images/tag-ol.png | Bin 0 -> 197 bytes
comm/suite/editor/base/content/images/tag-opg.png | Bin 0 -> 266 bytes
comm/suite/editor/base/content/images/tag-opt.png | Bin 0 -> 247 bytes
comm/suite/editor/base/content/images/tag-p.png | Bin 0 -> 170 bytes
comm/suite/editor/base/content/images/tag-pln.png | Bin 0 -> 219 bytes
comm/suite/editor/base/content/images/tag-pre.png | Bin 0 -> 211 bytes
comm/suite/editor/base/content/images/tag-prm.png | Bin 0 -> 248 bytes
comm/suite/editor/base/content/images/tag-q.png | Bin 0 -> 185 bytes
comm/suite/editor/base/content/images/tag-s.png | Bin 0 -> 181 bytes
comm/suite/editor/base/content/images/tag-scr.png | Bin 0 -> 257 bytes
comm/suite/editor/base/content/images/tag-slc.png | Bin 0 -> 249 bytes
comm/suite/editor/base/content/images/tag-sml.png | Bin 0 -> 245 bytes
comm/suite/editor/base/content/images/tag-smp.png | Bin 0 -> 246 bytes
comm/suite/editor/base/content/images/tag-spn.png | Bin 0 -> 246 bytes
comm/suite/editor/base/content/images/tag-stk.png | Bin 0 -> 261 bytes
comm/suite/editor/base/content/images/tag-stl.png | Bin 0 -> 245 bytes
comm/suite/editor/base/content/images/tag-stn.png | Bin 0 -> 277 bytes
comm/suite/editor/base/content/images/tag-sub.png | Bin 0 -> 221 bytes
comm/suite/editor/base/content/images/tag-sup.png | Bin 0 -> 218 bytes
comm/suite/editor/base/content/images/tag-tbd.png | Bin 0 -> 258 bytes
comm/suite/editor/base/content/images/tag-tbl.png | Bin 0 -> 240 bytes
comm/suite/editor/base/content/images/tag-td.png | Bin 0 -> 194 bytes
comm/suite/editor/base/content/images/tag-tft.png | Bin 0 -> 219 bytes
comm/suite/editor/base/content/images/tag-th.png | Bin 0 -> 189 bytes
comm/suite/editor/base/content/images/tag-thd.png | Bin 0 -> 253 bytes
comm/suite/editor/base/content/images/tag-tr.png | Bin 0 -> 197 bytes
comm/suite/editor/base/content/images/tag-tt.png | Bin 0 -> 179 bytes
comm/suite/editor/base/content/images/tag-ttl.png | Bin 0 -> 218 bytes
comm/suite/editor/base/content/images/tag-txt.png | Bin 0 -> 289 bytes
comm/suite/editor/base/content/images/tag-u.png | Bin 0 -> 164 bytes
comm/suite/editor/base/content/images/tag-ul.png | Bin 0 -> 182 bytes
.../editor/base/content/images/tag-userdefined.png | Bin 0 -> 178 bytes
comm/suite/editor/base/content/images/tag-var.png | Bin 0 -> 230 bytes
comm/suite/editor/base/content/images/tag-xmp.png | Bin 0 -> 223 bytes
comm/suite/editor/base/content/publishprefs.js | 867 +++++
comm/suite/editor/base/jar.mn | 124 +
comm/suite/editor/base/moz.build | 6 +
.../components/dialogs/content/EdAEAttributes.js | 973 +++++
.../dialogs/content/EdAECSSAttributes.js | 146 +
.../dialogs/content/EdAEHTMLAttributes.js | 367 ++
.../dialogs/content/EdAEJSEAttributes.js | 200 +
.../components/dialogs/content/EdAdvancedEdit.js | 342 ++
.../dialogs/content/EdAdvancedEdit.xhtml | 182 +
.../components/dialogs/content/EdButtonProps.js | 146 +
.../components/dialogs/content/EdButtonProps.xhtml | 92 +
.../components/dialogs/content/EdColorPicker.js | 297 ++
.../components/dialogs/content/EdColorPicker.xhtml | 56 +
.../components/dialogs/content/EdColorProps.js | 476 +++
.../components/dialogs/content/EdColorProps.xhtml | 134 +
.../components/dialogs/content/EdConvertToTable.js | 326 ++
.../dialogs/content/EdConvertToTable.xhtml | 43 +
.../components/dialogs/content/EdDialogCommon.js | 1038 +++++
.../components/dialogs/content/EdDialogTemplate.js | 45 +
.../dialogs/content/EdDialogTemplate.xhtml | 23 +
.../components/dialogs/content/EdDictionary.js | 164 +
.../components/dialogs/content/EdDictionary.xhtml | 59 +
.../components/dialogs/content/EdFieldSetProps.js | 196 +
.../dialogs/content/EdFieldSetProps.xhtml | 67 +
.../components/dialogs/content/EdFormProps.js | 136 +
.../components/dialogs/content/EdFormProps.xhtml | 98 +
.../components/dialogs/content/EdHLineProps.js | 227 ++
.../components/dialogs/content/EdHLineProps.xhtml | 80 +
.../components/dialogs/content/EdImageDialog.js | 661 ++++
.../dialogs/content/EdImageLinkLoader.js | 145 +
.../components/dialogs/content/EdImageProps.js | 293 ++
.../components/dialogs/content/EdImageProps.xhtml | 116 +
.../components/dialogs/content/EdInputImage.js | 189 +
.../components/dialogs/content/EdInputImage.xhtml | 104 +
.../components/dialogs/content/EdInputProps.js | 345 ++
.../components/dialogs/content/EdInputProps.xhtml | 135 +
.../editor/components/dialogs/content/EdInsSrc.js | 160 +
.../components/dialogs/content/EdInsSrc.xhtml | 42 +
.../components/dialogs/content/EdInsertChars.js | 409 ++
.../components/dialogs/content/EdInsertChars.xhtml | 55 +
.../components/dialogs/content/EdInsertMath.js | 330 ++
.../components/dialogs/content/EdInsertMath.xhtml | 60 +
.../components/dialogs/content/EdInsertTOC.js | 378 ++
.../components/dialogs/content/EdInsertTOC.xhtml | 225 ++
.../components/dialogs/content/EdInsertTable.js | 254 ++
.../components/dialogs/content/EdInsertTable.xhtml | 82 +
.../components/dialogs/content/EdLabelProps.js | 118 +
.../components/dialogs/content/EdLabelProps.xhtml | 66 +
.../components/dialogs/content/EdLinkProps.js | 331 ++
.../components/dialogs/content/EdLinkProps.xhtml | 79 +
.../components/dialogs/content/EdListProps.js | 455 +++
.../components/dialogs/content/EdListProps.xhtml | 73 +
.../dialogs/content/EdNamedAnchorProps.js | 159 +
.../dialogs/content/EdNamedAnchorProps.xhtml | 43 +
.../components/dialogs/content/EdPageProps.js | 159 +
.../components/dialogs/content/EdPageProps.xhtml | 50 +
.../editor/components/dialogs/content/EdReplace.js | 382 ++
.../components/dialogs/content/EdReplace.xhtml | 65 +
.../components/dialogs/content/EdSelectProps.js | 770 ++++
.../components/dialogs/content/EdSelectProps.xhtml | 143 +
.../components/dialogs/content/EdSnapToGrid.js | 62 +
.../components/dialogs/content/EdSnapToGrid.xhtml | 47 +
.../components/dialogs/content/EdSpellCheck.js | 495 +++
.../components/dialogs/content/EdSpellCheck.xhtml | 113 +
.../components/dialogs/content/EdTableProps.js | 1439 +++++++
.../components/dialogs/content/EdTableProps.xhtml | 287 ++
.../components/dialogs/content/EdTextAreaProps.js | 171 +
.../dialogs/content/EdTextAreaProps.xhtml | 115 +
.../components/dialogs/content/EditConflict.js | 42 +
.../components/dialogs/content/EditConflict.xhtml | 40 +
.../components/dialogs/content/EditorPublish.js | 558 +++
.../components/dialogs/content/EditorPublish.xhtml | 132 +
.../dialogs/content/EditorPublishOverlay.xhtml | 66 +
.../dialogs/content/EditorPublishProgress.js | 391 ++
.../dialogs/content/EditorPublishProgress.xhtml | 66 +
.../dialogs/content/EditorPublishSettings.js | 343 ++
.../dialogs/content/EditorPublishSettings.xhtml | 50 +
.../dialogs/content/EditorSaveAsCharset.js | 155 +
.../dialogs/content/EditorSaveAsCharset.xhtml | 46 +
.../components/dialogs/content/edImage.inc.xhtml | 248 ++
comm/suite/editor/components/dialogs/jar.mn | 82 +
comm/suite/editor/components/dialogs/moz.build | 6 +
comm/suite/editor/components/moz.build | 10 +
.../prefs/content/editorPrefsOverlay.xhtml | 50 +
.../components/prefs/content/pref-composer.xhtml | 84 +
.../components/prefs/content/pref-editing.js | 187 +
.../components/prefs/content/pref-editing.xhtml | 181 +
comm/suite/editor/components/prefs/jar.mn | 11 +
comm/suite/editor/components/prefs/moz.build | 6 +
.../editor/components/texzilla/content/TeXZilla.js | 339 ++
comm/suite/editor/components/texzilla/jar.mn | 6 +
comm/suite/editor/components/texzilla/moz.build | 6 +
comm/suite/editor/modules/editorUtilities.jsm | 12 +
comm/suite/editor/moz.build | 22 +
comm/suite/editor/nsComposerCmdLineHandler.js | 64 +
.../suite/editor/nsComposerCmdLineHandler.manifest | 3 +
comm/suite/editor/profile/composer.js | 70 +
213 files changed, 32335 insertions(+)
create mode 100644 comm/suite/editor/base/content/ComposerCommands.js
create mode 100644 comm/suite/editor/base/content/EditorAllTags.css
create mode 100644 comm/suite/editor/base/content/EditorContent.css
create mode 100644 comm/suite/editor/base/content/EditorContextMenu.js
create mode 100644 comm/suite/editor/base/content/EditorContextMenuOverlay.xhtml
create mode 100644 comm/suite/editor/base/content/StructBarContextMenu.js
create mode 100644 comm/suite/editor/base/content/composerOverlay.xhtml
create mode 100644 comm/suite/editor/base/content/editingOverlay.js
create mode 100644 comm/suite/editor/base/content/editingOverlay.xhtml
create mode 100644 comm/suite/editor/base/content/editor.js
create mode 100644 comm/suite/editor/base/content/editor.xhtml
create mode 100644 comm/suite/editor/base/content/editorApplicationOverlay.js
create mode 100644 comm/suite/editor/base/content/editorOverlay.xhtml
create mode 100644 comm/suite/editor/base/content/editorTasksOverlay.xhtml
create mode 100644 comm/suite/editor/base/content/editorUtilities.js
create mode 100644 comm/suite/editor/base/content/images/bringtofront-disabled.png
create mode 100644 comm/suite/editor/base/content/images/bringtofront.png
create mode 100644 comm/suite/editor/base/content/images/sendtoback-disabled.png
create mode 100644 comm/suite/editor/base/content/images/sendtoback.png
create mode 100644 comm/suite/editor/base/content/images/tag-a.png
create mode 100644 comm/suite/editor/base/content/images/tag-abr.png
create mode 100644 comm/suite/editor/base/content/images/tag-acr.png
create mode 100644 comm/suite/editor/base/content/images/tag-adr.png
create mode 100644 comm/suite/editor/base/content/images/tag-anchor.png
create mode 100644 comm/suite/editor/base/content/images/tag-app.png
create mode 100644 comm/suite/editor/base/content/images/tag-ara.png
create mode 100644 comm/suite/editor/base/content/images/tag-b.png
create mode 100644 comm/suite/editor/base/content/images/tag-bas.png
create mode 100644 comm/suite/editor/base/content/images/tag-bdo.png
create mode 100644 comm/suite/editor/base/content/images/tag-big.png
create mode 100644 comm/suite/editor/base/content/images/tag-blq.png
create mode 100644 comm/suite/editor/base/content/images/tag-body.png
create mode 100644 comm/suite/editor/base/content/images/tag-br.png
create mode 100644 comm/suite/editor/base/content/images/tag-bsf.png
create mode 100644 comm/suite/editor/base/content/images/tag-btn.png
create mode 100644 comm/suite/editor/base/content/images/tag-cit.png
create mode 100644 comm/suite/editor/base/content/images/tag-clg.png
create mode 100644 comm/suite/editor/base/content/images/tag-cod.png
create mode 100644 comm/suite/editor/base/content/images/tag-col.png
create mode 100644 comm/suite/editor/base/content/images/tag-cpt.png
create mode 100644 comm/suite/editor/base/content/images/tag-ctr.png
create mode 100644 comm/suite/editor/base/content/images/tag-dd.png
create mode 100644 comm/suite/editor/base/content/images/tag-del.png
create mode 100644 comm/suite/editor/base/content/images/tag-dfn.png
create mode 100644 comm/suite/editor/base/content/images/tag-dir.png
create mode 100644 comm/suite/editor/base/content/images/tag-div.png
create mode 100644 comm/suite/editor/base/content/images/tag-dl.png
create mode 100644 comm/suite/editor/base/content/images/tag-dt.png
create mode 100644 comm/suite/editor/base/content/images/tag-em.png
create mode 100644 comm/suite/editor/base/content/images/tag-fld.png
create mode 100644 comm/suite/editor/base/content/images/tag-fnt.png
create mode 100644 comm/suite/editor/base/content/images/tag-for.png
create mode 100644 comm/suite/editor/base/content/images/tag-frm.png
create mode 100644 comm/suite/editor/base/content/images/tag-fst.png
create mode 100644 comm/suite/editor/base/content/images/tag-h1.png
create mode 100644 comm/suite/editor/base/content/images/tag-h2.png
create mode 100644 comm/suite/editor/base/content/images/tag-h3.png
create mode 100644 comm/suite/editor/base/content/images/tag-h4.png
create mode 100644 comm/suite/editor/base/content/images/tag-h5.png
create mode 100644 comm/suite/editor/base/content/images/tag-h6.png
create mode 100644 comm/suite/editor/base/content/images/tag-hed.png
create mode 100644 comm/suite/editor/base/content/images/tag-hr.png
create mode 100644 comm/suite/editor/base/content/images/tag-html.png
create mode 100644 comm/suite/editor/base/content/images/tag-i.png
create mode 100644 comm/suite/editor/base/content/images/tag-ifr.png
create mode 100644 comm/suite/editor/base/content/images/tag-img.png
create mode 100644 comm/suite/editor/base/content/images/tag-inp.png
create mode 100644 comm/suite/editor/base/content/images/tag-ins.png
create mode 100644 comm/suite/editor/base/content/images/tag-isx.png
create mode 100644 comm/suite/editor/base/content/images/tag-kbd.png
create mode 100644 comm/suite/editor/base/content/images/tag-lbl.png
create mode 100644 comm/suite/editor/base/content/images/tag-lgn.png
create mode 100644 comm/suite/editor/base/content/images/tag-li.png
create mode 100644 comm/suite/editor/base/content/images/tag-lnk.png
create mode 100644 comm/suite/editor/base/content/images/tag-lst.png
create mode 100644 comm/suite/editor/base/content/images/tag-map.png
create mode 100644 comm/suite/editor/base/content/images/tag-men.png
create mode 100644 comm/suite/editor/base/content/images/tag-met.png
create mode 100644 comm/suite/editor/base/content/images/tag-nbr.png
create mode 100644 comm/suite/editor/base/content/images/tag-nfr.png
create mode 100644 comm/suite/editor/base/content/images/tag-nsc.png
create mode 100644 comm/suite/editor/base/content/images/tag-obj.png
create mode 100644 comm/suite/editor/base/content/images/tag-ol.png
create mode 100644 comm/suite/editor/base/content/images/tag-opg.png
create mode 100644 comm/suite/editor/base/content/images/tag-opt.png
create mode 100644 comm/suite/editor/base/content/images/tag-p.png
create mode 100644 comm/suite/editor/base/content/images/tag-pln.png
create mode 100644 comm/suite/editor/base/content/images/tag-pre.png
create mode 100644 comm/suite/editor/base/content/images/tag-prm.png
create mode 100644 comm/suite/editor/base/content/images/tag-q.png
create mode 100644 comm/suite/editor/base/content/images/tag-s.png
create mode 100644 comm/suite/editor/base/content/images/tag-scr.png
create mode 100644 comm/suite/editor/base/content/images/tag-slc.png
create mode 100644 comm/suite/editor/base/content/images/tag-sml.png
create mode 100644 comm/suite/editor/base/content/images/tag-smp.png
create mode 100644 comm/suite/editor/base/content/images/tag-spn.png
create mode 100644 comm/suite/editor/base/content/images/tag-stk.png
create mode 100644 comm/suite/editor/base/content/images/tag-stl.png
create mode 100644 comm/suite/editor/base/content/images/tag-stn.png
create mode 100644 comm/suite/editor/base/content/images/tag-sub.png
create mode 100644 comm/suite/editor/base/content/images/tag-sup.png
create mode 100644 comm/suite/editor/base/content/images/tag-tbd.png
create mode 100644 comm/suite/editor/base/content/images/tag-tbl.png
create mode 100644 comm/suite/editor/base/content/images/tag-td.png
create mode 100644 comm/suite/editor/base/content/images/tag-tft.png
create mode 100644 comm/suite/editor/base/content/images/tag-th.png
create mode 100644 comm/suite/editor/base/content/images/tag-thd.png
create mode 100644 comm/suite/editor/base/content/images/tag-tr.png
create mode 100644 comm/suite/editor/base/content/images/tag-tt.png
create mode 100644 comm/suite/editor/base/content/images/tag-ttl.png
create mode 100644 comm/suite/editor/base/content/images/tag-txt.png
create mode 100644 comm/suite/editor/base/content/images/tag-u.png
create mode 100644 comm/suite/editor/base/content/images/tag-ul.png
create mode 100644 comm/suite/editor/base/content/images/tag-userdefined.png
create mode 100644 comm/suite/editor/base/content/images/tag-var.png
create mode 100644 comm/suite/editor/base/content/images/tag-xmp.png
create mode 100644 comm/suite/editor/base/content/publishprefs.js
create mode 100644 comm/suite/editor/base/jar.mn
create mode 100644 comm/suite/editor/base/moz.build
create mode 100644 comm/suite/editor/components/dialogs/content/EdAEAttributes.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdAECSSAttributes.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdAEHTMLAttributes.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdAEJSEAttributes.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdAdvancedEdit.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdAdvancedEdit.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdButtonProps.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdButtonProps.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdColorPicker.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdColorPicker.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdColorProps.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdColorProps.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdConvertToTable.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdConvertToTable.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdDialogCommon.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdDialogTemplate.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdDialogTemplate.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdDictionary.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdDictionary.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdFieldSetProps.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdFieldSetProps.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdFormProps.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdFormProps.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdHLineProps.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdHLineProps.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdImageDialog.js
create mode 100755 comm/suite/editor/components/dialogs/content/EdImageLinkLoader.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdImageProps.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdImageProps.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdInputImage.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdInputImage.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdInputProps.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdInputProps.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdInsSrc.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdInsSrc.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdInsertChars.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdInsertChars.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdInsertMath.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdInsertMath.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdInsertTOC.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdInsertTOC.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdInsertTable.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdInsertTable.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdLabelProps.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdLabelProps.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdLinkProps.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdLinkProps.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdListProps.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdListProps.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdPageProps.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdPageProps.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdReplace.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdReplace.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdSelectProps.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdSelectProps.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdSnapToGrid.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdSnapToGrid.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdSpellCheck.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdSpellCheck.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdTableProps.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdTableProps.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EdTextAreaProps.js
create mode 100644 comm/suite/editor/components/dialogs/content/EdTextAreaProps.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EditConflict.js
create mode 100644 comm/suite/editor/components/dialogs/content/EditConflict.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EditorPublish.js
create mode 100644 comm/suite/editor/components/dialogs/content/EditorPublish.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EditorPublishOverlay.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EditorPublishProgress.js
create mode 100644 comm/suite/editor/components/dialogs/content/EditorPublishProgress.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EditorPublishSettings.js
create mode 100644 comm/suite/editor/components/dialogs/content/EditorPublishSettings.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.js
create mode 100644 comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.xhtml
create mode 100644 comm/suite/editor/components/dialogs/content/edImage.inc.xhtml
create mode 100644 comm/suite/editor/components/dialogs/jar.mn
create mode 100644 comm/suite/editor/components/dialogs/moz.build
create mode 100644 comm/suite/editor/components/moz.build
create mode 100644 comm/suite/editor/components/prefs/content/editorPrefsOverlay.xhtml
create mode 100644 comm/suite/editor/components/prefs/content/pref-composer.xhtml
create mode 100644 comm/suite/editor/components/prefs/content/pref-editing.js
create mode 100644 comm/suite/editor/components/prefs/content/pref-editing.xhtml
create mode 100644 comm/suite/editor/components/prefs/jar.mn
create mode 100644 comm/suite/editor/components/prefs/moz.build
create mode 100644 comm/suite/editor/components/texzilla/content/TeXZilla.js
create mode 100644 comm/suite/editor/components/texzilla/jar.mn
create mode 100644 comm/suite/editor/components/texzilla/moz.build
create mode 100644 comm/suite/editor/modules/editorUtilities.jsm
create mode 100644 comm/suite/editor/moz.build
create mode 100644 comm/suite/editor/nsComposerCmdLineHandler.js
create mode 100644 comm/suite/editor/nsComposerCmdLineHandler.manifest
create mode 100644 comm/suite/editor/profile/composer.js
(limited to 'comm/suite/editor')
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(" ");
+ } catch (e) {}
+ },
+};
+
+var nsInsertBreakAllCommand = {
+ isCommandEnabled(aCommand, dummy) {
+ return IsDocumentEditable() && IsEditingRenderedHTML();
+ },
+
+ getCommandStateParams(aCommand, aParams, aRefCon) {},
+ doCommandParams(aCommand, aParams, aRefCon) {},
+
+ doCommand(aCommand) {
+ try {
+ GetCurrentEditor().insertHTML(" ");
+ } 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+%editorDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+
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
+ 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 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
completely
+ str = str.replace(/\s*<\/p>\s*/g, "");
+
+ // Trim whitespace adjacent to
and tags
+ // and replace
with
+ // (which will be replaced with below)
+ str = str.replace(/\s*
\s*|\s* \s*/g, " ");
+
+ // Trim leading s
+ str = str.replace(/^( )+/, "");
+
+ // Trim trailing s
+ str = str.replace(/( )+$/, "");
+
+ // Reduce multiple internal to just 1
+ // TODO: Maybe add a checkbox to let user decide
+ // str = str.replace(/( )+/g, " ");
+
+ // 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 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 = " ";
+ }
+
+ // 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 = " ";
+ }
+
+ // 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 += "
";
+
+ 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* \s*/g, "\n
");
+
+ // 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 =
+ '
\n
' +
+ str +
+ "
\n
\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 @@
+
+
+
+
+
+
+
+
+
+
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
+ if (start && start.localName == "br") {
+ editor.deleteNode(start);
+ editor.insertNode(start, element, element.childNodes.length);
+ empty = false;
+ }
+ // Still nothing? Insert a 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 @@
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+%edFieldSetProperties;
+
+%edDialogOverlay;
+]>
+
+
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 @@
+
+
+
+
+
+
+
+
+%edFormProperties;
+
+%edDialogOverlay;
+]>
+
+
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 @@
+
+
+
+
+
+
+
+
+%edHLineProperties;
+
+%edDialogOverlay;
+]>
+
+
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
+ 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 @@
+
+# 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/.
+
+
+
+
+
+%edImageProperties;
+
+%composeEditorOverlayDTD;
+
+%edDialogOverlay;
+]>
+
+
+
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 @@
+
+# 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/.
+
+
+
+
+
+%edInputProperties;
+
+%edImageProperties;
+
+%composeEditorOverlayDTD;
+
+%edDialogOverlay;
+]>
+
+
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 @@
+
+
+
+
+
+
+
+%edInputProperties;
+
+%edDialogOverlay;
+]>
+
+
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(/]*>/, "").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 @@
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
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