summaryrefslogtreecommitdiffstats
path: root/comm/suite/editor/components/dialogs/content
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/suite/editor/components/dialogs/content
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/suite/editor/components/dialogs/content')
-rw-r--r--comm/suite/editor/components/dialogs/content/EdAEAttributes.js973
-rw-r--r--comm/suite/editor/components/dialogs/content/EdAECSSAttributes.js146
-rw-r--r--comm/suite/editor/components/dialogs/content/EdAEHTMLAttributes.js367
-rw-r--r--comm/suite/editor/components/dialogs/content/EdAEJSEAttributes.js200
-rw-r--r--comm/suite/editor/components/dialogs/content/EdAdvancedEdit.js342
-rw-r--r--comm/suite/editor/components/dialogs/content/EdAdvancedEdit.xhtml182
-rw-r--r--comm/suite/editor/components/dialogs/content/EdButtonProps.js146
-rw-r--r--comm/suite/editor/components/dialogs/content/EdButtonProps.xhtml92
-rw-r--r--comm/suite/editor/components/dialogs/content/EdColorPicker.js297
-rw-r--r--comm/suite/editor/components/dialogs/content/EdColorPicker.xhtml56
-rw-r--r--comm/suite/editor/components/dialogs/content/EdColorProps.js476
-rw-r--r--comm/suite/editor/components/dialogs/content/EdColorProps.xhtml134
-rw-r--r--comm/suite/editor/components/dialogs/content/EdConvertToTable.js326
-rw-r--r--comm/suite/editor/components/dialogs/content/EdConvertToTable.xhtml43
-rw-r--r--comm/suite/editor/components/dialogs/content/EdDialogCommon.js1038
-rw-r--r--comm/suite/editor/components/dialogs/content/EdDialogTemplate.js45
-rw-r--r--comm/suite/editor/components/dialogs/content/EdDialogTemplate.xhtml23
-rw-r--r--comm/suite/editor/components/dialogs/content/EdDictionary.js164
-rw-r--r--comm/suite/editor/components/dialogs/content/EdDictionary.xhtml59
-rw-r--r--comm/suite/editor/components/dialogs/content/EdFieldSetProps.js196
-rw-r--r--comm/suite/editor/components/dialogs/content/EdFieldSetProps.xhtml67
-rw-r--r--comm/suite/editor/components/dialogs/content/EdFormProps.js136
-rw-r--r--comm/suite/editor/components/dialogs/content/EdFormProps.xhtml98
-rw-r--r--comm/suite/editor/components/dialogs/content/EdHLineProps.js227
-rw-r--r--comm/suite/editor/components/dialogs/content/EdHLineProps.xhtml80
-rw-r--r--comm/suite/editor/components/dialogs/content/EdImageDialog.js661
-rwxr-xr-xcomm/suite/editor/components/dialogs/content/EdImageLinkLoader.js145
-rw-r--r--comm/suite/editor/components/dialogs/content/EdImageProps.js293
-rw-r--r--comm/suite/editor/components/dialogs/content/EdImageProps.xhtml116
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInputImage.js189
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInputImage.xhtml104
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInputProps.js345
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInputProps.xhtml135
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsSrc.js160
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsSrc.xhtml42
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsertChars.js409
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsertChars.xhtml55
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsertMath.js330
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsertMath.xhtml60
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsertTOC.js378
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsertTOC.xhtml225
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsertTable.js254
-rw-r--r--comm/suite/editor/components/dialogs/content/EdInsertTable.xhtml82
-rw-r--r--comm/suite/editor/components/dialogs/content/EdLabelProps.js118
-rw-r--r--comm/suite/editor/components/dialogs/content/EdLabelProps.xhtml66
-rw-r--r--comm/suite/editor/components/dialogs/content/EdLinkProps.js331
-rw-r--r--comm/suite/editor/components/dialogs/content/EdLinkProps.xhtml79
-rw-r--r--comm/suite/editor/components/dialogs/content/EdListProps.js455
-rw-r--r--comm/suite/editor/components/dialogs/content/EdListProps.xhtml73
-rw-r--r--comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.js159
-rw-r--r--comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.xhtml43
-rw-r--r--comm/suite/editor/components/dialogs/content/EdPageProps.js159
-rw-r--r--comm/suite/editor/components/dialogs/content/EdPageProps.xhtml50
-rw-r--r--comm/suite/editor/components/dialogs/content/EdReplace.js382
-rw-r--r--comm/suite/editor/components/dialogs/content/EdReplace.xhtml65
-rw-r--r--comm/suite/editor/components/dialogs/content/EdSelectProps.js770
-rw-r--r--comm/suite/editor/components/dialogs/content/EdSelectProps.xhtml143
-rw-r--r--comm/suite/editor/components/dialogs/content/EdSnapToGrid.js62
-rw-r--r--comm/suite/editor/components/dialogs/content/EdSnapToGrid.xhtml47
-rw-r--r--comm/suite/editor/components/dialogs/content/EdSpellCheck.js495
-rw-r--r--comm/suite/editor/components/dialogs/content/EdSpellCheck.xhtml113
-rw-r--r--comm/suite/editor/components/dialogs/content/EdTableProps.js1439
-rw-r--r--comm/suite/editor/components/dialogs/content/EdTableProps.xhtml287
-rw-r--r--comm/suite/editor/components/dialogs/content/EdTextAreaProps.js171
-rw-r--r--comm/suite/editor/components/dialogs/content/EdTextAreaProps.xhtml115
-rw-r--r--comm/suite/editor/components/dialogs/content/EditConflict.js42
-rw-r--r--comm/suite/editor/components/dialogs/content/EditConflict.xhtml40
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorPublish.js558
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorPublish.xhtml132
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorPublishOverlay.xhtml66
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorPublishProgress.js391
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorPublishProgress.xhtml66
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorPublishSettings.js343
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorPublishSettings.xhtml50
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.js155
-rw-r--r--comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.xhtml46
-rw-r--r--comm/suite/editor/components/dialogs/content/edImage.inc.xhtml248
77 files changed, 17655 insertions, 0 deletions
diff --git a/comm/suite/editor/components/dialogs/content/EdAEAttributes.js b/comm/suite/editor/components/dialogs/content/EdAEAttributes.js
new file mode 100644
index 0000000000..52b7e30fac
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdAEAttributes.js
@@ -0,0 +1,973 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HTML Attributes object for "Name" menulist
+var gHTMLAttr = {};
+
+// JS Events Attributes object for "Name" menulist
+var gJSAttr = {};
+
+// Core HTML attribute values //
+// This is appended to Name menulist when "_core" is attribute name
+var gCoreHTMLAttr = ["^id", "class", "title"];
+
+// Core event attribute values //
+// This is appended to all JS menulists
+// except those elements having "noJSEvents"
+// as a value in their gJSAttr array.
+var gCoreJSEvents = [
+ "onclick",
+ "ondblclick",
+ "onmousedown",
+ "onmouseup",
+ "onmouseover",
+ "onmousemove",
+ "onmouseout",
+ "-",
+ "onkeypress",
+ "onkeydown",
+ "onkeyup",
+];
+
+// Following are commonly-used strings
+
+// Also accept: sRGB: #RRGGBB //
+var gHTMLColors = [
+ "Aqua",
+ "Black",
+ "Blue",
+ "Fuchsia",
+ "Gray",
+ "Green",
+ "Lime",
+ "Maroon",
+ "Navy",
+ "Olive",
+ "Purple",
+ "Red",
+ "Silver",
+ "Teal",
+ "White",
+ "Yellow",
+];
+
+var gHAlign = ["left", "center", "right"];
+
+var gHAlignJustify = ["left", "center", "right", "justify"];
+
+var gHAlignTableContent = ["left", "center", "right", "justify", "char"];
+
+var gVAlignTable = ["top", "middle", "bottom", "baseline"];
+
+var gTarget = ["_blank", "_self", "_parent", "_top"];
+
+// ================ HTML Attributes ================ //
+/* For each element, there is an array of attributes,
+ whose name is the element name,
+ used to fill the "Attribute Name" menulist.
+ For each of those attributes, if they have a specific
+ set of values, those are listed in an array named:
+ "elementName_attName".
+
+ In each values string, the following characters
+ are signal to do input filtering:
+ "#" Allow only integer values
+ "%" Allow integer values or a number ending in "%"
+ "+" Allow integer values and allow "+" or "-" as first character
+ "!" Allow only one character
+ "^" The first character can be only be A-Z, a-z, hyphen, underscore, colon or period
+ "$" is an attribute required by HTML DTD
+*/
+
+/*
+ Most elements have the "dir" attribute,
+ so we use this value array
+ for all elements instead of specifying
+ separately for each element
+*/
+gHTMLAttr.all_dir = ["ltr", "rtl"];
+
+gHTMLAttr.a = [
+ "charset",
+ "type",
+ "name",
+ "href",
+ "^hreflang",
+ "target",
+ "rel",
+ "rev",
+ "!accesskey",
+ "shape", // with imagemap //
+ "coords", // with imagemap //
+ "#tabindex",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.a_target = gTarget;
+
+gHTMLAttr.a_rel = [
+ "alternate",
+ "stylesheet",
+ "start",
+ "next",
+ "prev",
+ "contents",
+ "index",
+ "glossary",
+ "copyright",
+ "chapter",
+ "section",
+ "subsection",
+ "appendix",
+ "help",
+ "bookmark",
+];
+
+gHTMLAttr.a_rev = [
+ "alternate",
+ "stylesheet",
+ "start",
+ "next",
+ "prev",
+ "contents",
+ "index",
+ "glossary",
+ "copyright",
+ "chapter",
+ "section",
+ "subsection",
+ "appendix",
+ "help",
+ "bookmark",
+];
+
+gHTMLAttr.a_shape = ["rect", "circle", "poly", "default"];
+
+gHTMLAttr.abbr = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.acronym = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.address = ["_core", "-", "^lang", "dir"];
+
+// this is deprecated //
+gHTMLAttr.applet = [
+ "codebase",
+ "archive",
+ "code",
+ "object",
+ "alt",
+ "name",
+ "%$width",
+ "%$height",
+ "align",
+ "#hspace",
+ "#vspace",
+ "-",
+ "_core",
+];
+
+gHTMLAttr.applet_align = ["top", "middle", "bottom", "left", "right"];
+
+gHTMLAttr.area = [
+ "shape",
+ "coords",
+ "href",
+ "nohref",
+ "target",
+ "$alt",
+ "#tabindex",
+ "!accesskey",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.area_target = gTarget;
+
+gHTMLAttr.area_shape = ["rect", "circle", "poly", "default"];
+
+gHTMLAttr.area_nohref = ["nohref"];
+
+gHTMLAttr.b = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.base = ["href", "target"];
+
+gHTMLAttr.base_target = gTarget;
+
+// this is deprecated //
+gHTMLAttr.basefont = ["^id", "$size", "color", "face"];
+
+gHTMLAttr.basefont_color = gHTMLColors;
+
+gHTMLAttr.bdo = ["_core", "-", "^lang", "$dir"];
+
+gHTMLAttr.bdo_dir = ["ltr", "rtl"];
+
+gHTMLAttr.big = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.blockquote = ["cite", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.body = [
+ "background",
+ "bgcolor",
+ "text",
+ "link",
+ "vlink",
+ "alink",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.body_bgcolor = gHTMLColors;
+
+gHTMLAttr.body_text = gHTMLColors;
+
+gHTMLAttr.body_link = gHTMLColors;
+
+gHTMLAttr.body_vlink = gHTMLColors;
+
+gHTMLAttr.body_alink = gHTMLColors;
+
+gHTMLAttr.br = ["clear", "-", "_core"];
+
+gHTMLAttr.br_clear = ["none", "left", "all", "right"];
+
+gHTMLAttr.button = [
+ "name",
+ "value",
+ "$type",
+ "disabled",
+ "#tabindex",
+ "!accesskey",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.button_type = ["submit", "button", "reset"];
+
+gHTMLAttr.button_disabled = ["disabled"];
+
+gHTMLAttr.caption = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.caption_align = ["top", "bottom", "left", "right"];
+
+// this is deprecated //
+gHTMLAttr.center = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.cite = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.code = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.col = [
+ "#$span",
+ "%width",
+ "align",
+ "!char",
+ "#charoff",
+ "valign",
+ "char",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.col_span = [
+ "1", // default
+];
+
+gHTMLAttr.col_align = gHAlignTableContent;
+
+gHTMLAttr.col_valign = ["top", "middle", "bottom", "baseline"];
+
+gHTMLAttr.colgroup = [
+ "#$span",
+ "%width",
+ "align",
+ "!char",
+ "#charoff",
+ "valign",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.colgroup_span = [
+ "1", // default
+];
+
+gHTMLAttr.colgroup_align = gHAlignTableContent;
+
+gHTMLAttr.colgroup_valign = ["top", "middle", "bottom", "baseline"];
+
+gHTMLAttr.dd = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.del = ["cite", "datetime", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.dfn = ["_core", "-", "^lang", "dir"];
+
+// this is deprecated //
+gHTMLAttr.dir = ["compact", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.dir_compact = ["compact"];
+
+gHTMLAttr.div = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.div_align = gHAlignJustify;
+
+gHTMLAttr.dl = ["compact", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.dl_compact = ["compact"];
+
+gHTMLAttr.dt = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.em = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.fieldset = ["_core", "-", "^lang", "dir"];
+
+// this is deprecated //
+gHTMLAttr.font = ["+size", "color", "face", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.font_color = gHTMLColors;
+
+gHTMLAttr.form = [
+ "$action",
+ "$method",
+ "enctype",
+ "accept",
+ "name",
+ "accept-charset",
+ "target",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.form_method = ["get", "post"];
+
+gHTMLAttr.form_enctype = ["application/x-www-form-urlencoded"];
+
+gHTMLAttr.form_target = gTarget;
+
+gHTMLAttr.frame = [
+ "longdesc",
+ "name",
+ "src",
+ "#frameborder",
+ "#marginwidth",
+ "#marginheight",
+ "noresize",
+ "$scrolling",
+];
+
+gHTMLAttr.frame_frameborder = ["1", "0"];
+
+gHTMLAttr.frame_noresize = ["noresize"];
+
+gHTMLAttr.frame_scrolling = ["auto", "yes", "no"];
+
+gHTMLAttr.frameset = ["rows", "cols", "-", "_core"];
+
+gHTMLAttr.h1 = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.h1_align = gHAlignJustify;
+
+gHTMLAttr.h2 = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.h2_align = gHAlignJustify;
+
+gHTMLAttr.h3 = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.h3_align = gHAlignJustify;
+
+gHTMLAttr.h4 = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.h4_align = gHAlignJustify;
+
+gHTMLAttr.h5 = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.h5_align = gHAlignJustify;
+
+gHTMLAttr.h6 = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.h6_align = gHAlignJustify;
+
+gHTMLAttr.head = ["profile", "-", "^lang", "dir"];
+
+gHTMLAttr.hr = [
+ "align",
+ "noshade",
+ "#size",
+ "%width",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.hr_align = gHAlign;
+
+gHTMLAttr.hr_noshade = ["noshade"];
+
+gHTMLAttr.html = ["version", "-", "^lang", "dir"];
+
+gHTMLAttr.i = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.iframe = [
+ "longdesc",
+ "name",
+ "src",
+ "$frameborder",
+ "marginwidth",
+ "marginheight",
+ "$scrolling",
+ "align",
+ "%height",
+ "%width",
+ "-",
+ "_core",
+];
+
+gHTMLAttr.iframe_frameborder = ["1", "0"];
+
+gHTMLAttr.iframe_scrolling = ["auto", "yes", "no"];
+
+gHTMLAttr.iframe_align = ["top", "middle", "bottom", "left", "right"];
+
+gHTMLAttr.img = [
+ "$src",
+ "$alt",
+ "longdesc",
+ "name",
+ "%height",
+ "%width",
+ "usemap",
+ "ismap",
+ "align",
+ "#border",
+ "#hspace",
+ "#vspace",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.img_ismap = ["ismap"];
+
+gHTMLAttr.img_align = ["top", "middle", "bottom", "left", "right"];
+
+gHTMLAttr.input = [
+ "$type",
+ "name",
+ "value",
+ "checked",
+ "disabled",
+ "readonly",
+ "#size",
+ "#maxlength",
+ "src",
+ "alt",
+ "usemap",
+ "ismap",
+ "#tabindex",
+ "!accesskey",
+ "accept",
+ "align",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.input_type = [
+ "text",
+ "password",
+ "checkbox",
+ "radio",
+ "submit",
+ "reset",
+ "file",
+ "hidden",
+ "image",
+ "button",
+];
+
+gHTMLAttr.input_checked = ["checked"];
+
+gHTMLAttr.input_disabled = ["disabled"];
+
+gHTMLAttr.input_readonly = ["readonly"];
+
+gHTMLAttr.input_ismap = ["ismap"];
+
+gHTMLAttr.input_align = ["top", "middle", "bottom", "left", "right"];
+
+gHTMLAttr.ins = ["cite", "datetime", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.isindex = ["prompt", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.kbd = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.label = ["for", "!accesskey", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.legend = ["!accesskey", "align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.legend_align = ["top", "bottom", "left", "right"];
+
+gHTMLAttr.li = ["type", "#value", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.li_type = ["disc", "square", "circle", "-", "1", "a", "A", "i", "I"];
+
+gHTMLAttr.link = [
+ "charset",
+ "href",
+ "^hreflang",
+ "type",
+ "rel",
+ "rev",
+ "media",
+ "target",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.link_target = gTarget;
+
+gHTMLAttr.link_rel = [
+ "alternate",
+ "stylesheet",
+ "start",
+ "next",
+ "prev",
+ "contents",
+ "index",
+ "glossary",
+ "copyright",
+ "chapter",
+ "section",
+ "subsection",
+ "appendix",
+ "help",
+ "bookmark",
+];
+
+gHTMLAttr.link_rev = [
+ "alternate",
+ "stylesheet",
+ "start",
+ "next",
+ "prev",
+ "contents",
+ "index",
+ "glossary",
+ "copyright",
+ "chapter",
+ "section",
+ "subsection",
+ "appendix",
+ "help",
+ "bookmark",
+];
+
+gHTMLAttr.map = ["$name", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.menu = ["compact", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.menu_compact = ["compact"];
+
+gHTMLAttr.meta = [
+ "http-equiv",
+ "name",
+ "$content",
+ "scheme",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.noframes = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.noscript = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.object = [
+ "declare",
+ "classid",
+ "codebase",
+ "data",
+ "type",
+ "codetype",
+ "archive",
+ "standby",
+ "%height",
+ "%width",
+ "usemap",
+ "name",
+ "#tabindex",
+ "align",
+ "#border",
+ "#hspace",
+ "#vspace",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.object_declare = ["declare"];
+
+gHTMLAttr.object_align = ["top", "middle", "bottom", "left", "right"];
+
+gHTMLAttr.ol = ["type", "compact", "#start", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.ol_type = ["1", "a", "A", "i", "I"];
+
+gHTMLAttr.ol_compact = ["compact"];
+
+gHTMLAttr.optgroup = ["disabled", "$label", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.optgroup_disabled = ["disabled"];
+
+gHTMLAttr.option = [
+ "selected",
+ "disabled",
+ "label",
+ "value",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.option_selected = ["selected"];
+
+gHTMLAttr.option_disabled = ["disabled"];
+
+gHTMLAttr.p = ["align", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.p_align = gHAlignJustify;
+
+gHTMLAttr.param = ["^id", "$name", "value", "$valuetype", "type"];
+
+gHTMLAttr.param_valuetype = ["data", "ref", "object"];
+
+gHTMLAttr.pre = ["%width", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.q = ["cite", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.s = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.samp = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.script = ["charset", "$type", "language", "src", "defer"];
+
+gHTMLAttr.script_defer = ["defer"];
+
+gHTMLAttr.select = [
+ "name",
+ "#size",
+ "multiple",
+ "disabled",
+ "#tabindex",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.select_multiple = ["multiple"];
+
+gHTMLAttr.select_disabled = ["disabled"];
+
+gHTMLAttr.small = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.span = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.strike = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.strong = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.style = ["$type", "media", "title", "-", "^lang", "dir"];
+
+gHTMLAttr.sub = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.sup = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.table = [
+ "summary",
+ "%width",
+ "#border",
+ "frame",
+ "rules",
+ "#cellspacing",
+ "#cellpadding",
+ "align",
+ "bgcolor",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.table_frame = [
+ "void",
+ "above",
+ "below",
+ "hsides",
+ "lhs",
+ "rhs",
+ "vsides",
+ "box",
+ "border",
+];
+
+gHTMLAttr.table_rules = ["none", "groups", "rows", "cols", "all"];
+
+// Note; This is alignment of the table,
+// not table contents, like all other table child elements
+gHTMLAttr.table_align = gHAlign;
+
+gHTMLAttr.table_bgcolor = gHTMLColors;
+
+gHTMLAttr.tbody = [
+ "align",
+ "!char",
+ "#charoff",
+ "valign",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.tbody_align = gHAlignTableContent;
+
+gHTMLAttr.tbody_valign = gVAlignTable;
+
+gHTMLAttr.td = [
+ "abbr",
+ "axis",
+ "headers",
+ "scope",
+ "$#rowspan",
+ "$#colspan",
+ "align",
+ "!char",
+ "#charoff",
+ "valign",
+ "nowrap",
+ "bgcolor",
+ "%width",
+ "%height",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.td_scope = ["row", "col", "rowgroup", "colgroup"];
+
+gHTMLAttr.td_rowspan = [
+ "1", // default
+];
+
+gHTMLAttr.td_colspan = [
+ "1", // default
+];
+
+gHTMLAttr.td_align = gHAlignTableContent;
+
+gHTMLAttr.td_valign = gVAlignTable;
+
+gHTMLAttr.td_nowrap = ["nowrap"];
+
+gHTMLAttr.td_bgcolor = gHTMLColors;
+
+gHTMLAttr.textarea = [
+ "name",
+ "$#rows",
+ "$#cols",
+ "disabled",
+ "readonly",
+ "#tabindex",
+ "!accesskey",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.textarea_disabled = ["disabled"];
+
+gHTMLAttr.textarea_readonly = ["readonly"];
+
+gHTMLAttr.tfoot = [
+ "align",
+ "!char",
+ "#charoff",
+ "valign",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.tfoot_align = gHAlignTableContent;
+
+gHTMLAttr.tfoot_valign = gVAlignTable;
+
+gHTMLAttr.th = [
+ "abbr",
+ "axis",
+ "headers",
+ "scope",
+ "$#rowspan",
+ "$#colspan",
+ "align",
+ "!char",
+ "#charoff",
+ "valign",
+ "nowrap",
+ "bgcolor",
+ "%width",
+ "%height",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.th_scope = ["row", "col", "rowgroup", "colgroup"];
+
+gHTMLAttr.th_rowspan = [
+ "1", // default
+];
+
+gHTMLAttr.th_colspan = [
+ "1", // default
+];
+
+gHTMLAttr.th_align = gHAlignTableContent;
+
+gHTMLAttr.th_valign = gVAlignTable;
+
+gHTMLAttr.th_nowrap = ["nowrap"];
+
+gHTMLAttr.th_bgcolor = gHTMLColors;
+
+gHTMLAttr.thead = [
+ "align",
+ "!char",
+ "#charoff",
+ "valign",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.thead_align = gHAlignTableContent;
+
+gHTMLAttr.thead_valign = gVAlignTable;
+
+gHTMLAttr.title = ["^lang", "dir"];
+
+gHTMLAttr.tr = [
+ "align",
+ "!char",
+ "#charoff",
+ "valign",
+ "bgcolor",
+ "-",
+ "_core",
+ "-",
+ "^lang",
+ "dir",
+];
+
+gHTMLAttr.tr_align = gHAlignTableContent;
+
+gHTMLAttr.tr_valign = gVAlignTable;
+
+gHTMLAttr.tr_bgcolor = gHTMLColors;
+
+gHTMLAttr.tt = ["_core", "-", "^lang", "dir"];
+
+gHTMLAttr.u = ["_core", "-", "^lang", "dir"];
+gHTMLAttr.ul = ["type", "compact", "-", "_core", "-", "^lang", "dir"];
+
+gHTMLAttr.ul_type = ["disc", "square", "circle"];
+
+gHTMLAttr.ul_compact = ["compact"];
+
+// Prefix with "_" since this is reserved (it's stripped out)
+gHTMLAttr._var = ["_core", "-", "^lang", "dir"];
+
+// ================ JS Attributes ================ //
+// These are element specific even handlers.
+/* Most all elements use gCoreJSEvents, so those
+ are assumed except for those listed here with "noEvents"
+*/
+
+gJSAttr.a = ["onfocus", "onblur"];
+
+gJSAttr.area = ["onfocus", "onblur"];
+
+gJSAttr.body = ["onload", "onupload"];
+
+gJSAttr.button = ["onfocus", "onblur"];
+
+gJSAttr.form = ["onsubmit", "onreset"];
+
+gJSAttr.frameset = ["onload", "onunload"];
+
+gJSAttr.input = ["onfocus", "onblur", "onselect", "onchange"];
+
+gJSAttr.label = ["onfocus", "onblur"];
+
+gJSAttr.select = ["onfocus", "onblur", "onchange"];
+
+gJSAttr.textarea = ["onfocus", "onblur", "onselect", "onchange"];
+
+// Elements that don't have JSEvents:
+gJSAttr.font = ["noJSEvents"];
+
+gJSAttr.applet = ["noJSEvents"];
+
+gJSAttr.isindex = ["noJSEvents"];
+
+gJSAttr.iframe = ["noJSEvents"];
diff --git a/comm/suite/editor/components/dialogs/content/EdAECSSAttributes.js b/comm/suite/editor/components/dialogs/content/EdAECSSAttributes.js
new file mode 100644
index 0000000000..977068bd70
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdAECSSAttributes.js
@@ -0,0 +1,146 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdAdvancedEdit.js */
+/* import-globals-from EdDialogCommon.js */
+
+// build attribute list in tree form from element attributes
+function BuildCSSAttributeTable() {
+ var style = gElement.style;
+ if (style == undefined) {
+ dump("Inline styles undefined\n");
+ return;
+ }
+
+ var declLength = style.length;
+
+ if (declLength == undefined || declLength == 0) {
+ if (declLength == undefined) {
+ dump("Failed to query the number of inline style declarations\n");
+ }
+
+ return;
+ }
+
+ if (declLength > 0) {
+ for (var i = 0; i < declLength; ++i) {
+ var name = style.item(i);
+ var value = style.getPropertyValue(name);
+ AddTreeItem(name, value, "CSSAList", CSSAttrs);
+ }
+ }
+
+ ClearCSSInputWidgets();
+}
+
+function onChangeCSSAttribute() {
+ var name = TrimString(gDialog.AddCSSAttributeNameInput.value);
+ if (!name) {
+ return;
+ }
+
+ var value = TrimString(gDialog.AddCSSAttributeValueInput.value);
+
+ // First try to update existing attribute
+ // If not found, add new attribute
+ if (!UpdateExistingAttribute(name, value, "CSSAList") && value) {
+ AddTreeItem(name, value, "CSSAList", CSSAttrs);
+ }
+}
+
+function ClearCSSInputWidgets() {
+ gDialog.AddCSSAttributeTree.view.selection.clearSelection();
+ gDialog.AddCSSAttributeNameInput.value = "";
+ gDialog.AddCSSAttributeValueInput.value = "";
+ SetTextboxFocus(gDialog.AddCSSAttributeNameInput);
+}
+
+function onSelectCSSTreeItem() {
+ if (!gDoOnSelectTree) {
+ return;
+ }
+
+ var tree = gDialog.AddCSSAttributeTree;
+ if (tree && tree.view.selection.count) {
+ gDialog.AddCSSAttributeNameInput.value = GetTreeItemAttributeStr(
+ getSelectedItem(tree)
+ );
+ gDialog.AddCSSAttributeValueInput.value = GetTreeItemValueStr(
+ getSelectedItem(tree)
+ );
+ }
+}
+
+function onInputCSSAttributeName() {
+ var attName = TrimString(
+ gDialog.AddCSSAttributeNameInput.value
+ ).toLowerCase();
+ var newValue = "";
+
+ var existingValue = GetAndSelectExistingAttributeValue(attName, "CSSAList");
+ if (existingValue) {
+ newValue = existingValue;
+ }
+
+ gDialog.AddCSSAttributeValueInput.value = newValue;
+}
+
+function editCSSAttributeValue(targetCell) {
+ if (IsNotTreeHeader(targetCell)) {
+ gDialog.AddCSSAttributeValueInput.inputField.select();
+ }
+}
+
+function UpdateCSSAttributes() {
+ var CSSAList = document.getElementById("CSSAList");
+ var styleString = "";
+ for (var i = 0; i < CSSAList.childNodes.length; i++) {
+ var item = CSSAList.childNodes[i];
+ var name = GetTreeItemAttributeStr(item);
+ var value = GetTreeItemValueStr(item);
+ // this code allows users to be sloppy in typing in values, and enter
+ // things like "foo: " and "bar;". This will trim off everything after the
+ // respective character.
+ if (name.includes(":")) {
+ name = name.substring(0, name.lastIndexOf(":"));
+ }
+ if (value.includes(";")) {
+ value = value.substring(0, value.lastIndexOf(";"));
+ }
+ if (i == CSSAList.childNodes.length - 1) {
+ // Last property.
+ styleString += name + ": " + value + ";";
+ } else {
+ styleString += name + ": " + value + "; ";
+ }
+ }
+ if (styleString) {
+ // Use editor transactions if modifying the element directly in the document
+ doRemoveAttribute("style");
+ doSetAttribute("style", styleString); // NOTE BUG 18894!!!
+ } else if (gElement.getAttribute("style")) {
+ doRemoveAttribute("style");
+ }
+}
+
+function RemoveCSSAttribute() {
+ // We only allow 1 selected item
+ if (gDialog.AddCSSAttributeTree.view.selection.count) {
+ // Remove the item from the tree
+ // We always rebuild complete "style" string,
+ // so no list of "removed" items
+ getSelectedItem(gDialog.AddCSSAttributeTree).remove();
+
+ ClearCSSInputWidgets();
+ }
+}
+
+function SelectCSSTree(index) {
+ gDoOnSelectTree = false;
+ try {
+ gDialog.AddCSSAttributeTree.selectedIndex = index;
+ } catch (e) {}
+ gDoOnSelectTree = true;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdAEHTMLAttributes.js b/comm/suite/editor/components/dialogs/content/EdAEHTMLAttributes.js
new file mode 100644
index 0000000000..1f96762754
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdAEHTMLAttributes.js
@@ -0,0 +1,367 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdAdvancedEdit.js */
+/* import-globals-from EdDialogCommon.js */
+
+function BuildHTMLAttributeNameList() {
+ gDialog.AddHTMLAttributeNameInput.removeAllItems();
+
+ var elementName = gElement.localName;
+ var attNames = gHTMLAttr[elementName];
+
+ if (attNames && attNames.length) {
+ var menuitem;
+
+ for (var i = 0; i < attNames.length; i++) {
+ var name = attNames[i];
+
+ if (name == "_core") {
+ // Signal to append the common 'core' attributes.
+ for (var j = 0; j < gCoreHTMLAttr.length; j++) {
+ name = gCoreHTMLAttr[j];
+
+ // only filtering rule used for core attributes as of 8-20-01
+ // Add more rules if necessary.
+ if (name.includes("^")) {
+ name = name.replace(/\^/g, "");
+ menuitem = gDialog.AddHTMLAttributeNameInput.appendItem(name, name);
+ menuitem.setAttribute("limitFirstChar", "true");
+ } else {
+ gDialog.AddHTMLAttributeNameInput.appendItem(name, name);
+ }
+ }
+ } else if (name == "-") {
+ // Signal for separator
+ var popup = gDialog.AddHTMLAttributeNameInput.menupopup;
+ if (popup) {
+ var sep = document.createXULElement("menuseparator");
+ if (sep) {
+ popup.appendChild(sep);
+ }
+ }
+ } else {
+ // Get information about value filtering
+ let forceOneChar = name.includes("!");
+ let forceInteger = name.includes("#");
+ let forceSignedInteger = name.includes("+");
+ let forceIntOrPercent = name.includes("%");
+ let limitFirstChar = name.includes("^");
+ // let required = name.includes("$");
+
+ // Strip flag characters
+ name = name.replace(/[!^#%$+]/g, "");
+
+ menuitem = gDialog.AddHTMLAttributeNameInput.appendItem(name, name);
+ if (menuitem) {
+ // Signify "required" attributes by special style
+ // TODO: Don't do this until next version, when we add
+ // explanatory text and an 'Autofill Required Attributes' button
+ // if (required)
+ // menuitem.setAttribute("class", "menuitem-highlight-1");
+
+ // Set flags to filter value input
+ if (forceOneChar) {
+ menuitem.setAttribute("forceOneChar", "true");
+ }
+ if (limitFirstChar) {
+ menuitem.setAttribute("limitFirstChar", "true");
+ }
+ if (forceInteger) {
+ menuitem.setAttribute("forceInteger", "true");
+ }
+ if (forceSignedInteger) {
+ menuitem.setAttribute("forceSignedInteger", "true");
+ }
+ if (forceIntOrPercent) {
+ menuitem.setAttribute("forceIntOrPercent", "true");
+ }
+ }
+ }
+ }
+ }
+}
+
+// build attribute list in tree form from element attributes
+function BuildHTMLAttributeTable() {
+ var nodeMap = gElement.attributes;
+ var i;
+ if (nodeMap.length > 0) {
+ var added = false;
+ for (i = 0; i < nodeMap.length; i++) {
+ let name = nodeMap[i].name.trim().toLowerCase();
+ if (
+ CheckAttributeNameSimilarity(nodeMap[i].nodeName, HTMLAttrs) ||
+ name.startsWith("on") ||
+ name == "style"
+ ) {
+ continue; // repeated or non-HTML attribute, ignore this one and go to next
+ }
+ if (
+ !name.startsWith("_moz") &&
+ AddTreeItem(name, nodeMap[i].value, "HTMLAList", HTMLAttrs)
+ ) {
+ added = true;
+ }
+ }
+
+ if (added) {
+ SelectHTMLTree(0);
+ }
+ }
+}
+
+function ClearHTMLInputWidgets() {
+ gDialog.AddHTMLAttributeTree.view.selection.clearSelection();
+ gDialog.AddHTMLAttributeNameInput.value = "";
+ gDialog.AddHTMLAttributeValueInput.value = "";
+ SetTextboxFocus(gDialog.AddHTMLAttributeNameInput);
+}
+
+function onSelectHTMLTreeItem() {
+ if (!gDoOnSelectTree) {
+ return;
+ }
+
+ var tree = gDialog.AddHTMLAttributeTree;
+ if (tree && tree.view.selection.count) {
+ var inputName = TrimString(
+ gDialog.AddHTMLAttributeNameInput.value
+ ).toLowerCase();
+ var selectedItem = getSelectedItem(tree);
+ var selectedName = selectedItem.firstChild.firstChild.getAttribute("label");
+
+ if (inputName == selectedName) {
+ // Already editing selected name - just update the value input
+ gDialog.AddHTMLAttributeValueInput.value = GetTreeItemValueStr(
+ selectedItem
+ );
+ } else {
+ gDialog.AddHTMLAttributeNameInput.value = selectedName;
+
+ // Change value input based on new selected name
+ onInputHTMLAttributeName();
+ }
+ }
+}
+
+function onInputHTMLAttributeName() {
+ let attName = gDialog.AddHTMLAttributeNameInput.value.toLowerCase().trim();
+
+ // Clear value widget, but prevent triggering update in tree
+ gUpdateTreeValue = false;
+ gDialog.AddHTMLAttributeValueInput.value = "";
+ gUpdateTreeValue = true;
+
+ if (attName) {
+ // Get value list for current attribute name
+ var valueListName;
+
+ // Most elements have the "dir" attribute,
+ // so we have just one array for the allowed values instead
+ // requiring duplicate entries for each element in EdAEAttributes.js
+ if (attName == "dir") {
+ valueListName = "all_dir";
+ } else {
+ valueListName = gElement.localName + "_" + attName;
+ }
+
+ // Strip off leading "_" we sometimes use (when element name is reserved word)
+ if (valueListName.startsWith("_")) {
+ valueListName = valueListName.slice(1);
+ }
+
+ var newValue = "";
+ var listLen = 0;
+
+ // Index to which widget we were using to edit the value
+ var deckIndex = gDialog.AddHTMLAttributeValueDeck.getAttribute(
+ "selectedIndex"
+ );
+
+ if (valueListName in gHTMLAttr) {
+ var valueList = gHTMLAttr[valueListName];
+
+ listLen = valueList.length;
+ if (listLen == 1) {
+ newValue = valueList[0];
+ }
+
+ // Note: For case where "value list" is actually just
+ // one (default) item, don't use menulist for that
+ if (listLen > 1) {
+ gDialog.AddHTMLAttributeValueMenulist.removeAllItems();
+
+ if (deckIndex != "1") {
+ // Switch to using editable menulist
+ gDialog.AddHTMLAttributeValueInput =
+ gDialog.AddHTMLAttributeValueMenulist;
+ gDialog.AddHTMLAttributeValueDeck.setAttribute("selectedIndex", "1");
+ }
+ // Rebuild the list
+ for (var i = 0; i < listLen; i++) {
+ if (valueList[i] == "-") {
+ // Signal for separator
+ var popup = gDialog.AddHTMLAttributeValueInput.menupopup;
+ if (popup) {
+ var sep = document.createXULElement("menuseparator");
+ if (sep) {
+ popup.appendChild(sep);
+ }
+ }
+ } else {
+ gDialog.AddHTMLAttributeValueMenulist.appendItem(
+ valueList[i],
+ valueList[i]
+ );
+ }
+ }
+ }
+ }
+
+ if (listLen <= 1 && deckIndex != "0") {
+ // No list: Use textbox for input instead
+ gDialog.AddHTMLAttributeValueInput = gDialog.AddHTMLAttributeValueTextbox;
+ gDialog.AddHTMLAttributeValueDeck.setAttribute("selectedIndex", "0");
+ }
+
+ // If attribute already exists in tree, use associated value,
+ // else use default found above
+ var existingValue = GetAndSelectExistingAttributeValue(
+ attName,
+ "HTMLAList"
+ );
+ if (existingValue) {
+ newValue = existingValue;
+ }
+
+ gDialog.AddHTMLAttributeValueInput.value = newValue;
+
+ if (!existingValue) {
+ onInputHTMLAttributeValue();
+ }
+ }
+}
+
+function onInputHTMLAttributeValue() {
+ if (!gUpdateTreeValue) {
+ return;
+ }
+
+ var name = TrimString(gDialog.AddHTMLAttributeNameInput.value);
+ if (!name) {
+ return;
+ }
+
+ // Trim spaces only from left since we must allow spaces within the string
+ // (we always reset the input field's value below)
+ var value = TrimStringLeft(gDialog.AddHTMLAttributeValueInput.value);
+ if (value) {
+ // Do value filtering based on type of attribute
+ // (Do not use "forceInteger()" to avoid multiple
+ // resetting of input's value and flickering)
+ var selectedItem = gDialog.AddHTMLAttributeNameInput.selectedItem;
+
+ if (selectedItem) {
+ if (
+ selectedItem.getAttribute("forceOneChar") == "true" &&
+ value.length > 1
+ ) {
+ value = value.slice(0, 1);
+ }
+
+ if (selectedItem.getAttribute("forceIntOrPercent") == "true") {
+ // Allow integer with optional "%" as last character
+ var percent = TrimStringRight(value).slice(-1);
+ value = value.replace(/\D+/g, "");
+ if (percent == "%") {
+ value += percent;
+ }
+ } else if (selectedItem.getAttribute("forceInteger") == "true") {
+ value = value.replace(/\D+/g, "");
+ } else if (selectedItem.getAttribute("forceSignedInteger") == "true") {
+ // Allow integer with optional "+" or "-" as first character
+ var sign = value[0];
+ value = value.replace(/\D+/g, "");
+ if (sign == "+" || sign == "-") {
+ value = sign + value;
+ }
+ }
+
+ // Special case attributes
+ if (selectedItem.getAttribute("limitFirstChar") == "true") {
+ // Limit first character to letter, and all others to
+ // letters, numbers, and a few others
+ value = value
+ .replace(/^[^a-zA-Z\u0080-\uFFFF]/, "")
+ .replace(/[^a-zA-Z0-9_\.\-\:\u0080-\uFFFF]+/g, "");
+ }
+
+ // Update once only if it changed
+ if (value != gDialog.AddHTMLAttributeValueInput.value) {
+ gDialog.AddHTMLAttributeValueInput.value = value;
+ }
+ }
+ }
+
+ // Update value in the tree list
+ // If not found, add new attribute
+ if (!UpdateExistingAttribute(name, value, "HTMLAList") && value) {
+ AddTreeItem(name, value, "HTMLAList", HTMLAttrs);
+ }
+}
+
+function editHTMLAttributeValue(targetCell) {
+ if (IsNotTreeHeader(targetCell)) {
+ gDialog.AddHTMLAttributeValueInput.select();
+ }
+}
+
+// update the object with added and removed attributes
+function UpdateHTMLAttributes() {
+ var HTMLAList = document.getElementById("HTMLAList");
+ var i;
+
+ // remove removed attributes
+ for (i = 0; i < HTMLRAttrs.length; i++) {
+ var name = HTMLRAttrs[i];
+
+ if (gElement.hasAttribute(name)) {
+ doRemoveAttribute(name);
+ }
+ }
+
+ // Set added or changed attributes
+ for (i = 0; i < HTMLAList.childNodes.length; i++) {
+ var item = HTMLAList.childNodes[i];
+ doSetAttribute(GetTreeItemAttributeStr(item), GetTreeItemValueStr(item));
+ }
+}
+
+function RemoveHTMLAttribute() {
+ // We only allow 1 selected item
+ if (gDialog.AddHTMLAttributeTree.view.selection.count) {
+ var item = getSelectedItem(gDialog.AddHTMLAttributeTree);
+ var attr = GetTreeItemAttributeStr(item);
+
+ // remove the item from the attribute array
+ HTMLRAttrs[HTMLRAttrs.length] = attr;
+ RemoveNameFromAttArray(attr, HTMLAttrs);
+
+ // Remove the item from the tree
+ item.remove();
+
+ // Clear inputs and selected item in tree
+ ClearHTMLInputWidgets();
+ }
+}
+
+function SelectHTMLTree(index) {
+ gDoOnSelectTree = false;
+ try {
+ gDialog.AddHTMLAttributeTree.selectedIndex = index;
+ } catch (e) {}
+ gDoOnSelectTree = true;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdAEJSEAttributes.js b/comm/suite/editor/components/dialogs/content/EdAEJSEAttributes.js
new file mode 100644
index 0000000000..c15c938b3e
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdAEJSEAttributes.js
@@ -0,0 +1,200 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdAdvancedEdit.js */
+/* import-globals-from EdDialogCommon.js */
+
+function BuildJSEAttributeNameList() {
+ gDialog.AddJSEAttributeNameList.removeAllItems();
+
+ // Get events specific to current element
+ var elementName = gElement.localName;
+ if (elementName in gJSAttr) {
+ var attNames = gJSAttr[elementName];
+ var i;
+ var popup;
+ var sep;
+
+ if (attNames && attNames.length) {
+ // Since we don't allow user-editable JS events yet (but we will soon)
+ // simply remove the JS tab to not allow adding JS events
+ if (attNames[0] == "noJSEvents") {
+ var tab = document.getElementById("tabJSE");
+ if (tab) {
+ tab.remove();
+ }
+
+ return;
+ }
+
+ for (i = 0; i < attNames.length; i++) {
+ gDialog.AddJSEAttributeNameList.appendItem(attNames[i], attNames[i]);
+ }
+
+ popup = gDialog.AddJSEAttributeNameList.firstChild;
+ if (popup) {
+ sep = document.createXULElement("menuseparator");
+ if (sep) {
+ popup.appendChild(sep);
+ }
+ }
+ }
+ }
+
+ // Always add core JS events unless we aborted above
+ for (i = 0; i < gCoreJSEvents.length; i++) {
+ if (gCoreJSEvents[i] == "-") {
+ if (!popup) {
+ popup = gDialog.AddJSEAttributeNameList.firstChild;
+ }
+
+ sep = document.createXULElement("menuseparator");
+
+ if (popup && sep) {
+ popup.appendChild(sep);
+ }
+ } else {
+ gDialog.AddJSEAttributeNameList.appendItem(
+ gCoreJSEvents[i],
+ gCoreJSEvents[i]
+ );
+ }
+ }
+
+ gDialog.AddJSEAttributeNameList.selectedIndex = 0;
+
+ // Use current name and value of first tree item if it exists
+ onSelectJSETreeItem();
+}
+
+// build attribute list in tree form from element attributes
+function BuildJSEAttributeTable() {
+ var nodeMap = gElement.attributes;
+ if (nodeMap.length > 0) {
+ var added = false;
+ for (var i = 0; i < nodeMap.length; i++) {
+ let name = nodeMap[i].nodeName.toLowerCase();
+ if (CheckAttributeNameSimilarity(nodeMap[i].nodeName, JSEAttrs)) {
+ // Repeated or non-JS handler, ignore this one and go to next.
+ continue;
+ }
+ if (!name.startsWith("on")) {
+ // Attribute isn't an event handler.
+ continue;
+ }
+ var value = gElement.getAttribute(nodeMap[i].nodeName);
+ if (AddTreeItem(name, value, "JSEAList", JSEAttrs)) {
+ // add item to tree
+ added = true;
+ }
+ }
+
+ // Select first item
+ if (added) {
+ gDialog.AddJSEAttributeTree.selectedIndex = 0;
+ }
+ }
+}
+
+function onSelectJSEAttribute() {
+ if (!gDoOnSelectTree) {
+ return;
+ }
+
+ gDialog.AddJSEAttributeValueInput.value = GetAndSelectExistingAttributeValue(
+ gDialog.AddJSEAttributeNameList.label,
+ "JSEAList"
+ );
+}
+
+function onSelectJSETreeItem() {
+ var tree = gDialog.AddJSEAttributeTree;
+ if (tree && tree.view.selection.count) {
+ // Select attribute name in list
+ gDialog.AddJSEAttributeNameList.value = GetTreeItemAttributeStr(
+ getSelectedItem(tree)
+ );
+
+ // Set value input to that in tree (no need to update this in the tree)
+ gUpdateTreeValue = false;
+ gDialog.AddJSEAttributeValueInput.value = GetTreeItemValueStr(
+ getSelectedItem(tree)
+ );
+ gUpdateTreeValue = true;
+ }
+}
+
+function onInputJSEAttributeValue() {
+ if (gUpdateTreeValue) {
+ var name = TrimString(gDialog.AddJSEAttributeNameList.label);
+ var value = TrimString(gDialog.AddJSEAttributeValueInput.value);
+
+ // Update value in the tree list
+ // Since we have a non-editable menulist,
+ // we MUST automatically add the event attribute if it doesn't exist
+ if (!UpdateExistingAttribute(name, value, "JSEAList") && value) {
+ AddTreeItem(name, value, "JSEAList", JSEAttrs);
+ }
+ }
+}
+
+function editJSEAttributeValue(targetCell) {
+ if (IsNotTreeHeader(targetCell)) {
+ gDialog.AddJSEAttributeValueInput.inputField.select();
+ }
+}
+
+function UpdateJSEAttributes() {
+ var JSEAList = document.getElementById("JSEAList");
+ var i;
+
+ // remove removed attributes
+ for (i = 0; i < JSERAttrs.length; i++) {
+ var name = JSERAttrs[i];
+
+ if (gElement.hasAttribute(name)) {
+ doRemoveAttribute(name);
+ }
+ }
+
+ // Add events
+ for (i = 0; i < JSEAList.childNodes.length; i++) {
+ var item = JSEAList.childNodes[i];
+
+ // set the event handler
+ doSetAttribute(GetTreeItemAttributeStr(item), GetTreeItemValueStr(item));
+ }
+}
+
+function RemoveJSEAttribute() {
+ // This differs from HTML and CSS panels:
+ // We reselect after removing, because there is not
+ // editable attribute name input, so we can't clear that
+ // like we do in other panels
+ var newIndex = gDialog.AddJSEAttributeTree.selectedIndex;
+
+ // We only allow 1 selected item
+ if (gDialog.AddJSEAttributeTree.view.selection.count) {
+ var item = getSelectedItem(gDialog.AddJSEAttributeTree);
+
+ // Name is the text of the treecell
+ var attr = GetTreeItemAttributeStr(item);
+
+ // remove the item from the attribute array
+ if (newIndex >= JSEAttrs.length - 1) {
+ newIndex--;
+ }
+
+ // remove the item from the attribute array
+ JSERAttrs[JSERAttrs.length] = attr;
+ RemoveNameFromAttArray(attr, JSEAttrs);
+
+ // Remove the item from the tree
+ item.remove();
+
+ // Reselect an item
+ gDialog.AddJSEAttributeTree.selectedIndex = newIndex;
+ }
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.js b/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.js
new file mode 100644
index 0000000000..60e9009905
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.js
@@ -0,0 +1,342 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdAEAttributes.js */
+/* import-globals-from EdAECSSAttributes.js */
+/* import-globals-from EdAEHTMLAttributes.js */
+/* import-globals-from EdAEJSEAttributes.js */
+/* import-globals-from EdDialogCommon.js */
+
+/** ************ GLOBALS **************/
+var gElement = null; // handle to actual element edited
+
+var HTMLAttrs = []; // html attributes
+var CSSAttrs = []; // css attributes
+var JSEAttrs = []; // js events
+
+var HTMLRAttrs = []; // removed html attributes
+var JSERAttrs = []; // removed js events
+
+/* Set false to allow changing selection in tree
+ without doing "onselect" handler actions
+*/
+var gDoOnSelectTree = true;
+var gUpdateTreeValue = true;
+
+/** ************ INITIALISATION && SETUP **************/
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+/**
+ * function : void Startup();
+ * parameters : none
+ * returns : none
+ * desc. : startup and initialisation, prepares dialog.
+ **/
+function Startup() {
+ var editor = GetCurrentEditor();
+
+ // Element to edit is passed in
+ if (!editor || !window.arguments[1]) {
+ dump("Advanced Edit: No editor or element to edit not supplied\n");
+ window.close();
+ return;
+ }
+ // This is the return value for the parent,
+ // who only needs to know if OK was clicked
+ window.opener.AdvancedEditOK = false;
+
+ // The actual element edited (not a copy!)
+ gElement = window.arguments[1];
+
+ // place the tag name in the header
+ var tagLabel = document.getElementById("tagLabel");
+ tagLabel.setAttribute("value", "<" + gElement.localName + ">");
+
+ // Create dialog object to store controls for easy access
+ gDialog.AddHTMLAttributeNameInput = document.getElementById(
+ "AddHTMLAttributeNameInput"
+ );
+
+ // We use a <deck> to switch between editable menulist and textbox
+ gDialog.AddHTMLAttributeValueDeck = document.getElementById(
+ "AddHTMLAttributeValueDeck"
+ );
+ gDialog.AddHTMLAttributeValueMenulist = document.getElementById(
+ "AddHTMLAttributeValueMenulist"
+ );
+ gDialog.AddHTMLAttributeValueTextbox = document.getElementById(
+ "AddHTMLAttributeValueTextbox"
+ );
+ gDialog.AddHTMLAttributeValueInput = gDialog.AddHTMLAttributeValueTextbox;
+
+ gDialog.AddHTMLAttributeTree = document.getElementById("HTMLATree");
+ gDialog.AddCSSAttributeNameInput = document.getElementById(
+ "AddCSSAttributeNameInput"
+ );
+ gDialog.AddCSSAttributeValueInput = document.getElementById(
+ "AddCSSAttributeValueInput"
+ );
+ gDialog.AddCSSAttributeTree = document.getElementById("CSSATree");
+ gDialog.AddJSEAttributeNameList = document.getElementById(
+ "AddJSEAttributeNameList"
+ );
+ gDialog.AddJSEAttributeValueInput = document.getElementById(
+ "AddJSEAttributeValueInput"
+ );
+ gDialog.AddJSEAttributeTree = document.getElementById("JSEATree");
+ gDialog.okButton = document.documentElement.getButton("accept");
+
+ // build the attribute trees
+ BuildHTMLAttributeTable();
+ BuildCSSAttributeTable();
+ BuildJSEAttributeTable();
+
+ // Build attribute name arrays for menulists
+ BuildJSEAttributeNameList();
+ BuildHTMLAttributeNameList();
+ // No menulists for CSS panel (yet)
+
+ // Set focus to Name editable menulist in HTML panel
+ SetTextboxFocus(gDialog.AddHTMLAttributeNameInput);
+
+ // size the dialog properly
+ window.sizeToContent();
+
+ SetWindowLocation();
+}
+
+/**
+ * function : bool onAccept ( void );
+ * parameters : none
+ * returns : boolean true to close the window
+ * desc. : event handler for ok button
+ **/
+function onAccept() {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+ try {
+ // Update our gElement attributes
+ UpdateHTMLAttributes();
+ UpdateCSSAttributes();
+ UpdateJSEAttributes();
+ } catch (ex) {
+ dump(ex);
+ }
+ editor.endTransaction();
+
+ window.opener.AdvancedEditOK = true;
+ SaveWindowLocation();
+}
+
+// Helpers for removing and setting attributes
+// Use editor transactions if modifying the element already in the document
+// (Temporary element from a property dialog won't have a parent node)
+function doRemoveAttribute(attrib) {
+ try {
+ var editor = GetCurrentEditor();
+ if (gElement.parentNode) {
+ editor.removeAttribute(gElement, attrib);
+ } else {
+ gElement.removeAttribute(attrib);
+ }
+ } catch (ex) {}
+}
+
+function doSetAttribute(attrib, value) {
+ try {
+ var editor = GetCurrentEditor();
+ if (gElement.parentNode) {
+ editor.setAttribute(gElement, attrib, value);
+ } else {
+ gElement.setAttribute(attrib, value);
+ }
+ } catch (ex) {}
+}
+
+/**
+ * function : bool CheckAttributeNameSimilarity ( string attName, array attArray );
+ * parameters : attribute to look for, array of current attributes
+ * returns : true if attribute already exists, false if it does not
+ * desc. : checks to see if any other attributes by the same name as the arg supplied
+ * already exist.
+ **/
+function CheckAttributeNameSimilarity(attName, attArray) {
+ for (var i = 0; i < attArray.length; i++) {
+ if (attName.toLowerCase() == attArray[i].toLowerCase()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * function : bool UpdateExistingAttribute ( string attName, string attValue, string treeChildrenId );
+ * parameters : attribute to look for, new value, ID of <treeChildren> node in XUL tree
+ * returns : true if attribute already exists in tree, false if it does not
+ * desc. : checks to see if any other attributes by the same name as the arg supplied
+ * already exist while setting the associated value if different from current value
+ **/
+function UpdateExistingAttribute(attName, attValue, treeChildrenId) {
+ var treeChildren = document.getElementById(treeChildrenId);
+ if (!treeChildren) {
+ return false;
+ }
+
+ var name;
+ var i;
+ attName = TrimString(attName).toLowerCase();
+ attValue = TrimString(attValue);
+
+ for (i = 0; i < treeChildren.childNodes.length; i++) {
+ var item = treeChildren.childNodes[i];
+ name = GetTreeItemAttributeStr(item);
+ if (name.toLowerCase() == attName) {
+ // Set the text in the "value' column treecell
+ SetTreeItemValueStr(item, attValue);
+
+ // Select item just changed,
+ // but don't trigger the tree's onSelect handler
+ gDoOnSelectTree = false;
+ try {
+ selectTreeItem(treeChildren, item);
+ } catch (e) {}
+ gDoOnSelectTree = true;
+
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * function : string GetAndSelectExistingAttributeValue ( string attName, string treeChildrenId );
+ * parameters : attribute to look for, ID of <treeChildren> node in XUL tree
+ * returns : value in from the tree or empty string if name not found
+ **/
+function GetAndSelectExistingAttributeValue(attName, treeChildrenId) {
+ if (!attName) {
+ return "";
+ }
+
+ var treeChildren = document.getElementById(treeChildrenId);
+ var name;
+ var i;
+
+ for (i = 0; i < treeChildren.childNodes.length; i++) {
+ var item = treeChildren.childNodes[i];
+ name = GetTreeItemAttributeStr(item);
+ if (name.toLowerCase() == attName.toLowerCase()) {
+ // Select item in the tree
+ // but don't trigger the tree's onSelect handler
+ gDoOnSelectTree = false;
+ try {
+ selectTreeItem(treeChildren, item);
+ } catch (e) {}
+ gDoOnSelectTree = true;
+
+ // Get the text in the "value' column treecell
+ return GetTreeItemValueStr(item);
+ }
+ }
+
+ // Attribute doesn't exist in tree, so remove selection
+ gDoOnSelectTree = false;
+ try {
+ treeChildren.parentNode.view.selection.clearSelection();
+ } catch (e) {}
+ gDoOnSelectTree = true;
+
+ return "";
+}
+
+/* Tree structure:
+ <treeItem>
+ <treeRow>
+ <treeCell> // Name Cell
+ <treeCell // Value Cell
+*/
+function GetTreeItemAttributeStr(treeItem) {
+ if (treeItem) {
+ return TrimString(treeItem.firstChild.firstChild.getAttribute("label"));
+ }
+
+ return "";
+}
+
+function GetTreeItemValueStr(treeItem) {
+ if (treeItem) {
+ return TrimString(treeItem.firstChild.lastChild.getAttribute("label"));
+ }
+
+ return "";
+}
+
+function SetTreeItemValueStr(treeItem, value) {
+ if (treeItem && GetTreeItemValueStr(treeItem) != value) {
+ treeItem.firstChild.lastChild.setAttribute("label", value);
+ }
+}
+
+function IsNotTreeHeader(treeCell) {
+ if (treeCell) {
+ return treeCell.parentNode.parentNode.nodeName != "treehead";
+ }
+
+ return false;
+}
+
+function RemoveNameFromAttArray(attName, attArray) {
+ for (var i = 0; i < attArray.length; i++) {
+ if (attName.toLowerCase() == attArray[i].toLowerCase()) {
+ // Remove 1 array item
+ attArray.splice(i, 1);
+ break;
+ }
+ }
+}
+
+// adds a generalised treeitem.
+function AddTreeItem(name, value, treeChildrenId, attArray) {
+ attArray[attArray.length] = name;
+ var treeChildren = document.getElementById(treeChildrenId);
+ var treeitem = document.createXULElement("treeitem");
+ var treerow = document.createXULElement("treerow");
+
+ var attrCell = document.createXULElement("treecell");
+ attrCell.setAttribute("class", "propertylist");
+ attrCell.setAttribute("label", name);
+
+ var valueCell = document.createXULElement("treecell");
+ valueCell.setAttribute("class", "propertylist");
+ valueCell.setAttribute("label", value);
+
+ treerow.appendChild(attrCell);
+ treerow.appendChild(valueCell);
+ treeitem.appendChild(treerow);
+ treeChildren.appendChild(treeitem);
+
+ // Select item just added, but suppress calling the onSelect handler.
+ gDoOnSelectTree = false;
+ try {
+ selectTreeItem(treeChildren, treeitem);
+ } catch (e) {}
+ gDoOnSelectTree = true;
+
+ return treeitem;
+}
+
+function selectTreeItem(treeChildren, item) {
+ var index = treeChildren.parentNode.view.getIndexOfItem(item);
+ treeChildren.parentNode.view.selection.select(index);
+}
+
+function getSelectedItem(tree) {
+ if (tree.view.selection.count == 1) {
+ return tree.view.getItemAtIndex(tree.currentIndex);
+ }
+ return null;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.xhtml b/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.xhtml
new file mode 100644
index 0000000000..94942709a1
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdAdvancedEdit.xhtml
@@ -0,0 +1,182 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!-- first checkin of the year 2000! -->
+<!-- Ben Goodger, 12:50AM, 01/00/00 NZST -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/menulist.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EdAdvancedEdit.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ id="advancedEditDlg"
+ style="width: 40em;"
+ title="&WindowTitle.label;"
+ onload="Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <!-- element page functions -->
+ <script src="chrome://editor/content/EdAEHTMLAttributes.js"/>
+ <script src="chrome://editor/content/EdAECSSAttributes.js"/>
+ <script src="chrome://editor/content/EdAEJSEAttributes.js"/>
+ <script src="chrome://editor/content/EdAEAttributes.js"/>
+
+ <!-- global dialog functions -->
+ <script src="chrome://editor/content/EdAdvancedEdit.js"/>
+
+ <script src="chrome://messenger/content/customElements.js"/>
+
+ <hbox>
+ <label value="&currentattributesfor.label;"/>
+ <label class="header" id="tagLabel"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <tabbox flex="1">
+ <tabs>
+ <tab label="&tabHTML.label;"/>
+ <tab label="&tabCSS.label;"/>
+ <tab label="&tabJSE.label;" id="tabJSE"/>
+ </tabs>
+ <tabpanels flex="1">
+ <!-- ============================================================== -->
+ <!-- HTML Attributes -->
+ <!-- ============================================================== -->
+ <vbox>
+ <tree id="HTMLATree" class="AttributesTree" flex="1"
+ hidecolumnpicker="true" seltype="single"
+ onselect="onSelectHTMLTreeItem();"
+ onclick="onSelectHTMLTreeItem();"
+ ondblclick="editHTMLAttributeValue(event.target);">
+ <treecols>
+ <treecol id="HTMLAttrCol" flex="35" label="&tree.attributeHeader.label;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="HTMLValCol" flex="65" label="&tree.valueHeader.label;"/>
+ </treecols>
+ <treechildren id="HTMLAList" flex="1"/>
+ </tree>
+ <hbox align="center">
+ <label value="&editAttribute.label;"/>
+ <spacer flex="1"/>
+ <button label="&removeAttribute.label;" oncommand="RemoveHTMLAttribute();"/>
+ </hbox>
+ <grid>
+ <columns>
+ <column flex="1"/><column flex="1"/>
+ </columns>
+ <rows>
+ <row equalsize="always">
+ <label control="AddHTMLAttributeNameInput" value="&AttName.label;"/>
+ <label control="AddHTMLAttributeValueInput" value="&AttValue.label;"/>
+ </row>
+ <row align="top" equalsize="always">
+ <!-- Lists are built at runtime -->
+ <menulist is="menulist-editable" id="AddHTMLAttributeNameInput"
+ editable="true" flex="1"
+ oninput="onInputHTMLAttributeName();"
+ oncommand="onInputHTMLAttributeName();"/>
+ <deck id="AddHTMLAttributeValueDeck" selectedIndex="0">
+ <hbox align="top">
+ <textbox id="AddHTMLAttributeValueTextbox" flex="1"
+ oninput="onInputHTMLAttributeValue();"/>
+ </hbox>
+ <hbox align="top">
+ <menulist is="menulist-editable" id="AddHTMLAttributeValueMenulist"
+ editable="true" flex="1"
+ oninput="onInputHTMLAttributeValue();"
+ oncommand="onInputHTMLAttributeValue();"/>
+ </hbox>
+ </deck>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+ <!-- ============================================================== -->
+ <!-- CSS Attributes -->
+ <!-- ============================================================== -->
+ <vbox>
+ <tree id="CSSATree" class="AttributesTree" flex="1"
+ hidecolumnpicker="true" seltype="single"
+ onselect="onSelectCSSTreeItem();"
+ onclick="onSelectCSSTreeItem();"
+ ondblclick="editCSSAttributeValue(event.target);">
+ <treecols>
+ <treecol id="CSSPropCol" flex="35" label="&tree.propertyHeader.label;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="CSSValCol" flex="65" label="&tree.valueHeader.label;"/>
+ </treecols>
+ <treechildren id="CSSAList" flex="1"/>
+ </tree>
+ <hbox align="center">
+ <label value="&editAttribute.label;"/>
+ <spacer flex="1"/>
+ <button label="&removeAttribute.label;" oncommand="RemoveCSSAttribute();"/>
+ </hbox>
+ <grid>
+ <columns>
+ <column flex="1"/><column flex="1"/>
+ </columns>
+ <rows>
+ <row equalsize="always">
+ <label value="&PropertyName.label;"/>
+ <label value="&AttValue.label;"/>
+ </row>
+ <row align="top" equalsize="always">
+ <textbox id="AddCSSAttributeNameInput" flex="1"
+ oninput="onInputCSSAttributeName();"/>
+ <textbox id="AddCSSAttributeValueInput" flex="1"
+ oninput="onChangeCSSAttribute();"/>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+ <!-- ============================================================== -->
+ <!-- JavaScript Event Handlers -->
+ <!-- ============================================================== -->
+ <vbox>
+ <tree id="JSEATree" class="AttributesTree" flex="1"
+ hidecolumnpicker="true" seltype="single"
+ onselect="onSelectJSETreeItem();"
+ onclick="onSelectJSETreeItem();"
+ ondblclick="editJSEAttributeValue(event.target);">
+ <treecols>
+ <treecol id="AttrCol" flex="35" label="&tree.attributeHeader.label;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="HeaderCol" flex="65" label="&tree.valueHeader.label;"/>
+ </treecols>
+ <treechildren id="JSEAList" flex="1"/>
+ </tree>
+ <hbox align="center">
+ <label value="&editAttribute.label;"/>
+ <spacer flex="1"/>
+ <button label="&removeAttribute.label;" oncommand="RemoveJSEAttribute()"/>
+ </hbox>
+ <grid>
+ <columns>
+ <column flex="1"/><column flex="1"/>
+ </columns>
+ <rows>
+ <row equalsize="always">
+ <label value="&AttName.label;"/>
+ <label value="&AttValue.label;"/>
+ </row>
+ <row align="top" equalsize="always">
+ <!-- List is built at runtime -->
+ <menulist id="AddJSEAttributeNameList" flex="1"
+ oncommand="onSelectJSEAttribute();"/>
+ <textbox id="AddJSEAttributeValueInput" flex="1"
+ oninput="onInputJSEAttributeValue();"/>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+ </tabpanels>
+ </tabbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdButtonProps.js b/comm/suite/editor/components/dialogs/content/EdButtonProps.js
new file mode 100644
index 0000000000..1cd0ee7365
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdButtonProps.js
@@ -0,0 +1,146 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var insertNew;
+var buttonElement;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ gDialog = {
+ buttonType: document.getElementById("ButtonType"),
+ buttonName: document.getElementById("ButtonName"),
+ buttonValue: document.getElementById("ButtonValue"),
+ buttonDisabled: document.getElementById("ButtonDisabled"),
+ buttonTabIndex: document.getElementById("ButtonTabIndex"),
+ buttonAccessKey: document.getElementById("ButtonAccessKey"),
+ MoreSection: document.getElementById("MoreSection"),
+ MoreFewerButton: document.getElementById("MoreFewerButton"),
+ RemoveButton: document.getElementById("RemoveButton"),
+ };
+
+ // Get a single selected button element
+ const kTagName = "button";
+ try {
+ buttonElement = editor.getSelectedElement(kTagName);
+ } catch (e) {}
+
+ if (buttonElement) {
+ // We found an element and don't need to insert one
+ insertNew = false;
+ } else {
+ insertNew = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+ try {
+ buttonElement = editor.createElementWithDefaults(kTagName);
+ } catch (e) {}
+
+ if (!buttonElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+ // Hide button removing existing button
+ gDialog.RemoveButton.hidden = true;
+ }
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = buttonElement.cloneNode(false);
+
+ InitDialog();
+
+ InitMoreFewer();
+
+ gDialog.buttonType.focus();
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ var type = globalElement.getAttribute("type");
+ var index = 0;
+ switch (type) {
+ case "button":
+ index = 2;
+ break;
+ case "reset":
+ index = 1;
+ break;
+ }
+ gDialog.buttonType.selectedIndex = index;
+ gDialog.buttonName.value = globalElement.getAttribute("name");
+ gDialog.buttonValue.value = globalElement.getAttribute("value");
+ gDialog.buttonDisabled.setAttribute(
+ "checked",
+ globalElement.hasAttribute("disabled")
+ );
+ gDialog.buttonTabIndex.value = globalElement.getAttribute("tabindex");
+ gDialog.buttonAccessKey.value = globalElement.getAttribute("accesskey");
+}
+
+function RemoveButton() {
+ RemoveContainer(buttonElement);
+ SaveWindowLocation();
+ window.close();
+}
+
+function ValidateData() {
+ var attributes = {
+ type: ["", "reset", "button"][gDialog.buttonType.selectedIndex],
+ name: gDialog.buttonName.value,
+ value: gDialog.buttonValue.value,
+ tabindex: gDialog.buttonTabIndex.value,
+ accesskey: gDialog.buttonAccessKey.value,
+ };
+ for (var a in attributes) {
+ if (attributes[a]) {
+ globalElement.setAttribute(a, attributes[a]);
+ } else {
+ globalElement.removeAttribute(a);
+ }
+ }
+ if (gDialog.buttonDisabled.checked) {
+ globalElement.setAttribute("disabled", "");
+ } else {
+ globalElement.removeAttribute("disabled");
+ }
+ return true;
+}
+
+function onAccept() {
+ // All values are valid - copy to actual element in doc or
+ // element created to insert
+ ValidateData();
+
+ var editor = GetCurrentEditor();
+
+ editor.cloneAttributes(buttonElement, globalElement);
+
+ if (insertNew) {
+ if (!InsertElementAroundSelection(buttonElement)) {
+ /* eslint-disable-next-line no-unsanitized/property */
+ buttonElement.innerHTML = editor.outputToString(
+ "text/html",
+ kOutputSelectionOnly
+ );
+ editor.insertElementAtSelection(buttonElement, true);
+ }
+ }
+
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdButtonProps.xhtml b/comm/suite/editor/components/dialogs/content/EdButtonProps.xhtml
new file mode 100644
index 0000000000..70e4774f13
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdButtonProps.xhtml
@@ -0,0 +1,92 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edButtonProperties SYSTEM "chrome://editor/locale/EditorButtonProperties.dtd">
+%edButtonProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ buttons="accept,cancel">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdButtonProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&Settings.label;</label>
+ </hbox>
+ <grid><columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label control="ButtonType" value="&ButtonType.label;" accesskey="&ButtonType.accesskey;"/>
+ <menulist id="ButtonType">
+ <menupopup>
+ <menuitem label="&submit.value;"/>
+ <menuitem label="&reset.value;"/>
+ <menuitem label="&button.value;"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row align="center">
+ <label control="ButtonName" value="&ButtonName.label;" accesskey="&ButtonName.accesskey;"/>
+ <textbox id="ButtonName"/>
+ </row>
+ <row align="center">
+ <label control="ButtonValue" value="&ButtonValue.label;" accesskey="&ButtonValue.accesskey;"/>
+ <textbox id="ButtonValue"/>
+ </row>
+ </rows>
+ </grid>
+ <hbox>
+ <button id="MoreFewerButton" oncommand="onMoreFewer();" persist="more"/>
+ </hbox>
+ <grid id="MoreSection"><columns><column/><column/></columns>
+ <rows>
+ <row>
+ <spacer/>
+ <checkbox id="ButtonDisabled" label="&ButtonDisabled.label;" accesskey="&ButtonDisabled.accesskey;"/>
+ </row>
+ <row align="center">
+ <label control="ButtonTabIndex" value="&tabIndex.label;" accesskey="&tabIndex.accesskey;"/>
+ <hbox>
+ <textbox id="ButtonTabIndex" class="narrow" oninput="forceInteger(this.id);"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="ButtonAccessKey" value="&AccessKey.label;" accesskey="&AccessKey.accesskey;"/>
+ <hbox>
+ <textbox id="ButtonAccessKey" class="narrow"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <!-- from EdDialogOverlay -->
+ <hbox flex="1" style="margin-top: 0.2em">
+ <button id="RemoveButton" label="&RemoveButton.label;" accesskey="&RemoveButton.accesskey;" oncommand="RemoveButton();"/>
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator class="groove"/>
+
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdColorPicker.js b/comm/suite/editor/components/dialogs/content/EdColorPicker.js
new file mode 100644
index 0000000000..95ce279368
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdColorPicker.js
@@ -0,0 +1,297 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// Cancel() is in EdDialogCommon.js
+
+var insertNew = true;
+var tagname = "TAG NAME";
+var gColor = "";
+var LastPickedColor = "";
+var ColorType = "Text";
+var TextType = false;
+var HighlightType = false;
+var TableOrCell = false;
+var LastPickedIsDefault = true;
+var NoDefault = false;
+var gColorObj;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancelColor);
+
+function Startup() {
+ if (!window.arguments[1]) {
+ dump("EdColorPicker: Missing color object param\n");
+ return;
+ }
+
+ // window.arguments[1] is object to get initial values and return color data
+ gColorObj = window.arguments[1];
+ gColorObj.Cancel = false;
+
+ gDialog.ColorPicker = document.getElementById("ColorPicker");
+ gDialog.ColorInput = document.getElementById("ColorInput");
+ gDialog.LastPickedButton = document.getElementById("LastPickedButton");
+ gDialog.LastPickedColor = document.getElementById("LastPickedColor");
+ gDialog.CellOrTableGroup = document.getElementById("CellOrTableGroup");
+ gDialog.TableRadio = document.getElementById("TableRadio");
+ gDialog.CellRadio = document.getElementById("CellRadio");
+ gDialog.ColorSwatch = document.getElementById("ColorPickerSwatch");
+ gDialog.Ok = document.documentElement.getButton("accept");
+
+ // The type of color we are setting:
+ // text: Text, Link, ActiveLink, VisitedLink,
+ // or background: Page, Table, or Cell
+ if (gColorObj.Type) {
+ ColorType = gColorObj.Type;
+ // Get string for dialog title from passed-in type
+ // (note constraint on editor.properties string name)
+ let IsCSSPrefChecked = Services.prefs.getBoolPref("editor.use_css");
+
+ if (GetCurrentEditor()) {
+ if (ColorType == "Page" && IsCSSPrefChecked && IsHTMLEditor()) {
+ document.title = GetString("BlockColor");
+ } else {
+ document.title = GetString(ColorType + "Color");
+ }
+ }
+ }
+
+ gDialog.ColorInput.value = "";
+ var tmpColor;
+ var haveTableRadio = false;
+
+ switch (ColorType) {
+ case "Page":
+ tmpColor = gColorObj.PageColor;
+ if (tmpColor && tmpColor.toLowerCase() != "window") {
+ gColor = tmpColor;
+ }
+ break;
+ case "Table":
+ if (gColorObj.TableColor) {
+ gColor = gColorObj.TableColor;
+ }
+ break;
+ case "Cell":
+ if (gColorObj.CellColor) {
+ gColor = gColorObj.CellColor;
+ }
+ break;
+ case "TableOrCell":
+ TableOrCell = true;
+ document.getElementById("TableOrCellGroup").collapsed = false;
+ haveTableRadio = true;
+ if (gColorObj.SelectedType == "Cell") {
+ gColor = gColorObj.CellColor;
+ gDialog.CellOrTableGroup.selectedItem = gDialog.CellRadio;
+ gDialog.CellRadio.focus();
+ } else {
+ gColor = gColorObj.TableColor;
+ gDialog.CellOrTableGroup.selectedItem = gDialog.TableRadio;
+ gDialog.TableRadio.focus();
+ }
+ break;
+ case "Highlight":
+ HighlightType = true;
+ if (gColorObj.HighlightColor) {
+ gColor = gColorObj.HighlightColor;
+ }
+ break;
+ default:
+ // Any other type will change some kind of text,
+ TextType = true;
+ tmpColor = gColorObj.TextColor;
+ if (tmpColor && tmpColor.toLowerCase() != "windowtext") {
+ gColor = gColorObj.TextColor;
+ }
+ break;
+ }
+
+ // Set initial color in input field and in the colorpicker
+ SetCurrentColor(gColor);
+ gDialog.ColorPicker.value = gColor;
+
+ // Use last-picked colors passed in, or those persistent on dialog
+ if (TextType) {
+ if (!("LastTextColor" in gColorObj) || !gColorObj.LastTextColor) {
+ gColorObj.LastTextColor = gDialog.LastPickedColor.getAttribute(
+ "LastTextColor"
+ );
+ }
+ LastPickedColor = gColorObj.LastTextColor;
+ } else if (HighlightType) {
+ if (!("LastHighlightColor" in gColorObj) || !gColorObj.LastHighlightColor) {
+ gColorObj.LastHighlightColor = gDialog.LastPickedColor.getAttribute(
+ "LastHighlightColor"
+ );
+ }
+ LastPickedColor = gColorObj.LastHighlightColor;
+ } else {
+ if (
+ !("LastBackgroundColor" in gColorObj) ||
+ !gColorObj.LastBackgroundColor
+ ) {
+ gColorObj.LastBackgroundColor = gDialog.LastPickedColor.getAttribute(
+ "LastBackgroundColor"
+ );
+ }
+ LastPickedColor = gColorObj.LastBackgroundColor;
+ }
+
+ // Set method to detect clicking on OK button
+ // so we don't get fooled by changing "default" behavior
+ gDialog.Ok.setAttribute("onclick", "SetDefaultToOk()");
+
+ if (!LastPickedColor) {
+ // Hide the button, as there is no last color available.
+ gDialog.LastPickedButton.hidden = true;
+ } else {
+ gDialog.LastPickedColor.setAttribute(
+ "style",
+ "background-color: " + LastPickedColor
+ );
+
+ // Make "Last-picked" the default button, until the user selects a color.
+ gDialog.Ok.removeAttribute("default");
+ gDialog.LastPickedButton.setAttribute("default", "true");
+ }
+
+ // Caller can prevent user from submitting an empty, i.e., default color
+ NoDefault = gColorObj.NoDefault;
+ if (NoDefault) {
+ // Hide the "Default button -- user must pick a color
+ document.getElementById("DefaultColorButton").collapsed = true;
+ }
+
+ // Set focus to colorpicker if not set to table radio buttons above
+ if (!haveTableRadio) {
+ gDialog.ColorPicker.focus();
+ }
+
+ SetWindowLocation();
+}
+
+function SelectColor() {
+ var color = gDialog.ColorPicker.value;
+ if (color) {
+ SetCurrentColor(color);
+ }
+}
+
+function RemoveColor() {
+ SetCurrentColor("");
+ gDialog.ColorInput.focus();
+ SetDefaultToOk();
+}
+
+function SelectColorByKeypress(aEvent) {
+ if (aEvent.charCode == aEvent.DOM_VK_SPACE) {
+ SelectColor();
+ SetDefaultToOk();
+ }
+}
+
+function SelectLastPickedColor() {
+ SetCurrentColor(LastPickedColor);
+ if (onAccept()) {
+ // window.close();
+ return true;
+ }
+
+ return false;
+}
+
+function SetCurrentColor(color) {
+ // TODO: Validate color?
+ if (!color) {
+ color = "";
+ }
+ gColor = TrimString(color).toLowerCase();
+ if (gColor == "mixed") {
+ gColor = "";
+ }
+ gDialog.ColorInput.value = gColor;
+ SetColorSwatch();
+}
+
+function SetColorSwatch() {
+ // TODO: DON'T ALLOW SPACES?
+ var color = TrimString(gDialog.ColorInput.value);
+ if (color) {
+ gDialog.ColorSwatch.setAttribute("style", "background-color:" + color);
+ gDialog.ColorSwatch.removeAttribute("default");
+ } else {
+ gDialog.ColorSwatch.setAttribute("style", "background-color:inherit");
+ gDialog.ColorSwatch.setAttribute("default", "true");
+ }
+}
+
+function SetDefaultToOk() {
+ gDialog.LastPickedButton.removeAttribute("default");
+ gDialog.Ok.setAttribute("default", "true");
+ LastPickedIsDefault = false;
+}
+
+function ValidateData() {
+ if (LastPickedIsDefault) {
+ gColor = LastPickedColor;
+ } else {
+ gColor = gDialog.ColorInput.value;
+ }
+
+ gColor = TrimString(gColor).toLowerCase();
+
+ // TODO: Validate the color string!
+
+ if (NoDefault && !gColor) {
+ ShowInputErrorMessage(GetString("NoColorError"));
+ SetTextboxFocus(gDialog.ColorInput);
+ return false;
+ }
+ return true;
+}
+
+function onAccept(event) {
+ if (!ValidateData()) {
+ event.preventDefault();
+ return;
+ }
+
+ // Set return values and save in persistent color attributes
+ if (TextType) {
+ gColorObj.TextColor = gColor;
+ if (gColor.length > 0) {
+ gDialog.LastPickedColor.setAttribute("LastTextColor", gColor);
+ gColorObj.LastTextColor = gColor;
+ }
+ } else if (HighlightType) {
+ gColorObj.HighlightColor = gColor;
+ if (gColor.length > 0) {
+ gDialog.LastPickedColor.setAttribute("LastHighlightColor", gColor);
+ gColorObj.LastHighlightColor = gColor;
+ }
+ } else {
+ gColorObj.BackgroundColor = gColor;
+ if (gColor.length > 0) {
+ gDialog.LastPickedColor.setAttribute("LastBackgroundColor", gColor);
+ gColorObj.LastBackgroundColor = gColor;
+ }
+ // If table or cell requested, tell caller which element to set on
+ if (TableOrCell && gDialog.TableRadio.selected) {
+ gColorObj.Type = "Table";
+ }
+ }
+ SaveWindowLocation();
+}
+
+function onCancelColor() {
+ // Tells caller that user canceled
+ gColorObj.Cancel = true;
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdColorPicker.xhtml b/comm/suite/editor/components/dialogs/content/EdColorPicker.xhtml
new file mode 100644
index 0000000000..c18bc90e62
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdColorPicker.xhtml
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EdColorPicker.dtd">
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdColorPicker.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <hbox id="TableOrCellGroup" align="center" collapsed="true">
+ <label control="CellOrTableGroup" value="&background.label;" accesskey="&background.accessKey;"/>
+ <radiogroup id="CellOrTableGroup" orient="horizontal">
+ <radio id="TableRadio" label="&table.label;" accesskey="&table.accessKey;"/>
+ <radio id="CellRadio" label="&cell.label;" accesskey="&cell.accessKey;"/>
+ </radiogroup>
+ </hbox>
+ <label value="&chooseColor1.label;"/>
+ <html:input type="color" id="ColorPicker"
+ onclick="SetDefaultToOk();"
+ ondblclick="if (onAccept()) { window.close(); }"
+ onkeypress="SelectColorByKeypress(event);"
+ onchange="SelectColor();"/>
+
+ <spacer class="spacer"/>
+ <vbox flex="1">
+ <button id="LastPickedButton" crop="right" oncommand="SelectLastPickedColor();">
+ <spacer id="LastPickedColor"
+ LastTextColor="" LastBackgroundColor=""
+ persist="LastTextColor LastBackgroundColor"/>
+ <label value="&lastPickedColor.label;" accesskey="&lastPickedColor.accessKey;" flex="1" style="text-align: center;"/>
+ </button>
+ <label value="&chooseColor2.label;" accesskey="&chooseColor2.accessKey;" control="ColorInput"/>
+ <label value="&setColorExample.label;"/>
+ <hbox align="center" flex="1=">
+ <textbox id="ColorInput" style="width: 8em" oninput="SetColorSwatch(); SetDefaultToOk();"/>
+ <spacer flex="1"/>
+ <spacer id="ColorPickerSwatch"/>
+ <spacer flex="1"/>
+ <button id="DefaultColorButton" label="&default.label;" accesskey="&default.accessKey;"
+ style="margin-right:0px;" oncommand="RemoveColor()"/>
+ </hbox>
+ </vbox>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdColorProps.js b/comm/suite/editor/components/dialogs/content/EdColorProps.js
new file mode 100644
index 0000000000..62d3f29c9a
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdColorProps.js
@@ -0,0 +1,476 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ Behavior notes:
+ Radio buttons select "UseDefaultColors" vs. "UseCustomColors" modes.
+ If any color attribute is set in the body, mode is "Custom Colors",
+ even if 1 or more (but not all) are actually null (= "use default")
+ When in "Custom Colors" mode, all colors will be set on body tag,
+ even if they are just default colors, to assure compatible colors in page.
+ User cannot select "use default" for individual colors
+*/
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// Cancel() is in EdDialogCommon.js
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+var gBodyElement;
+var prefs;
+var gBackgroundImage;
+
+// Initialize in case we can't get them from prefs???
+var defaultTextColor = "#000000";
+var defaultLinkColor = "#000099";
+var defaultActiveColor = "#000099";
+var defaultVisitedColor = "#990099";
+var defaultBackgroundColor = "#FFFFFF";
+const styleStr = "style";
+const textStr = "text";
+const linkStr = "link";
+const vlinkStr = "vlink";
+const alinkStr = "alink";
+const bgcolorStr = "bgcolor";
+const backgroundStr = "background";
+const cssColorStr = "color";
+const cssBackgroundColorStr = "background-color";
+const cssBackgroundImageStr = "background-image";
+const colorStyle = cssColorStr + ": ";
+const backColorStyle = cssBackgroundColorStr + ": ";
+const backImageStyle = "; " + cssBackgroundImageStr + ": url(";
+
+var customTextColor;
+var customLinkColor;
+var customActiveColor;
+var customVisitedColor;
+var customBackgroundColor;
+var previewBGColor;
+
+// dialog initialization code
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ gDialog.ColorPreview = document.getElementById("ColorPreview");
+ gDialog.NormalText = document.getElementById("NormalText");
+ gDialog.LinkText = document.getElementById("LinkText");
+ gDialog.ActiveLinkText = document.getElementById("ActiveLinkText");
+ gDialog.VisitedLinkText = document.getElementById("VisitedLinkText");
+ gDialog.PageColorGroup = document.getElementById("PageColorGroup");
+ gDialog.DefaultColorsRadio = document.getElementById("DefaultColorsRadio");
+ gDialog.CustomColorsRadio = document.getElementById("CustomColorsRadio");
+ gDialog.BackgroundImageInput = document.getElementById(
+ "BackgroundImageInput"
+ );
+
+ try {
+ gBodyElement = editor.rootElement;
+ } catch (e) {}
+
+ if (!gBodyElement) {
+ dump("Failed to get BODY element!\n");
+ window.close();
+ }
+
+ // Set element we will edit
+ globalElement = gBodyElement.cloneNode(false);
+
+ // Initialize default colors from browser prefs
+ var browserColors = GetDefaultBrowserColors();
+ if (browserColors) {
+ // Use author's browser pref colors passed into dialog
+ defaultTextColor = browserColors.TextColor;
+ defaultLinkColor = browserColors.LinkColor;
+ defaultActiveColor = browserColors.ActiveLinkColor;
+ defaultVisitedColor = browserColors.VisitedLinkColor;
+ defaultBackgroundColor = browserColors.BackgroundColor;
+ }
+
+ // We only need to test for this once per dialog load
+ gHaveDocumentUrl = GetDocumentBaseUrl();
+
+ InitDialog();
+
+ gDialog.PageColorGroup.focus();
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ // Get image from document
+ gBackgroundImage = GetHTMLOrCSSStyleValue(
+ globalElement,
+ backgroundStr,
+ cssBackgroundImageStr
+ );
+ if (/url\((.*)\)/.test(gBackgroundImage)) {
+ gBackgroundImage = RegExp.$1;
+ }
+
+ if (gBackgroundImage) {
+ // Shorten data URIs for display.
+ shortenImageData(gBackgroundImage, gDialog.BackgroundImageInput);
+ gDialog.ColorPreview.setAttribute(
+ styleStr,
+ backImageStyle + gBackgroundImage + ");"
+ );
+ }
+
+ SetRelativeCheckbox();
+
+ customTextColor = GetHTMLOrCSSStyleValue(globalElement, textStr, cssColorStr);
+ customTextColor = ConvertRGBColorIntoHEXColor(customTextColor);
+ customLinkColor = globalElement.getAttribute(linkStr);
+ customActiveColor = globalElement.getAttribute(alinkStr);
+ customVisitedColor = globalElement.getAttribute(vlinkStr);
+ customBackgroundColor = GetHTMLOrCSSStyleValue(
+ globalElement,
+ bgcolorStr,
+ cssBackgroundColorStr
+ );
+ customBackgroundColor = ConvertRGBColorIntoHEXColor(customBackgroundColor);
+
+ var haveCustomColor =
+ customTextColor ||
+ customLinkColor ||
+ customVisitedColor ||
+ customActiveColor ||
+ customBackgroundColor;
+
+ // Set default color explicitly for any that are missing
+ // PROBLEM: We are using "windowtext" and "window" for the Windows OS
+ // default color values. This works with CSS in preview window,
+ // but we should NOT use these as values for HTML attributes!
+
+ if (!customTextColor) {
+ customTextColor = defaultTextColor;
+ }
+ if (!customLinkColor) {
+ customLinkColor = defaultLinkColor;
+ }
+ if (!customActiveColor) {
+ customActiveColor = defaultActiveColor;
+ }
+ if (!customVisitedColor) {
+ customVisitedColor = defaultVisitedColor;
+ }
+ if (!customBackgroundColor) {
+ customBackgroundColor = defaultBackgroundColor;
+ }
+
+ if (haveCustomColor) {
+ // If any colors are set, then check the "Custom" radio button
+ gDialog.PageColorGroup.selectedItem = gDialog.CustomColorsRadio;
+ UseCustomColors();
+ } else {
+ gDialog.PageColorGroup.selectedItem = gDialog.DefaultColorsRadio;
+ UseDefaultColors();
+ }
+}
+
+function GetColorAndUpdate(ColorWellID) {
+ // Only allow selecting when in custom mode
+ if (!gDialog.CustomColorsRadio.selected) {
+ return;
+ }
+
+ var colorWell = document.getElementById(ColorWellID);
+ if (!colorWell) {
+ return;
+ }
+
+ // Don't allow a blank color, i.e., using the "default"
+ var colorObj = {
+ NoDefault: true,
+ Type: "",
+ TextColor: 0,
+ PageColor: 0,
+ Cancel: false,
+ };
+
+ switch (ColorWellID) {
+ case "textCW":
+ colorObj.Type = "Text";
+ colorObj.TextColor = customTextColor;
+ break;
+ case "linkCW":
+ colorObj.Type = "Link";
+ colorObj.TextColor = customLinkColor;
+ break;
+ case "activeCW":
+ colorObj.Type = "ActiveLink";
+ colorObj.TextColor = customActiveColor;
+ break;
+ case "visitedCW":
+ colorObj.Type = "VisitedLink";
+ colorObj.TextColor = customVisitedColor;
+ break;
+ case "backgroundCW":
+ colorObj.Type = "Page";
+ colorObj.PageColor = customBackgroundColor;
+ break;
+ }
+
+ window.openDialog(
+ "chrome://editor/content/EdColorPicker.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ "",
+ colorObj
+ );
+
+ // User canceled the dialog
+ if (colorObj.Cancel) {
+ return;
+ }
+
+ var color = "";
+ switch (ColorWellID) {
+ case "textCW":
+ color = customTextColor = colorObj.TextColor;
+ break;
+ case "linkCW":
+ color = customLinkColor = colorObj.TextColor;
+ break;
+ case "activeCW":
+ color = customActiveColor = colorObj.TextColor;
+ break;
+ case "visitedCW":
+ color = customVisitedColor = colorObj.TextColor;
+ break;
+ case "backgroundCW":
+ color = customBackgroundColor = colorObj.BackgroundColor;
+ break;
+ }
+
+ setColorWell(ColorWellID, color);
+ SetColorPreview(ColorWellID, color);
+}
+
+function SetColorPreview(ColorWellID, color) {
+ switch (ColorWellID) {
+ case "textCW":
+ gDialog.NormalText.setAttribute(styleStr, colorStyle + color);
+ break;
+ case "linkCW":
+ gDialog.LinkText.setAttribute(styleStr, colorStyle + color);
+ break;
+ case "activeCW":
+ gDialog.ActiveLinkText.setAttribute(styleStr, colorStyle + color);
+ break;
+ case "visitedCW":
+ gDialog.VisitedLinkText.setAttribute(styleStr, colorStyle + color);
+ break;
+ case "backgroundCW":
+ // Must combine background color and image style values
+ var styleValue = backColorStyle + color;
+ if (gBackgroundImage) {
+ styleValue += ";" + backImageStyle + gBackgroundImage + ");";
+ }
+
+ gDialog.ColorPreview.setAttribute(styleStr, styleValue);
+ previewBGColor = color;
+ break;
+ }
+}
+
+function UseCustomColors() {
+ SetElementEnabledById("TextButton", true);
+ SetElementEnabledById("LinkButton", true);
+ SetElementEnabledById("ActiveLinkButton", true);
+ SetElementEnabledById("VisitedLinkButton", true);
+ SetElementEnabledById("BackgroundButton", true);
+ SetElementEnabledById("Text", true);
+ SetElementEnabledById("Link", true);
+ SetElementEnabledById("Active", true);
+ SetElementEnabledById("Visited", true);
+ SetElementEnabledById("Background", true);
+
+ SetColorPreview("textCW", customTextColor);
+ SetColorPreview("linkCW", customLinkColor);
+ SetColorPreview("activeCW", customActiveColor);
+ SetColorPreview("visitedCW", customVisitedColor);
+ SetColorPreview("backgroundCW", customBackgroundColor);
+
+ setColorWell("textCW", customTextColor);
+ setColorWell("linkCW", customLinkColor);
+ setColorWell("activeCW", customActiveColor);
+ setColorWell("visitedCW", customVisitedColor);
+ setColorWell("backgroundCW", customBackgroundColor);
+}
+
+function UseDefaultColors() {
+ SetColorPreview("textCW", defaultTextColor);
+ SetColorPreview("linkCW", defaultLinkColor);
+ SetColorPreview("activeCW", defaultActiveColor);
+ SetColorPreview("visitedCW", defaultVisitedColor);
+ SetColorPreview("backgroundCW", defaultBackgroundColor);
+
+ // Setting to blank color will remove color from buttons,
+ setColorWell("textCW", "");
+ setColorWell("linkCW", "");
+ setColorWell("activeCW", "");
+ setColorWell("visitedCW", "");
+ setColorWell("backgroundCW", "");
+
+ // Disable color buttons and labels
+ SetElementEnabledById("TextButton", false);
+ SetElementEnabledById("LinkButton", false);
+ SetElementEnabledById("ActiveLinkButton", false);
+ SetElementEnabledById("VisitedLinkButton", false);
+ SetElementEnabledById("BackgroundButton", false);
+ SetElementEnabledById("Text", false);
+ SetElementEnabledById("Link", false);
+ SetElementEnabledById("Active", false);
+ SetElementEnabledById("Visited", false);
+ SetElementEnabledById("Background", false);
+}
+
+function chooseFile() {
+ // Get a local image file, converted into URL format
+ GetLocalFileURL("img").then(fileURL => {
+ // Always try to relativize local file URLs
+ if (gHaveDocumentUrl) {
+ fileURL = MakeRelativeUrl(fileURL);
+ }
+
+ gDialog.BackgroundImageInput.value = fileURL;
+
+ SetRelativeCheckbox();
+ ValidateAndPreviewImage(true);
+ SetTextboxFocus(gDialog.BackgroundImageInput);
+ });
+}
+
+function ChangeBackgroundImage() {
+ // Don't show error message for image while user is typing
+ ValidateAndPreviewImage(false);
+ SetRelativeCheckbox();
+}
+
+function ValidateAndPreviewImage(ShowErrorMessage) {
+ // First make a string with just background color
+ var styleValue = backColorStyle + previewBGColor + ";";
+
+ var retVal = true;
+ var image = TrimString(gDialog.BackgroundImageInput.value);
+ if (image) {
+ if (isImageDataShortened(image)) {
+ gBackgroundImage = restoredImageData(gDialog.BackgroundImageInput);
+ } else {
+ gBackgroundImage = image;
+
+ // Display must use absolute URL if possible
+ var displayImage = gHaveDocumentUrl ? MakeAbsoluteUrl(image) : image;
+ styleValue += backImageStyle + displayImage + ");";
+ }
+ } else {
+ gBackgroundImage = null;
+ }
+
+ // Set style on preview (removes image if not valid)
+ gDialog.ColorPreview.setAttribute(styleStr, styleValue);
+
+ // Note that an "empty" string is valid
+ return retVal;
+}
+
+function ValidateData() {
+ var editor = GetCurrentEditor();
+ try {
+ // Colors values are updated as they are picked, no validation necessary
+ if (gDialog.DefaultColorsRadio.selected) {
+ editor.removeAttributeOrEquivalent(globalElement, textStr, true);
+ globalElement.removeAttribute(linkStr);
+ globalElement.removeAttribute(vlinkStr);
+ globalElement.removeAttribute(alinkStr);
+ editor.removeAttributeOrEquivalent(globalElement, bgcolorStr, true);
+ } else {
+ // Do NOT accept the CSS "WindowsOS" color strings!
+ // Problem: We really should try to get the actual color values
+ // from windows, but I don't know how to do that!
+ var tmpColor = customTextColor.toLowerCase();
+ if (tmpColor != "windowtext") {
+ editor.setAttributeOrEquivalent(
+ globalElement,
+ textStr,
+ customTextColor,
+ true
+ );
+ } else {
+ editor.removeAttributeOrEquivalent(globalElement, textStr, true);
+ }
+
+ tmpColor = customBackgroundColor.toLowerCase();
+ if (tmpColor != "window") {
+ editor.setAttributeOrEquivalent(
+ globalElement,
+ bgcolorStr,
+ customBackgroundColor,
+ true
+ );
+ } else {
+ editor.removeAttributeOrEquivalent(globalElement, bgcolorStr, true);
+ }
+
+ globalElement.setAttribute(linkStr, customLinkColor);
+ globalElement.setAttribute(vlinkStr, customVisitedColor);
+ globalElement.setAttribute(alinkStr, customActiveColor);
+ }
+
+ if (ValidateAndPreviewImage(true)) {
+ // A valid image may be null for no image
+ if (gBackgroundImage) {
+ globalElement.setAttribute(backgroundStr, gBackgroundImage);
+ } else {
+ editor.removeAttributeOrEquivalent(globalElement, backgroundStr, true);
+ }
+
+ return true;
+ }
+ } catch (e) {}
+ return false;
+}
+
+function onAccept(event) {
+ // If it's a file, convert to a data URL.
+ if (gBackgroundImage && /^file:/i.test(gBackgroundImage)) {
+ let nsFile = Services.io
+ .newURI(gBackgroundImage)
+ .QueryInterface(Ci.nsIFileURL).file;
+ if (nsFile.exists()) {
+ let reader = new FileReader();
+ reader.addEventListener("load", function() {
+ gBackgroundImage = reader.result;
+ gDialog.BackgroundImageInput.value = reader.result;
+ if (onAccept(event)) {
+ window.close();
+ }
+ });
+ File.createFromNsIFile(nsFile).then(file => {
+ reader.readAsDataURL(file);
+ });
+ event.preventDefault(); // Don't close just yet...
+ return false;
+ }
+ }
+ if (ValidateData()) {
+ // Copy attributes to element we are changing
+ try {
+ GetCurrentEditor().cloneAttributes(gBodyElement, globalElement);
+ } catch (e) {}
+
+ SaveWindowLocation();
+ return true; // do close the window
+ }
+ event.preventDefault();
+ return false;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdColorProps.xhtml b/comm/suite/editor/components/dialogs/content/EdColorProps.xhtml
new file mode 100644
index 0000000000..85393ed209
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdColorProps.xhtml
@@ -0,0 +1,134 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edColorPropertiesDTD SYSTEM "chrome://editor/locale/EditorColorProperties.dtd">
+%edColorPropertiesDTD;
+<!ENTITY % composeEditorOverlayDTD SYSTEM "chrome://messenger/locale/messengercompose/mailComposeEditorOverlay.dtd">
+%composeEditorOverlayDTD;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdColorProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox align="start">
+ <hbox class="groupbox-title">
+ <label class="header">&pageColors.label;</label>
+ </hbox>
+ <radiogroup id="PageColorGroup">
+ <radio id="DefaultColorsRadio" label="&defaultColorsRadio.label;" oncommand="UseDefaultColors()"
+ accesskey="&defaultColorsRadio.accessKey;"
+ tooltiptext="&defaultColorsRadio.tooltip;" />
+ <radio id="CustomColorsRadio" label="&customColorsRadio.label;" oncommand="UseCustomColors()"
+ accesskey="&customColorsRadio.accessKey;"
+ tooltiptext="&customColorsRadio.tooltip;" />
+ </radiogroup>
+ <hbox class="indent">
+ <grid>
+ <columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label id="Text" control="TextButton"
+ value="&normalText.label;&colon.character;"
+ accesskey="&normalText.accessKey;"/>
+ <button id="TextButton" class="color-button" oncommand="GetColorAndUpdate('textCW');">
+ <spacer id="textCW" class="color-well"/>
+ </button>
+ </row>
+ <row align="center">
+ <label id="Link" control="LinkButton"
+ value="&linkText.label;&colon.character;"
+ accesskey="&linkText.accessKey;"/>
+ <button id="LinkButton" class="color-button" oncommand="GetColorAndUpdate('linkCW');">
+ <spacer id="linkCW" class="color-well"/>
+ </button>
+ </row>
+ <row align="center">
+ <label id="Active" control="ActiveLinkButton"
+ value="&activeLinkText.label;&colon.character;"
+ accesskey="&activeLinkText.accessKey;"/>
+ <button id="ActiveLinkButton" class="color-button" oncommand="GetColorAndUpdate('activeCW');">
+ <spacer id="activeCW" class="color-well"/>
+ </button>
+ </row>
+ <row align="center">
+ <label id="Visited" control="VisitedLinkButton"
+ value="&visitedLinkText.label;&colon.character;"
+ accesskey="&visitedLinkText.accessKey;"/>
+ <button id="VisitedLinkButton" class="color-button" oncommand="GetColorAndUpdate('visitedCW');">
+ <spacer id="visitedCW" class="color-well"/>
+ </button>
+ </row>
+ <row align="center">
+ <label id="Background" control="BackgroundButton"
+ value="&background.label;"
+ accesskey="&background.accessKey;"/>
+ <button id="BackgroundButton" class="color-button" oncommand="GetColorAndUpdate('backgroundCW');">
+ <spacer id="backgroundCW" class="color-well"/>
+ </button>
+ </row>
+ </rows>
+ </grid>
+ <vbox id="ColorPreview" flex="1">
+ <spacer flex="1"/>
+ <label class="larger" id="NormalText" value="&normalText.label;"/>
+ <spacer flex="1"/>
+ <label class="larger" id="LinkText" value="&linkText.label;"/>
+ <spacer flex="1"/>
+ <label class="larger" id="ActiveLinkText" value="&activeLinkText.label;"/>
+ <spacer flex="1"/>
+ <label class="larger" id="VisitedLinkText" value="&visitedLinkText.label;"/>
+ <spacer flex="1"/>
+ </vbox>
+ <spacer flex="1"/>
+ </hbox>
+ <spacer class="spacer"/>
+ </groupbox>
+ <spacer class="spacer"/>
+ <label control="BackgroundImageInput"
+ value="&backgroundImage.label;"
+ tooltiptext="&backgroundImage.tooltip;"
+ accesskey="&backgroundImage.accessKey;"/>
+ <tooltip id="shortenedDataURI">
+ <label value="&backgroundImage.shortenedDataURI;"/>
+ </tooltip>
+ <textbox id="BackgroundImageInput" class="uri-element" oninput="ChangeBackgroundImage()"
+ tooltiptext="&backgroundImage.tooltip;" flex="1"/>
+ <hbox align="center">
+ <checkbox id="MakeRelativeCheckbox"
+ for="BackgroundImageInput"
+ label="&makeUrlRelative.label;"
+ accesskey="&makeUrlRelative.accessKey;"
+ oncommand="MakeInputValueRelativeOrAbsolute(this);"
+ tooltiptext="&makeUrlRelative.tooltip;"/>
+ <spacer flex="1"/>
+ <button id="ChooseFile"
+ oncommand="chooseFile()"
+ label="&chooseFileButton.label;"
+ accesskey="&chooseFileButton.accessKey;"/>
+ </hbox>
+ <spacer class="smallspacer"/>
+ <hbox>
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdConvertToTable.js b/comm/suite/editor/components/dialogs/content/EdConvertToTable.js
new file mode 100644
index 0000000000..a149e708f8
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdConvertToTable.js
@@ -0,0 +1,326 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+var gIndex;
+var gCommaIndex = "0";
+var gSpaceIndex = "1";
+var gOtherIndex = "2";
+
+// dialog initialization code
+function Startup() {
+ if (!GetCurrentEditor()) {
+ window.close();
+ return;
+ }
+
+ gDialog.sepRadioGroup = document.getElementById("SepRadioGroup");
+ gDialog.sepCharacterInput = document.getElementById("SepCharacterInput");
+ gDialog.deleteSepCharacter = document.getElementById("DeleteSepCharacter");
+ gDialog.collapseSpaces = document.getElementById("CollapseSpaces");
+
+ // We persist the user's separator character
+ gDialog.sepCharacterInput.value = gDialog.sepRadioGroup.getAttribute(
+ "character"
+ );
+
+ gIndex = gDialog.sepRadioGroup.getAttribute("index");
+
+ switch (gIndex) {
+ case gCommaIndex:
+ default:
+ gDialog.sepRadioGroup.selectedItem = document.getElementById("comma");
+ break;
+ case gSpaceIndex:
+ gDialog.sepRadioGroup.selectedItem = document.getElementById("space");
+ break;
+ case gOtherIndex:
+ gDialog.sepRadioGroup.selectedItem = document.getElementById("other");
+ break;
+ }
+
+ // Set initial enable state on character input and "collapse" checkbox
+ SelectCharacter(gIndex);
+
+ SetWindowLocation();
+}
+
+function InputSepCharacter() {
+ var str = gDialog.sepCharacterInput.value;
+
+ // Limit input to 1 character
+ if (str.length > 1) {
+ str = str.slice(0, 1);
+ }
+
+ // We can never allow tag or entity delimiters for separator character
+ if (str == "<" || str == ">" || str == "&" || str == ";" || str == " ") {
+ str = "";
+ }
+
+ gDialog.sepCharacterInput.value = str;
+}
+
+function SelectCharacter(radioGroupIndex) {
+ gIndex = radioGroupIndex;
+ SetElementEnabledById("SepCharacterInput", gIndex == gOtherIndex);
+ SetElementEnabledById("CollapseSpaces", gIndex == gSpaceIndex);
+}
+
+/* eslint-disable complexity */
+function onAccept() {
+ var sepCharacter = "";
+ switch (gIndex) {
+ case gCommaIndex:
+ sepCharacter = ",";
+ break;
+ case gSpaceIndex:
+ sepCharacter = " ";
+ break;
+ case gOtherIndex:
+ sepCharacter = gDialog.sepCharacterInput.value.slice(0, 1);
+ break;
+ }
+
+ var editor = GetCurrentEditor();
+ var str;
+ try {
+ str = editor.outputToString(
+ "text/html",
+ kOutputLFLineBreak | kOutputSelectionOnly
+ );
+ } catch (e) {}
+ if (!str) {
+ SaveWindowLocation();
+ return;
+ }
+
+ // Replace nbsp with spaces:
+ str = str.replace(/\u00a0/g, " ");
+
+ // Strip out </p> completely
+ str = str.replace(/\s*<\/p>\s*/g, "");
+
+ // Trim whitespace adjacent to <p> and <br> tags
+ // and replace <p> with <br>
+ // (which will be replaced with </tr> below)
+ str = str.replace(/\s*<p>\s*|\s*<br>\s*/g, "<br>");
+
+ // Trim leading <br>s
+ str = str.replace(/^(<br>)+/, "");
+
+ // Trim trailing <br>s
+ str = str.replace(/(<br>)+$/, "");
+
+ // Reduce multiple internal <br> to just 1
+ // TODO: Maybe add a checkbox to let user decide
+ // str = str.replace(/(<br>)+/g, "<br>");
+
+ // Trim leading and trailing spaces
+ str = str.trim();
+
+ // Remove all tag contents so we don't replace
+ // separator character within tags
+ // Also converts lists to something useful
+ var stack = [];
+ var start;
+ var end;
+ var searchStart = 0;
+ var listSeparator = "";
+ var listItemSeparator = "";
+ var endList = false;
+
+ do {
+ start = str.indexOf("<", searchStart);
+
+ if (start >= 0) {
+ end = str.indexOf(">", start + 1);
+ if (end > start) {
+ let tagContent = str.slice(start + 1, end).trim();
+
+ if (/^ol|^ul|^dl/.test(tagContent)) {
+ // Replace list tag with <BR> to start new row
+ // at beginning of second or greater list tag
+ str = str.slice(0, start) + listSeparator + str.slice(end + 1);
+ if (listSeparator == "") {
+ listSeparator = "<br>";
+ }
+
+ // Reset for list item separation into cells
+ listItemSeparator = "";
+ } else if (/^li|^dt|^dd/.test(tagContent)) {
+ // Start a new row if this is first item after the ending the last list
+ if (endList) {
+ listItemSeparator = "<br>";
+ }
+
+ // Start new cell at beginning of second or greater list items
+ str = str.slice(0, start) + listItemSeparator + str.slice(end + 1);
+
+ if (endList || listItemSeparator == "") {
+ listItemSeparator = sepCharacter;
+ }
+
+ endList = false;
+ } else {
+ // Find end tags
+ endList = /^\/ol|^\/ul|^\/dl/.test(tagContent);
+ if (endList || /^\/li|^\/dt|^\/dd/.test(tagContent)) {
+ // Strip out tag
+ str = str.slice(0, start) + str.slice(end + 1);
+ } else {
+ // Not a list-related tag: Store tag contents in an array
+ stack.push(tagContent);
+
+ // Keep the "<" and ">" while removing from source string
+ start++;
+ str = str.slice(0, start) + str.slice(end);
+ }
+ }
+ }
+ searchStart = start + 1;
+ }
+ } while (start >= 0);
+
+ // Replace separator characters with table cells
+ var replaceString;
+ if (gDialog.deleteSepCharacter.checked) {
+ replaceString = "";
+ } else {
+ // Don't delete separator character,
+ // so include it at start of string to replace
+ replaceString = sepCharacter;
+ }
+
+ replaceString += "<td>";
+
+ if (sepCharacter.length > 0) {
+ var tempStr = sepCharacter;
+ var regExpChars = ".!@#$%^&*-+[]{}()|\\/";
+ if (regExpChars.includes(sepCharacter)) {
+ tempStr = "\\" + sepCharacter;
+ }
+
+ if (gIndex == gSpaceIndex) {
+ // If checkbox is checked,
+ // one or more adjacent spaces are one separator
+ if (gDialog.collapseSpaces.checked) {
+ tempStr = "\\s+";
+ } else {
+ tempStr = "\\s";
+ }
+ }
+ var pattern = new RegExp(tempStr, "g");
+ str = str.replace(pattern, replaceString);
+ }
+
+ // Put back tag contents that we removed above
+ searchStart = 0;
+ var stackIndex = 0;
+ do {
+ start = str.indexOf("<", searchStart);
+ end = start + 1;
+ if (start >= 0 && str.charAt(end) == ">") {
+ // We really need a FIFO stack!
+ str = str.slice(0, end) + stack[stackIndex++] + str.slice(end);
+ }
+ searchStart = end;
+ } while (start >= 0);
+
+ // End table row and start another for each br or p
+ str = str.replace(/\s*<br>\s*/g, "</tr>\n<tr><td>");
+
+ // Add the table tags and the opening and closing tr/td tags
+ // Default table attributes should be same as those used in nsHTMLEditor::CreateElementWithDefaults()
+ // (Default width="100%" is used in EdInsertTable.js)
+ str =
+ '<table border="1" width="100%" cellpadding="2" cellspacing="2">\n<tr><td>' +
+ str +
+ "</tr>\n</table>\n";
+
+ editor.beginTransaction();
+
+ // Delete the selection -- makes it easier to find where table will insert
+ var nodeBeforeTable = null;
+ var nodeAfterTable = null;
+ try {
+ editor.deleteSelection(editor.eNone, editor.eStrip);
+
+ var anchorNodeBeforeInsert = editor.selection.anchorNode;
+ var offset = editor.selection.anchorOffset;
+ if (anchorNodeBeforeInsert.nodeType == Node.TEXT_NODE) {
+ // Text was split. Table should be right after the first or before
+ nodeBeforeTable = anchorNodeBeforeInsert.previousSibling;
+ nodeAfterTable = anchorNodeBeforeInsert;
+ } else {
+ // Table should be inserted right after node pointed to by selection
+ if (offset > 0) {
+ nodeBeforeTable = anchorNodeBeforeInsert.childNodes.item(offset - 1);
+ }
+
+ nodeAfterTable = anchorNodeBeforeInsert.childNodes.item(offset);
+ }
+
+ editor.insertHTML(str);
+ } catch (e) {}
+
+ var table = null;
+ if (nodeAfterTable) {
+ var previous = nodeAfterTable.previousSibling;
+ if (previous && previous.nodeName.toLowerCase() == "table") {
+ table = previous;
+ }
+ }
+ if (!table && nodeBeforeTable) {
+ var next = nodeBeforeTable.nextSibling;
+ if (next && next.nodeName.toLowerCase() == "table") {
+ table = next;
+ }
+ }
+
+ if (table) {
+ // Fixup table only if pref is set
+ var firstRow;
+ try {
+ if (Services.prefs.getBoolPref("editor.table.maintain_structure")) {
+ editor.normalizeTable(table);
+ }
+
+ firstRow = editor.getFirstRow(table);
+ } catch (e) {}
+
+ // Put caret in first cell
+ if (firstRow) {
+ var node2 = firstRow.firstChild;
+ do {
+ if (
+ node2.nodeName.toLowerCase() == "td" ||
+ node2.nodeName.toLowerCase() == "th"
+ ) {
+ try {
+ editor.selection.collapse(node2, 0);
+ } catch (e) {}
+ break;
+ }
+ node2 = node2.nextSibling;
+ } while (node2);
+ }
+ }
+
+ editor.endTransaction();
+
+ // Save persisted attributes
+ gDialog.sepRadioGroup.setAttribute("index", gIndex);
+ if (gIndex == gOtherIndex) {
+ gDialog.sepRadioGroup.setAttribute("character", sepCharacter);
+ }
+
+ SaveWindowLocation();
+}
+/* eslint-enable complexity */
diff --git a/comm/suite/editor/components/dialogs/content/EdConvertToTable.xhtml b/comm/suite/editor/components/dialogs/content/EdConvertToTable.xhtml
new file mode 100644
index 0000000000..d3d5c4a465
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdConvertToTable.xhtml
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EdConvertToTable.dtd">
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload = "Startup()"
+ style="min-width:20em">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <!--- Element-specific methods -->
+ <script src="chrome://editor/content/EdConvertToTable.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+ <description class="wrap" flex="1">&instructions1.label;</description>
+ <description class="wrap" flex="1">&instructions2.label;</description>
+ <radiogroup id="SepRadioGroup" persist="index character" index="0" character="">
+ <radio id="comma" label="&commaRadio.label;" oncommand="SelectCharacter('0');"/>
+ <radio id="space" label="&spaceRadio.label;" oncommand="SelectCharacter('1');"/>
+ <hbox>
+ <spacer class="radio-spacer"/>
+ <checkbox id="CollapseSpaces" label="&collapseSpaces.label;"
+ checked="true" persist="checked"
+ tooltiptext="&collapseSpaces.tooltip;"/>
+ </hbox>
+ <hbox align="center">
+ <radio id="other" label="&otherRadio.label;" oncommand="SelectCharacter('2');"/>
+ <textbox class="narrow" id="SepCharacterInput" oninput="InputSepCharacter()"/>
+ </hbox>
+ </radiogroup>
+ <spacer class="spacer"/>
+ <checkbox id="DeleteSepCharacter" label="&deleteCharCheck.label;" persist="checked"/>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdDialogCommon.js b/comm/suite/editor/components/dialogs/content/EdDialogCommon.js
new file mode 100644
index 0000000000..c6b7c63778
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdDialogCommon.js
@@ -0,0 +1,1038 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Each editor window must include this file
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* globals InitDialog, ChangeLinkLocation, ValidateData */
+
+// Object to attach commonly-used widgets (all dialogs should use this)
+var gDialog = {};
+
+var gHaveDocumentUrl = false;
+var gValidationError = false;
+
+// Use for 'defaultIndex' param in InitPixelOrPercentMenulist
+const gPixel = 0;
+const gPercent = 1;
+
+const gMaxPixels = 100000; // Used for image size, borders, spacing, and padding
+// Gecko code uses 1000 for maximum rowspan, colspan
+// Also, editing performance is really bad above this
+const gMaxRows = 1000;
+const gMaxColumns = 1000;
+const gMaxTableSize = 1000000; // Width or height of table or cells
+
+// For dialogs that expand in size. Default is smaller size see "onMoreFewer()" below
+var SeeMore = false;
+
+// A XUL element with id="location" for managing
+// dialog location relative to parent window
+var gLocation;
+
+// The element being edited - so AdvancedEdit can have access to it
+var globalElement;
+
+/* Validate contents of an input field
+ *
+ * inputWidget The 'textbox' XUL element for text input of the attribute's value
+ * listWidget The 'menulist' XUL element for choosing "pixel" or "percent"
+ * May be null when no pixel/percent is used.
+ * minVal minimum allowed for input widget's value
+ * maxVal maximum allowed for input widget's value
+ * (when "listWidget" is used, maxVal is used for "pixel" maximum,
+ * 100% is assumed if "percent" is the user's choice)
+ * element The DOM element that we set the attribute on. May be null.
+ * attName Name of the attribute to set. May be null or ignored if "element" is null
+ * mustHaveValue If true, error dialog is displayed if "value" is empty string
+ *
+ * This calls "ValidateNumberRange()", which puts up an error dialog to inform the user.
+ * If error, we also:
+ * Shift focus and select contents of the inputWidget,
+ * Switch to appropriate panel of tabbed dialog if user implements "SwitchToValidate()",
+ * and/or will expand the dialog to full size if "More / Fewer" feature is implemented
+ *
+ * Returns the "value" as a string, or "" if error or input contents are empty
+ * The global "gValidationError" variable is set true if error was found
+ */
+function ValidateNumber(
+ inputWidget,
+ listWidget,
+ minVal,
+ maxVal,
+ element,
+ attName,
+ mustHaveValue,
+ mustShowMoreSection
+) {
+ if (!inputWidget) {
+ gValidationError = true;
+ return "";
+ }
+
+ // Global error return value
+ gValidationError = false;
+ var maxLimit = maxVal;
+ var isPercent = false;
+
+ var numString = TrimString(inputWidget.value);
+ if (numString || mustHaveValue) {
+ if (listWidget) {
+ isPercent = listWidget.selectedIndex == 1;
+ }
+ if (isPercent) {
+ maxLimit = 100;
+ }
+
+ // This method puts up the error message
+ numString = ValidateNumberRange(numString, minVal, maxLimit, mustHaveValue);
+ if (!numString) {
+ // Switch to appropriate panel for error reporting
+ SwitchToValidatePanel();
+
+ // or expand dialog for users of "More / Fewer" button
+ if (
+ "dialog" in window &&
+ window.dialog &&
+ "MoreSection" in gDialog &&
+ gDialog.MoreSection
+ ) {
+ if (!SeeMore) {
+ onMoreFewer();
+ }
+ }
+
+ // Error - shift to offending input widget
+ SetTextboxFocus(inputWidget);
+ gValidationError = true;
+ } else {
+ if (isPercent) {
+ numString += "%";
+ }
+ if (element) {
+ GetCurrentEditor().setAttributeOrEquivalent(
+ element,
+ attName,
+ numString,
+ true
+ );
+ }
+ }
+ } else if (element) {
+ GetCurrentEditor().removeAttributeOrEquivalent(element, attName, true);
+ }
+ return numString;
+}
+
+/* Validate contents of an input field
+ *
+ * value number to validate
+ * minVal minimum allowed for input widget's value
+ * maxVal maximum allowed for input widget's value
+ * (when "listWidget" is used, maxVal is used for "pixel" maximum,
+ * 100% is assumed if "percent" is the user's choice)
+ * mustHaveValue If true, error dialog is displayed if "value" is empty string
+ *
+ * If inputWidget's value is outside of range, or is empty when "mustHaveValue" = true,
+ * an error dialog is popuped up to inform the user. The focus is shifted
+ * to the inputWidget.
+ *
+ * Returns the "value" as a string, or "" if error or input contents are empty
+ * The global "gValidationError" variable is set true if error was found
+ */
+function ValidateNumberRange(value, minValue, maxValue, mustHaveValue) {
+ // Initialize global error flag
+ gValidationError = false;
+ value = TrimString(String(value));
+
+ // We don't show error for empty string unless caller wants to
+ if (!value && !mustHaveValue) {
+ return "";
+ }
+
+ var numberStr = "";
+
+ if (value.length > 0) {
+ // Extract just numeric characters
+ var number = Number(value.replace(/\D+/g, ""));
+ if (number >= minValue && number <= maxValue) {
+ // Return string version of the number
+ return String(number);
+ }
+ numberStr = String(number);
+ }
+
+ var message = "";
+
+ if (numberStr.length > 0) {
+ // We have a number from user outside of allowed range
+ message = GetString("ValidateRangeMsg");
+ message = message.replace(/%n%/, numberStr);
+ message += "\n ";
+ }
+ message += GetString("ValidateNumberMsg");
+
+ // Replace variable placeholders in message with number values
+ message = message.replace(/%min%/, minValue).replace(/%max%/, maxValue);
+ ShowInputErrorMessage(message);
+
+ // Return an empty string to indicate error
+ gValidationError = true;
+ return "";
+}
+
+function SetTextboxFocusById(id) {
+ SetTextboxFocus(document.getElementById(id));
+}
+
+function SetTextboxFocus(textbox) {
+ if (textbox) {
+ // XXX Using the setTimeout is hacky workaround for bug 103197
+ // Must create a new function to keep "textbox" in scope
+ setTimeout(
+ function(textbox) {
+ textbox.focus();
+ textbox.select();
+ },
+ 0,
+ textbox
+ );
+ }
+}
+
+function ShowInputErrorMessage(message) {
+ Services.prompt.alert(window, GetString("InputError"), message);
+ window.focus();
+}
+
+// Get the text appropriate to parent container
+// to determine what a "%" value is referring to.
+// elementForAtt is element we are actually setting attributes on
+// (a temporary copy of element in the doc to allow canceling),
+// but elementInDoc is needed to find parent context in document
+function GetAppropriatePercentString(elementForAtt, elementInDoc) {
+ var editor = GetCurrentEditor();
+ try {
+ var name = elementForAtt.nodeName.toLowerCase();
+ if (name == "td" || name == "th") {
+ return GetString("PercentOfTable");
+ }
+
+ // Check if element is within a table cell
+ if (editor.getElementOrParentByTagName("td", elementInDoc)) {
+ return GetString("PercentOfCell");
+ }
+ return GetString("PercentOfWindow");
+ } catch (e) {
+ return "";
+ }
+}
+
+function ClearListbox(listbox) {
+ if (listbox) {
+ listbox.clearSelection();
+ while (listbox.hasChildNodes()) {
+ listbox.lastChild.remove();
+ }
+ }
+}
+
+function forceInteger(elementID) {
+ var editField = document.getElementById(elementID);
+ if (!editField) {
+ return;
+ }
+
+ var stringIn = editField.value;
+ if (stringIn && stringIn.length > 0) {
+ // Strip out all nonnumeric characters
+ stringIn = stringIn.replace(/\D+/g, "");
+ if (!stringIn) {
+ stringIn = "";
+ }
+
+ // Write back only if changed
+ if (stringIn != editField.value) {
+ editField.value = stringIn;
+ }
+ }
+}
+
+function InitPixelOrPercentMenulist(
+ elementForAtt,
+ elementInDoc,
+ attribute,
+ menulistID,
+ defaultIndex
+) {
+ if (!defaultIndex) {
+ defaultIndex = gPixel;
+ }
+
+ // var size = elementForAtt.getAttribute(attribute);
+ var size = GetHTMLOrCSSStyleValue(elementForAtt, attribute, attribute);
+ var menulist = document.getElementById(menulistID);
+ var pixelItem;
+ var percentItem;
+
+ if (!menulist) {
+ dump("NO MENULIST found for ID=" + menulistID + "\n");
+ return size;
+ }
+
+ menulist.removeAllItems();
+ pixelItem = menulist.appendItem(GetString("Pixels"));
+
+ if (!pixelItem) {
+ return 0;
+ }
+
+ percentItem = menulist.appendItem(
+ GetAppropriatePercentString(elementForAtt, elementInDoc)
+ );
+ if (size && size.length > 0) {
+ // Search for a "%" or "px"
+ if (size.includes("%")) {
+ // Strip out the %
+ size = size.substr(0, size.indexOf("%"));
+ if (percentItem) {
+ menulist.selectedItem = percentItem;
+ }
+ } else {
+ if (size.includes("px")) {
+ // Strip out the px
+ size = size.substr(0, size.indexOf("px"));
+ }
+ menulist.selectedItem = pixelItem;
+ }
+ } else {
+ menulist.selectedIndex = defaultIndex;
+ }
+
+ return size;
+}
+
+function onAdvancedEdit() {
+ // First validate data from widgets in the "simpler" property dialog
+ if (ValidateData()) {
+ // Set true if OK is clicked in the Advanced Edit dialog
+ window.AdvancedEditOK = false;
+ // Open the AdvancedEdit dialog, passing in the element to be edited
+ // (the copy named "globalElement")
+ window.openDialog(
+ "chrome://editor/content/EdAdvancedEdit.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal,resizable=yes",
+ "",
+ globalElement
+ );
+ window.focus();
+ if (window.AdvancedEditOK) {
+ // Copy edited attributes to the dialog widgets:
+ InitDialog();
+ }
+ }
+}
+
+function getColor(ColorPickerID) {
+ var colorPicker = document.getElementById(ColorPickerID);
+ var color;
+ if (colorPicker) {
+ // Extract color from colorPicker and assign to colorWell.
+ color = colorPicker.getAttribute("color");
+ if (color && color == "") {
+ return null;
+ }
+ // Clear color so next if it's called again before
+ // color picker is actually used, we dedect the "don't set color" state
+ colorPicker.setAttribute("color", "");
+ }
+
+ return color;
+}
+
+function setColorWell(ColorWellID, color) {
+ var colorWell = document.getElementById(ColorWellID);
+ if (colorWell) {
+ if (!color || color == "") {
+ // Don't set color (use default)
+ // Trigger change to not show color swatch
+ colorWell.setAttribute("default", "true");
+ // Style in CSS sets "background-color",
+ // but color won't clear unless we do this:
+ colorWell.removeAttribute("style");
+ } else {
+ colorWell.removeAttribute("default");
+ // Use setAttribute so colorwell can be a XUL element, such as button
+ colorWell.setAttribute("style", "background-color:" + color);
+ }
+ }
+}
+
+function getColorAndSetColorWell(ColorPickerID, ColorWellID) {
+ var color = getColor(ColorPickerID);
+ setColorWell(ColorWellID, color);
+ return color;
+}
+
+function InitMoreFewer() {
+ // Set SeeMore bool to the OPPOSITE of the current state,
+ // which is automatically saved by using the 'persist="more"'
+ // attribute on the gDialog.MoreFewerButton button
+ // onMoreFewer will toggle it and redraw the dialog
+ SeeMore = gDialog.MoreFewerButton.getAttribute("more") != "1";
+ onMoreFewer();
+ gDialog.MoreFewerButton.setAttribute(
+ "accesskey",
+ GetString("PropertiesAccessKey")
+ );
+}
+
+function onMoreFewer() {
+ if (SeeMore) {
+ gDialog.MoreSection.collapsed = true;
+ gDialog.MoreFewerButton.setAttribute("more", "0");
+ gDialog.MoreFewerButton.setAttribute("label", GetString("MoreProperties"));
+ SeeMore = false;
+ } else {
+ gDialog.MoreSection.collapsed = false;
+ gDialog.MoreFewerButton.setAttribute("more", "1");
+ gDialog.MoreFewerButton.setAttribute("label", GetString("FewerProperties"));
+ SeeMore = true;
+ }
+ window.sizeToContent();
+}
+
+function SwitchToValidatePanel() {
+ // no default implementation
+ // Only EdTableProps.js currently implements this
+}
+
+const nsIFilePicker = Ci.nsIFilePicker;
+
+/**
+ * @return {Promise} URL spec of the file chosen, or null
+ */
+function GetLocalFileURL(filterType) {
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ var fileType = "html";
+
+ if (filterType == "img") {
+ fp.init(window, GetString("SelectImageFile"), nsIFilePicker.modeOpen);
+ fp.appendFilters(nsIFilePicker.filterImages);
+ fileType = "image";
+ } else if (filterType.startsWith("html")) {
+ // Current usage of this is in Link dialog,
+ // where we always want HTML first
+ fp.init(window, GetString("OpenHTMLFile"), nsIFilePicker.modeOpen);
+
+ // When loading into Composer, direct user to prefer HTML files and text files,
+ // so we call separately to control the order of the filter list
+ fp.appendFilters(nsIFilePicker.filterHTML);
+ fp.appendFilters(nsIFilePicker.filterText);
+
+ // Link dialog also allows linking to images
+ if (filterType.includes("img", 1)) {
+ fp.appendFilters(nsIFilePicker.filterImages);
+ }
+ }
+ // Default or last filter is "All Files"
+ fp.appendFilters(nsIFilePicker.filterAll);
+
+ // set the file picker's current directory to last-opened location saved in prefs
+ SetFilePickerDirectory(fp, fileType);
+
+ return new Promise(resolve => {
+ fp.open(rv => {
+ if (rv != nsIFilePicker.returnOK || !fp.file) {
+ resolve(null);
+ return;
+ }
+ SaveFilePickerDirectory(fp, fileType);
+ resolve(fp.fileURL.spec);
+ });
+ });
+}
+
+function GetMetaElementByAttribute(name, value) {
+ if (name) {
+ name = name.toLowerCase();
+ let editor = GetCurrentEditor();
+ try {
+ return editor.document.querySelector(
+ "meta[" + name + '="' + value + '"]'
+ );
+ } catch (e) {}
+ }
+ return null;
+}
+
+function CreateMetaElementWithAttribute(name, value) {
+ let editor = GetCurrentEditor();
+ try {
+ let metaElement = editor.createElementWithDefaults("meta");
+ if (name) {
+ metaElement.setAttribute(name, value);
+ }
+ return metaElement;
+ } catch (e) {}
+ return null;
+}
+
+// Change "content" attribute on a META element,
+// or delete entire element it if content is empty
+// This uses undoable editor transactions
+function SetMetaElementContent(metaElement, content, insertNew, prepend) {
+ if (metaElement) {
+ var editor = GetCurrentEditor();
+ try {
+ if (!content || content == "") {
+ if (!insertNew) {
+ editor.deleteNode(metaElement);
+ }
+ } else if (insertNew) {
+ metaElement.setAttribute("content", content);
+ if (prepend) {
+ PrependHeadElement(metaElement);
+ } else {
+ AppendHeadElement(metaElement);
+ }
+ } else {
+ editor.setAttribute(metaElement, "content", content);
+ }
+ } catch (e) {}
+ }
+}
+
+function GetHeadElement() {
+ var editor = GetCurrentEditor();
+ try {
+ return editor.document.querySelector("head");
+ } catch (e) {}
+
+ return null;
+}
+
+function PrependHeadElement(element) {
+ var head = GetHeadElement();
+ if (head) {
+ var editor = GetCurrentEditor();
+ try {
+ // Use editor's undoable transaction
+ // XXX Here tried to prevent updating Selection with unknown 4th argument,
+ // but nsIEditor.setShouldTxnSetSelection is not used for that.
+ editor.insertNode(element, head, 0);
+ } catch (e) {}
+ }
+}
+
+function AppendHeadElement(element) {
+ var head = GetHeadElement();
+ if (head) {
+ var position = 0;
+ if (head.hasChildNodes()) {
+ position = head.childNodes.length;
+ }
+
+ var editor = GetCurrentEditor();
+ try {
+ // Use editor's undoable transaction
+ // XXX Here tried to prevent updating Selection with unknown 4th argument,
+ // but nsIEditor.setShouldTxnSetSelection is not used for that.
+ editor.insertNode(element, head, position);
+ } catch (e) {}
+ }
+}
+
+function SetWindowLocation() {
+ gLocation = document.getElementById("location");
+ if (gLocation) {
+ window.screenX = Math.max(
+ 0,
+ Math.min(
+ window.opener.screenX + Number(gLocation.getAttribute("offsetX")),
+ screen.availWidth - window.outerWidth
+ )
+ );
+ window.screenY = Math.max(
+ 0,
+ Math.min(
+ window.opener.screenY + Number(gLocation.getAttribute("offsetY")),
+ screen.availHeight - window.outerHeight
+ )
+ );
+ }
+}
+
+function SaveWindowLocation() {
+ if (gLocation) {
+ gLocation.setAttribute("offsetX", window.screenX - window.opener.screenX);
+ gLocation.setAttribute("offsetY", window.screenY - window.opener.screenY);
+ }
+}
+
+function onCancel() {
+ SaveWindowLocation();
+}
+
+function SetRelativeCheckbox(checkbox) {
+ if (!checkbox) {
+ checkbox = document.getElementById("MakeRelativeCheckbox");
+ if (!checkbox) {
+ return;
+ }
+ }
+
+ var editor = GetCurrentEditor();
+ // Mail never allows relative URLs, so hide the checkbox
+ if (editor && editor.flags & Ci.nsIEditor.eEditorMailMask) {
+ checkbox.collapsed = true;
+ return;
+ }
+
+ var input = document.getElementById(checkbox.getAttribute("for"));
+ if (!input) {
+ return;
+ }
+
+ var url = TrimString(input.value);
+ var urlScheme = GetScheme(url);
+
+ // Check it if url is relative (no scheme).
+ checkbox.checked = url.length > 0 && !urlScheme;
+
+ // Now do checkbox enabling:
+ var enable = false;
+
+ var docUrl = GetDocumentBaseUrl();
+ var docScheme = GetScheme(docUrl);
+
+ if (url && docUrl && docScheme) {
+ if (urlScheme) {
+ // Url is absolute
+ // If we can make a relative URL, then enable must be true!
+ // (this lets the smarts of MakeRelativeUrl do all the hard work)
+ enable = GetScheme(MakeRelativeUrl(url)).length == 0;
+ } else if (url[0] == "#") {
+ // Url is relative
+ // Check if url is a named anchor
+ // but document doesn't have a filename
+ // (it's probably "index.html" or "index.htm",
+ // but we don't want to allow a malformed URL)
+ var docFilename = GetFilename(docUrl);
+ enable = docFilename.length > 0;
+ } else {
+ // Any other url is assumed
+ // to be ok to try to make absolute
+ enable = true;
+ }
+ }
+
+ SetElementEnabled(checkbox, enable);
+}
+
+// oncommand handler for the Relativize checkbox in EditorOverlay.xhtml
+function MakeInputValueRelativeOrAbsolute(checkbox) {
+ var input = document.getElementById(checkbox.getAttribute("for"));
+ if (!input) {
+ return;
+ }
+
+ var docUrl = GetDocumentBaseUrl();
+ if (!docUrl) {
+ // Checkbox should be disabled if not saved,
+ // but keep this error message in case we change that
+ Services.prompt.alert(window, "", GetString("SaveToUseRelativeUrl"));
+ window.focus();
+ } else {
+ // Note that "checked" is opposite of its last state,
+ // which determines what we want to do here
+ if (checkbox.checked) {
+ input.value = MakeRelativeUrl(input.value);
+ } else {
+ input.value = MakeAbsoluteUrl(input.value);
+ }
+
+ // Reset checkbox to reflect url state
+ SetRelativeCheckbox(checkbox);
+ }
+}
+
+var IsBlockParent = [
+ "applet",
+ "blockquote",
+ "body",
+ "center",
+ "dd",
+ "div",
+ "form",
+ "li",
+ "noscript",
+ "object",
+ "td",
+ "th",
+];
+
+var NotAnInlineParent = [
+ "col",
+ "colgroup",
+ "dl",
+ "dir",
+ "menu",
+ "ol",
+ "table",
+ "tbody",
+ "tfoot",
+ "thead",
+ "tr",
+ "ul",
+];
+
+function nodeIsBreak(editor, node) {
+ return !node || node.localName == "br" || editor.nodeIsBlock(node);
+}
+
+function InsertElementAroundSelection(element) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+
+ try {
+ // First get the selection as a single range
+ var range, start, end, offset;
+ var count = editor.selection.rangeCount;
+ if (count == 1) {
+ range = editor.selection.getRangeAt(0).cloneRange();
+ } else {
+ range = editor.document.createRange();
+ start = editor.selection.getRangeAt(0);
+ range.setStart(start.startContainer, start.startOffset);
+ end = editor.selection.getRangeAt(--count);
+ range.setEnd(end.endContainer, end.endOffset);
+ }
+
+ // Flatten the selection to child nodes of the common ancestor
+ while (range.startContainer != range.commonAncestorContainer) {
+ range.setStartBefore(range.startContainer);
+ }
+ while (range.endContainer != range.commonAncestorContainer) {
+ range.setEndAfter(range.endContainer);
+ }
+
+ if (editor.nodeIsBlock(element)) {
+ // Block element parent must be a valid block
+ while (!IsBlockParent.includes(range.commonAncestorContainer.localName)) {
+ range.selectNode(range.commonAncestorContainer);
+ }
+ } else {
+ if (!nodeIsBreak(editor, range.commonAncestorContainer)) {
+ // Fail if we're not inserting a block (use setInlineProperty instead)
+ return false;
+ }
+ if (NotAnInlineParent.includes(range.commonAncestorContainer.localName)) {
+ // Inline element parent must not be an invalid block
+ do {
+ range.selectNode(range.commonAncestorContainer);
+ } while (
+ NotAnInlineParent.includes(range.commonAncestorContainer.localName)
+ );
+ } else {
+ // Further insert block check
+ for (var i = range.startOffset; ; i++) {
+ if (i == range.endOffset) {
+ return false;
+ }
+ if (
+ nodeIsBreak(editor, range.commonAncestorContainer.childNodes[i])
+ ) {
+ break;
+ }
+ }
+ }
+ }
+
+ // The range may be contained by body text, which should all be selected.
+ offset = range.startOffset;
+ start = range.startContainer.childNodes[offset];
+ if (!nodeIsBreak(editor, start)) {
+ while (!nodeIsBreak(editor, start.previousSibling)) {
+ start = start.previousSibling;
+ offset--;
+ }
+ }
+ end = range.endContainer.childNodes[range.endOffset];
+ if (end && !nodeIsBreak(editor, end.previousSibling)) {
+ while (!nodeIsBreak(editor, end)) {
+ end = end.nextSibling;
+ }
+ }
+
+ // Now insert the node
+ // XXX Here tried to prevent updating Selection with unknown 4th argument,
+ // but nsIEditor.setShouldTxnSetSelection is not used for that.
+ editor.insertNode(element, range.commonAncestorContainer, offset);
+ offset = element.childNodes.length;
+ if (!editor.nodeIsBlock(element)) {
+ editor.setShouldTxnSetSelection(false);
+ }
+
+ // Move all the old child nodes to the element
+ var empty = true;
+ while (start != end) {
+ var next = start.nextSibling;
+ editor.deleteNode(start);
+ editor.insertNode(start, element, element.childNodes.length);
+ empty = false;
+ start = next;
+ }
+ if (!editor.nodeIsBlock(element)) {
+ editor.setShouldTxnSetSelection(true);
+ } else {
+ // Also move a trailing <br>
+ if (start && start.localName == "br") {
+ editor.deleteNode(start);
+ editor.insertNode(start, element, element.childNodes.length);
+ empty = false;
+ }
+ // Still nothing? Insert a <br> so the node is not empty
+ if (empty) {
+ editor.insertNode(
+ editor.createElementWithDefaults("br"),
+ element,
+ element.childNodes.length
+ );
+ }
+
+ // Hack to set the selection just inside the element
+ editor.insertNode(editor.document.createTextNode(""), element, offset);
+ }
+ } finally {
+ editor.endTransaction();
+ }
+
+ return true;
+}
+
+function nodeIsBlank(node) {
+ return node && node.nodeType == Node.TEXT_NODE && !/\S/.test(node.data);
+}
+
+function nodeBeginsBlock(editor, node) {
+ while (nodeIsBlank(node)) {
+ node = node.nextSibling;
+ }
+ return nodeIsBreak(editor, node);
+}
+
+function nodeEndsBlock(editor, node) {
+ while (nodeIsBlank(node)) {
+ node = node.previousSibling;
+ }
+ return nodeIsBreak(editor, node);
+}
+
+// C++ function isn't exposed to JS :-(
+function RemoveBlockContainer(element) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+
+ try {
+ var range = editor.document.createRange();
+ range.selectNode(element);
+ var offset = range.startOffset;
+ var parent = element.parentNode;
+
+ // May need to insert a break after the removed element
+ if (
+ !nodeBeginsBlock(editor, element.nextSibling) &&
+ !nodeEndsBlock(editor, element.lastChild)
+ ) {
+ editor.insertNode(
+ editor.createElementWithDefaults("br"),
+ parent,
+ range.endOffset
+ );
+ }
+
+ // May need to insert a break before the removed element, or if it was empty
+ if (
+ !nodeEndsBlock(editor, element.previousSibling) &&
+ !nodeBeginsBlock(editor, element.firstChild || element.nextSibling)
+ ) {
+ editor.insertNode(
+ editor.createElementWithDefaults("br"),
+ parent,
+ offset++
+ );
+ }
+
+ // Now remove the element
+ editor.deleteNode(element);
+
+ // Need to copy the contained nodes?
+ for (var i = 0; i < element.childNodes.length; i++) {
+ editor.insertNode(
+ element.childNodes[i].cloneNode(true),
+ parent,
+ offset++
+ );
+ }
+ } finally {
+ editor.endTransaction();
+ }
+}
+
+// C++ function isn't exposed to JS :-(
+function RemoveContainer(element) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+
+ try {
+ var range = editor.document.createRange();
+ var parent = element.parentNode;
+ // Allow for automatic joining of text nodes
+ // so we can't delete the container yet
+ // so we need to copy the contained nodes
+ for (var i = 0; i < element.childNodes.length; i++) {
+ range.selectNode(element);
+ editor.insertNode(
+ element.childNodes[i].cloneNode(true),
+ parent,
+ range.startOffset
+ );
+ }
+ // Now remove the element
+ editor.deleteNode(element);
+ } finally {
+ editor.endTransaction();
+ }
+}
+
+function FillLinkMenulist(linkMenulist, headingsArray) {
+ var menupopup = linkMenulist.firstChild;
+ var editor = GetCurrentEditor();
+ try {
+ var treeWalker = editor.document.createTreeWalker(
+ editor.document,
+ 1,
+ null,
+ true
+ );
+ var headingList = [];
+ var anchorList = []; // for sorting
+ var anchorMap = {}; // for weeding out duplicates and making heading anchors unique
+ var anchor;
+ var i;
+ for (
+ var element = treeWalker.nextNode();
+ element;
+ element = treeWalker.nextNode()
+ ) {
+ // grab headings
+ // Skip headings that already have a named anchor as their first child
+ // (this may miss nearby anchors, but at least we don't insert another
+ // under the same heading)
+ if (
+ element instanceof HTMLHeadingElement &&
+ element.textContent &&
+ !(
+ element.firstChild instanceof HTMLAnchorElement &&
+ element.firstChild.name
+ )
+ ) {
+ headingList.push(element);
+ }
+
+ // grab named anchors
+ if (element instanceof HTMLAnchorElement && element.name) {
+ anchor = "#" + element.name;
+ if (!(anchor in anchorMap)) {
+ anchorList.push({ anchor, sortkey: anchor.toLowerCase() });
+ anchorMap[anchor] = true;
+ }
+ }
+
+ // grab IDs
+ if (element.id) {
+ anchor = "#" + element.id;
+ if (!(anchor in anchorMap)) {
+ anchorList.push({ anchor, sortkey: anchor.toLowerCase() });
+ anchorMap[anchor] = true;
+ }
+ }
+ }
+ // add anchor for headings
+ for (i = 0; i < headingList.length; i++) {
+ var heading = headingList[i];
+
+ // Use just first 40 characters, don't add "...",
+ // and replace whitespace with "_" and strip non-word characters
+ anchor =
+ "#" +
+ ConvertToCDATAString(
+ TruncateStringAtWordEnd(heading.textContent, 40, false)
+ );
+
+ // Append "_" to any name already in the list
+ while (anchor in anchorMap) {
+ anchor += "_";
+ }
+ anchorList.push({ anchor, sortkey: anchor.toLowerCase() });
+ anchorMap[anchor] = true;
+
+ // Save nodes in an array so we can create anchor node under it later
+ headingsArray[anchor] = heading;
+ }
+ if (anchorList.length) {
+ // case insensitive sort
+ anchorList.sort((a, b) => {
+ if (a.sortkey < b.sortkey) {
+ return -1;
+ }
+ if (a.sortkey > b.sortkey) {
+ return 1;
+ }
+ return 0;
+ });
+
+ for (i = 0; i < anchorList.length; i++) {
+ createMenuItem(menupopup, anchorList[i].anchor);
+ }
+ } else {
+ // Don't bother with named anchors in Mail.
+ if (editor && editor.flags & Ci.nsIEditor.eEditorMailMask) {
+ menupopup.remove();
+ linkMenulist.removeAttribute("enablehistory");
+ return;
+ }
+ var item = createMenuItem(
+ menupopup,
+ GetString("NoNamedAnchorsOrHeadings")
+ );
+ item.setAttribute("disabled", "true");
+ }
+ } catch (e) {}
+}
+
+function createMenuItem(aMenuPopup, aLabel) {
+ var menuitem = document.createXULElement("menuitem");
+ menuitem.setAttribute("label", aLabel);
+ aMenuPopup.appendChild(menuitem);
+ return menuitem;
+}
+
+// Shared by Image and Link dialogs for the "Choose" button for links
+function chooseLinkFile() {
+ GetLocalFileURL("html, img").then(fileURL => {
+ // Always try to relativize local file URLs
+ if (gHaveDocumentUrl) {
+ fileURL = MakeRelativeUrl(fileURL);
+ }
+
+ gDialog.hrefInput.value = fileURL;
+
+ // Do stuff specific to a particular dialog
+ // (This is defined separately in Image and Link dialogs)
+ ChangeLinkLocation();
+ });
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdDialogTemplate.js b/comm/suite/editor/components/dialogs/content/EdDialogTemplate.js
new file mode 100644
index 0000000000..ee74e8c871
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdDialogTemplate.js
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// Cancel() is in EdDialogCommon.js
+var insertNew = true;
+var tagname = "TAG NAME";
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ if (!GetCurrentEditor()) {
+ window.close();
+ return;
+ }
+ // gDialog is declared in EdDialogCommon.js
+ // Set commonly-used widgets like this:
+ gDialog.fooButton = document.getElementById("fooButton");
+
+ InitDialog();
+
+ // Set window location relative to parent window (based on persisted attributes)
+ SetWindowLocation();
+
+ // Set focus to first widget in dialog, e.g.:
+ SetTextboxFocus(gDialog.fooButton);
+}
+
+function InitDialog() {
+ // Initialize all dialog widgets here,
+ // e.g., get attributes from an element for property dialog
+}
+
+function onAccept() {
+ // Validate all user data and set attributes and possibly insert new element here
+ // If there's an error the user must correct, return false to keep dialog open.
+
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdDialogTemplate.xhtml b/comm/suite/editor/components/dialogs/content/EdDialogTemplate.xhtml
new file mode 100644
index 0000000000..8f76af4654
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdDialogTemplate.xhtml
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<?xul-overlay href="chrome://editor/content/EdDialogOverlay.xhtml"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/Ed?????????.dtd">
+<!-- dialog containing a control requiring initial setup -->
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/Ed?????.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdDictionary.js b/comm/suite/editor/components/dialogs/content/EdDictionary.js
new file mode 100644
index 0000000000..892e92dd1a
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdDictionary.js
@@ -0,0 +1,164 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var gSpellChecker;
+var gWordToAdd;
+
+function Startup() {
+ if (!GetCurrentEditor()) {
+ window.close();
+ return;
+ }
+ // Get the SpellChecker shell
+ if ("gSpellChecker" in window.opener && window.opener.gSpellChecker) {
+ gSpellChecker = window.opener.gSpellChecker;
+ }
+
+ if (!gSpellChecker) {
+ dump("SpellChecker not found!!!\n");
+ window.close();
+ return;
+ }
+ // The word to add word is passed as the 2nd extra parameter in window.openDialog()
+ gWordToAdd = window.arguments[1];
+
+ gDialog.WordInput = document.getElementById("WordInput");
+ gDialog.DictionaryList = document.getElementById("DictionaryList");
+
+ gDialog.WordInput.value = gWordToAdd;
+ FillDictionaryList();
+
+ // Select the supplied word if it is already in the list
+ SelectWordToAddInList();
+ SetTextboxFocus(gDialog.WordInput);
+}
+
+function ValidateWordToAdd() {
+ gWordToAdd = TrimString(gDialog.WordInput.value);
+ if (gWordToAdd.length > 0) {
+ return true;
+ }
+ return false;
+}
+
+function SelectWordToAddInList() {
+ for (var i = 0; i < gDialog.DictionaryList.getRowCount(); i++) {
+ var wordInList = gDialog.DictionaryList.getItemAtIndex(i);
+ if (wordInList && gWordToAdd == wordInList.label) {
+ gDialog.DictionaryList.selectedIndex = i;
+ break;
+ }
+ }
+}
+
+function AddWord() {
+ if (ValidateWordToAdd()) {
+ try {
+ gSpellChecker.AddWordToDictionary(gWordToAdd);
+ } catch (e) {
+ dump(
+ "Exception occurred in gSpellChecker.AddWordToDictionary\nWord to add probably already existed\n"
+ );
+ }
+
+ // Rebuild the dialog list
+ FillDictionaryList();
+
+ SelectWordToAddInList();
+ gDialog.WordInput.value = "";
+ }
+}
+
+function ReplaceWord() {
+ if (ValidateWordToAdd()) {
+ var selItem = gDialog.DictionaryList.selectedItem;
+ if (selItem) {
+ try {
+ gSpellChecker.RemoveWordFromDictionary(selItem.label);
+ } catch (e) {}
+
+ try {
+ // Add to the dictionary list
+ gSpellChecker.AddWordToDictionary(gWordToAdd);
+
+ // Just change the text on the selected item instead of rebuilding the list.
+ // The items are richlist items, so the label sits in the first child.
+ selItem.firstChild.setAttribute("value", gWordToAdd);
+ } catch (e) {
+ // Rebuild list and select the word - it was probably already in the list
+ dump("Exception occurred adding word in ReplaceWord\n");
+ FillDictionaryList();
+ SelectWordToAddInList();
+ }
+ }
+ }
+}
+
+function RemoveWord() {
+ var selIndex = gDialog.DictionaryList.selectedIndex;
+ if (selIndex >= 0) {
+ var word = gDialog.DictionaryList.selectedItem.label;
+
+ // Remove word from list
+ gDialog.DictionaryList.selectedItem.remove();
+
+ // Remove from dictionary
+ try {
+ // Not working: BUG 43348
+ gSpellChecker.RemoveWordFromDictionary(word);
+ } catch (e) {
+ dump("Failed to remove word from dictionary\n");
+ }
+
+ ResetSelectedItem(selIndex);
+ }
+}
+
+function FillDictionaryList() {
+ var selIndex = gDialog.DictionaryList.selectedIndex;
+
+ // Clear the current contents of the list
+ ClearListbox(gDialog.DictionaryList);
+
+ // Get the list from the spell checker
+ gSpellChecker.GetPersonalDictionary();
+
+ var haveList = false;
+
+ // Get words until an empty string is returned
+ do {
+ var word = gSpellChecker.GetPersonalDictionaryWord();
+ if (word != "") {
+ gDialog.DictionaryList.appendItem(word, "");
+ haveList = true;
+ }
+ } while (word != "");
+
+ // XXX: BUG 74467: If list is empty, it doesn't layout to full height correctly
+ // (ignores "rows" attribute) (bug is latered, so we are fixing here for now)
+ if (!haveList) {
+ gDialog.DictionaryList.appendItem("", "");
+ }
+
+ ResetSelectedItem(selIndex);
+}
+
+function ResetSelectedItem(index) {
+ var lastIndex = gDialog.DictionaryList.getRowCount() - 1;
+ if (index > lastIndex) {
+ index = lastIndex;
+ }
+
+ // If we didn't have a selected item,
+ // set it to the first item
+ if (index == -1 && lastIndex >= 0) {
+ index = 0;
+ }
+
+ gDialog.DictionaryList.selectedIndex = index;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdDictionary.xhtml b/comm/suite/editor/components/dialogs/content/EdDictionary.xhtml
new file mode 100644
index 0000000000..cebbe1c192
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdDictionary.xhtml
@@ -0,0 +1,59 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorPersonalDictionary.dtd">
+<dialog buttons="cancel" title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ persist="screenX screenY"
+ onload="Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdDictionary.js"/>
+
+ <grid>
+ <columns><column style="width: 15em" flex="1"/><column flex="1"/></columns>
+ <rows>
+ <row>
+ <label value="&wordEditField.label;"
+ control="WordInput"
+ accesskey="&wordEditField.accessKey;"/>
+ <spacer/>
+ </row>
+ <row>
+ <textbox id="WordInput" flex="1"/>
+ <button id="AddWord" oncommand="AddWord()" label="&AddButton.label;"
+ accesskey="&AddButton.accessKey;"/>
+ </row>
+ <row>
+ <label value="&DictionaryList.label;"
+ control="DictionaryList"
+ accesskey="&DictionaryList.accessKey;"/>
+ <spacer/>
+ </row>
+ <row>
+ <richlistbox id="DictionaryList"
+ class="theme-listbox"
+ flex="1"
+ height="150px"/>
+ <vbox flex="1">
+ <button id="ReplaceWord" oncommand="ReplaceWord()" label="&ReplaceButton.label;"
+ accesskey="&ReplaceButton.accessKey;"/>
+ <spacer class="spacer"/>
+ <button id="RemoveWord" oncommand="RemoveWord()" label="&RemoveButton.label;"
+ accesskey="&RemoveButton.accessKey;"/>
+ <spacer class="spacer"/>
+ <spacer flex="1"/>
+ <button dlgtype="cancel" class="exit-dialog" id="close" label="&CloseButton.label;"
+ default="true" oncommand="onClose();"
+ accesskey="&CloseButton.accessKey;"/>
+ </vbox>
+ </row>
+ </rows>
+ </grid>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdFieldSetProps.js b/comm/suite/editor/components/dialogs/content/EdFieldSetProps.js
new file mode 100644
index 0000000000..1799a2b28f
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdFieldSetProps.js
@@ -0,0 +1,196 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var insertNew;
+var fieldsetElement;
+var newLegend;
+var legendElement;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ dump("Failed to get active editor!\n");
+ window.close();
+ return;
+ }
+
+ gDialog.editText = document.getElementById("EditText");
+ gDialog.legendText = document.getElementById("LegendText");
+ gDialog.legendAlign = document.getElementById("LegendAlign");
+ gDialog.RemoveFieldSet = document.getElementById("RemoveFieldSet");
+
+ // Get a single selected field set element
+ const kTagName = "fieldset";
+ try {
+ // Find a selected fieldset, or if one is at start or end of selection.
+ fieldsetElement = editor.getSelectedElement(kTagName);
+ if (!fieldsetElement) {
+ fieldsetElement = editor.getElementOrParentByTagName(
+ kTagName,
+ editor.selection.anchorNode
+ );
+ }
+ if (!fieldsetElement) {
+ fieldsetElement = editor.getElementOrParentByTagName(
+ kTagName,
+ editor.selection.focusNode
+ );
+ }
+ } catch (e) {}
+
+ if (fieldsetElement) {
+ // We found an element and don't need to insert one
+ insertNew = false;
+ } else {
+ insertNew = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+ try {
+ fieldsetElement = editor.createElementWithDefaults(kTagName);
+ } catch (e) {}
+
+ if (!fieldsetElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+ // Hide button removing existing fieldset
+ gDialog.RemoveFieldSet.hidden = true;
+ }
+
+ legendElement = fieldsetElement.querySelector("legend");
+ if (legendElement) {
+ newLegend = false;
+ var range = editor.document.createRange();
+ range.selectNode(legendElement);
+ gDialog.legendText.value = range.toString();
+ if (legendElement.innerHTML.includes("<")) {
+ gDialog.editText.checked = false;
+ gDialog.editText.disabled = false;
+ gDialog.legendText.disabled = true;
+ gDialog.editText.addEventListener(
+ "command",
+ () =>
+ Services.prompt.alert(
+ window,
+ GetString("Alert"),
+ GetString("EditTextWarning")
+ ),
+ { capture: false, once: true }
+ );
+ gDialog.RemoveFieldSet.focus();
+ } else {
+ SetTextboxFocus(gDialog.legendText);
+ }
+ } else {
+ newLegend = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+
+ legendElement = editor.createElementWithDefaults("legend");
+ if (!legendElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+ SetTextboxFocus(gDialog.legendText);
+ }
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = legendElement.cloneNode(false);
+
+ InitDialog();
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ gDialog.legendAlign.value = GetHTMLOrCSSStyleValue(
+ globalElement,
+ "align",
+ "caption-side"
+ );
+}
+
+function RemoveFieldSet() {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+ try {
+ if (!newLegend) {
+ editor.deleteNode(legendElement);
+ }
+ RemoveBlockContainer(fieldsetElement);
+ } finally {
+ editor.endTransaction();
+ }
+ SaveWindowLocation();
+ window.close();
+}
+
+function ValidateData() {
+ if (gDialog.legendAlign.value) {
+ globalElement.setAttribute("align", gDialog.legendAlign.value);
+ } else {
+ globalElement.removeAttribute("align");
+ }
+ return true;
+}
+
+function onAccept() {
+ // All values are valid - copy to actual element in doc
+ ValidateData();
+
+ var editor = GetCurrentEditor();
+
+ editor.beginTransaction();
+
+ try {
+ editor.cloneAttributes(legendElement, globalElement);
+
+ if (insertNew) {
+ if (gDialog.legendText.value) {
+ fieldsetElement.appendChild(legendElement);
+ legendElement.appendChild(
+ editor.document.createTextNode(gDialog.legendText.value)
+ );
+ }
+ InsertElementAroundSelection(fieldsetElement);
+ } else if (gDialog.editText.checked) {
+ editor.setShouldTxnSetSelection(false);
+
+ if (gDialog.legendText.value) {
+ if (newLegend) {
+ editor.insertNode(legendElement, fieldsetElement, 0);
+ } else {
+ while (legendElement.firstChild) {
+ editor.deleteNode(legendElement.lastChild);
+ }
+ }
+ editor.insertNode(
+ editor.document.createTextNode(gDialog.legendText.value),
+ legendElement,
+ 0
+ );
+ } else if (!newLegend) {
+ editor.deleteNode(legendElement);
+ }
+
+ editor.setShouldTxnSetSelection(true);
+ }
+ } finally {
+ editor.endTransaction();
+ }
+
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdFieldSetProps.xhtml b/comm/suite/editor/components/dialogs/content/EdFieldSetProps.xhtml
new file mode 100644
index 0000000000..0d67fad276
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdFieldSetProps.xhtml
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edFieldSetProperties SYSTEM "chrome://editor/locale/EditorFieldSetProperties.dtd">
+%edFieldSetProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ buttons="accept,cancel">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdFieldSetProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header" accesskey="&Legend.accesskey;">&Legend.label;</label>
+ </hbox>
+ <grid><columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <checkbox id="EditText" label="&EditLegendText.label;" accesskey="&EditLegendText.accesskey;" checked="true" disabled="true"
+ oncommand="gDialog.legendText.disabled = !gDialog.editText.checked;"/>
+ <textbox id="LegendText" accesskey="&Legend.accesskey;"/>
+ </row>
+ <row align="center">
+ <label control="LegendAlign" value="&LegendAlign.label;" accesskey="&LegendAlign.accesskey;"/>
+ <menulist id="LegendAlign">
+ <menupopup>
+ <menuitem label="&AlignDefault.label;"/>
+ <menuitem label="&AlignLeft.label;" value="left"/>
+ <menuitem label="&AlignCenter.label;" value="center"/>
+ <menuitem label="&AlignRight.label;" value="right"/>
+ </menupopup>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <!-- from EdDialogOverlay -->
+ <hbox flex="1" style="margin-top: 0.2em">
+ <button id="RemoveFieldSet" label="&RemoveFieldSet.label;" accesskey="&RemoveFieldSet.accesskey;" oncommand="RemoveFieldSet();"/>
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator class="groove"/>
+
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdFormProps.js b/comm/suite/editor/components/dialogs/content/EdFormProps.js
new file mode 100644
index 0000000000..9391fa4316
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdFormProps.js
@@ -0,0 +1,136 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var gForm;
+var insertNew;
+var formElement;
+var formActionWarning;
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ dump("Failed to get active editor!\n");
+ window.close();
+ return;
+ }
+
+ gForm = {
+ Name: document.getElementById("FormName"),
+ Action: document.getElementById("FormAction"),
+ Method: document.getElementById("FormMethod"),
+ EncType: document.getElementById("FormEncType"),
+ Target: document.getElementById("FormTarget"),
+ };
+ gDialog.MoreSection = document.getElementById("MoreSection");
+ gDialog.MoreFewerButton = document.getElementById("MoreFewerButton");
+ gDialog.RemoveForm = document.getElementById("RemoveForm");
+
+ // Get a single selected form element
+ const kTagName = "form";
+ try {
+ formElement = editor.getSelectedElement(kTagName);
+ if (!formElement) {
+ formElement = editor.getElementOrParentByTagName(
+ kTagName,
+ editor.selection.anchorNode
+ );
+ }
+ if (!formElement) {
+ formElement = editor.getElementOrParentByTagName(
+ kTagName,
+ editor.selection.focusNode
+ );
+ }
+ } catch (e) {}
+
+ if (formElement) {
+ // We found an element and don't need to insert one
+ insertNew = false;
+ formActionWarning = formElement.hasAttribute("action");
+ } else {
+ insertNew = true;
+ formActionWarning = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+ try {
+ formElement = editor.createElementWithDefaults(kTagName);
+ } catch (e) {}
+
+ if (!formElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+ // Hide button removing existing form
+ gDialog.RemoveForm.hidden = true;
+ }
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = formElement.cloneNode(false);
+
+ InitDialog();
+
+ InitMoreFewer();
+
+ SetTextboxFocus(gForm.Name);
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ for (var attribute in gForm) {
+ gForm[attribute].value = globalElement.getAttribute(attribute);
+ }
+}
+
+function RemoveForm() {
+ RemoveBlockContainer(formElement);
+ SaveWindowLocation();
+ window.close();
+}
+
+function ValidateData() {
+ for (var attribute in gForm) {
+ if (gForm[attribute].value) {
+ globalElement.setAttribute(attribute, gForm[attribute].value);
+ } else {
+ globalElement.removeAttribute(attribute);
+ }
+ }
+ return true;
+}
+
+function onAccept(event) {
+ if (formActionWarning && !gForm.Action.value) {
+ Services.prompt.alert(
+ window,
+ GetString("Alert"),
+ GetString("NoFormAction")
+ );
+ gForm.Action.focus();
+ formActionWarning = false;
+ event.preventDefault();
+ return;
+ }
+ // All values are valid - copy to actual element in doc or
+ // element created to insert
+ ValidateData();
+
+ var editor = GetCurrentEditor();
+
+ editor.cloneAttributes(formElement, globalElement);
+
+ if (insertNew) {
+ InsertElementAroundSelection(formElement);
+ }
+
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdFormProps.xhtml b/comm/suite/editor/components/dialogs/content/EdFormProps.xhtml
new file mode 100644
index 0000000000..275daef785
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdFormProps.xhtml
@@ -0,0 +1,98 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/menulist.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edFormProperties SYSTEM "chrome://editor/locale/EditorFormProperties.dtd">
+%edFormProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ buttons="accept,cancel">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdFormProps.js"/>
+
+ <script src="chrome://messenger/content/customElements.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&Settings.label;</label>
+ </hbox>
+ <grid><columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label control="FormName" value="&FormName.label;" accesskey="&FormName.accesskey;"/>
+ <textbox id="FormName"/>
+ </row>
+ <row align="center">
+ <label control="FormAction" value="&FormAction.label;" accesskey="&FormAction.accesskey;"/>
+ <textbox id="FormAction"/>
+ </row>
+ <row align="center">
+ <label control="FormMethod" value="&FormMethod.label;" accesskey="&FormMethod.accesskey;"/>
+ <hbox>
+ <menulist is="menulist-editable" id="FormMethod" editable="true" autoSelectMenuitem="true">
+ <menupopup>
+ <menuitem label="GET"/>
+ <menuitem label="POST"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ </row>
+ <hbox>
+ <button id="MoreFewerButton" oncommand="onMoreFewer();" persist="more"/>
+ </hbox>
+ <rows id="MoreSection">
+ <row align="center">
+ <label control="FormEncType" value="&FormEncType.label;" accesskey="&FormEncType.accesskey;"/>
+ <menulist is="menulist-editable" id="FormEncType" editable="true" autoSelectMenuitem="true">
+ <menupopup>
+ <menuitem label="application/x-www-form-urlencoded"/>
+ <menuitem label="multipart/form-data"/>
+ <menuitem label="text/plain"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row align="center">
+ <label control="FormTarget" value="&FormTarget.label;" accesskey="&FormTarget.accesskey;"/>
+ <menulist is="menulist-editable" id="FormTarget" editable="true" autoSelectMenuitem="true">
+ <menupopup>
+ <menuitem label="_blank"/>
+ <menuitem label="_self"/>
+ <menuitem label="_parent"/>
+ <menuitem label="_top"/>
+ </menupopup>
+ </menulist>
+ </row>
+ </rows>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <!-- from EdDialogOverlay -->
+ <hbox flex="1" style="margin-top: 0.2em">
+ <button id="RemoveForm" label="&RemoveForm.label;" accesskey="&RemoveForm.accesskey;" oncommand="RemoveForm();"/>
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdHLineProps.js b/comm/suite/editor/components/dialogs/content/EdHLineProps.js
new file mode 100644
index 0000000000..eff9c06a41
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdHLineProps.js
@@ -0,0 +1,227 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var tagName = "hr";
+var gHLineElement;
+var width;
+var height;
+var align;
+var shading;
+const gMaxHRSize = 1000; // This is hard-coded in nsHTMLHRElement::StringToAttribute()
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+ try {
+ // Get the selected horizontal line
+ gHLineElement = editor.getSelectedElement(tagName);
+ } catch (e) {}
+
+ if (!gHLineElement) {
+ // We should never be here if not editing an existing HLine
+ window.close();
+ return;
+ }
+ gDialog.heightInput = document.getElementById("height");
+ gDialog.widthInput = document.getElementById("width");
+ gDialog.leftAlign = document.getElementById("leftAlign");
+ gDialog.centerAlign = document.getElementById("centerAlign");
+ gDialog.rightAlign = document.getElementById("rightAlign");
+ gDialog.alignGroup = gDialog.rightAlign.radioGroup;
+ gDialog.shading = document.getElementById("3dShading");
+ gDialog.pixelOrPercentMenulist = document.getElementById(
+ "pixelOrPercentMenulist"
+ );
+
+ // Make a copy to use for AdvancedEdit and onSaveDefault
+ globalElement = gHLineElement.cloneNode(false);
+
+ // Initialize control values based on existing attributes
+ InitDialog();
+
+ // SET FOCUS TO FIRST CONTROL
+ SetTextboxFocus(gDialog.widthInput);
+
+ // Resize window
+ window.sizeToContent();
+
+ SetWindowLocation();
+}
+
+// Set dialog widgets with attribute data
+// We get them from globalElement copy so this can be used
+// by AdvancedEdit(), which is shared by all property dialogs
+function InitDialog() {
+ // Just to be confusing, "size" is used instead of height because it does
+ // not accept % values, only pixels
+ var height = GetHTMLOrCSSStyleValue(globalElement, "size", "height");
+ if (height.includes("px")) {
+ height = height.substr(0, height.indexOf("px"));
+ }
+ if (!height) {
+ height = 2; // Default value
+ }
+
+ // We will use "height" here and in UI
+ gDialog.heightInput.value = height;
+
+ // Get the width attribute of the element, stripping out "%"
+ // This sets contents of menulist (adds pixel and percent menuitems elements)
+ gDialog.widthInput.value = InitPixelOrPercentMenulist(
+ globalElement,
+ gHLineElement,
+ "width",
+ "pixelOrPercentMenulist"
+ );
+
+ var marginLeft = GetHTMLOrCSSStyleValue(
+ globalElement,
+ "align",
+ "margin-left"
+ ).toLowerCase();
+ var marginRight = GetHTMLOrCSSStyleValue(
+ globalElement,
+ "align",
+ "margin-right"
+ ).toLowerCase();
+ align = marginLeft + " " + marginRight;
+ gDialog.leftAlign.checked = align == "left left" || align == "0px auto";
+ gDialog.centerAlign.checked =
+ align == "center center" || align == "auto auto" || align == " ";
+ gDialog.rightAlign.checked = align == "right right" || align == "auto 0px";
+
+ if (gDialog.centerAlign.checked) {
+ gDialog.alignGroup.selectedItem = gDialog.centerAlign;
+ } else if (gDialog.rightAlign.checked) {
+ gDialog.alignGroup.selectedItem = gDialog.rightAlign;
+ } else {
+ gDialog.alignGroup.selectedItem = gDialog.leftAlign;
+ }
+
+ gDialog.shading.checked = !globalElement.hasAttribute("noshade");
+}
+
+function onSaveDefault() {
+ // "false" means set attributes on the globalElement,
+ // not the real element being edited
+ if (ValidateData()) {
+ var alignInt;
+ if (align == "left") {
+ alignInt = 0;
+ } else if (align == "right") {
+ alignInt = 2;
+ } else {
+ alignInt = 1;
+ }
+ Services.prefs.setIntPref("editor.hrule.align", alignInt);
+
+ var percent;
+ var widthInt;
+ var heightInt;
+
+ if (width) {
+ if (width.includes("%")) {
+ percent = true;
+ widthInt = Number(width.substr(0, width.indexOf("%")));
+ } else {
+ percent = false;
+ widthInt = Number(width);
+ }
+ } else {
+ percent = true;
+ widthInt = Number(100);
+ }
+
+ heightInt = height ? Number(height) : 2;
+
+ Services.prefs.setIntPref("editor.hrule.width", widthInt);
+ Services.prefs.setBoolPref("editor.hrule.width_percent", percent);
+ Services.prefs.setIntPref("editor.hrule.height", heightInt);
+ Services.prefs.setBoolPref("editor.hrule.shading", shading);
+
+ // Write the prefs out NOW!
+ Services.prefs.savePrefFile(null);
+ }
+}
+
+// Get and validate data from widgets.
+// Set attributes on globalElement so they can be accessed by AdvancedEdit()
+function ValidateData() {
+ // Height is always pixels
+ height = ValidateNumber(
+ gDialog.heightInput,
+ null,
+ 1,
+ gMaxHRSize,
+ globalElement,
+ "size",
+ false
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ width = ValidateNumber(
+ gDialog.widthInput,
+ gDialog.pixelOrPercentMenulist,
+ 1,
+ gMaxPixels,
+ globalElement,
+ "width",
+ false
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ align = "left";
+ if (gDialog.centerAlign.selected) {
+ // Don't write out default attribute
+ align = "";
+ } else if (gDialog.rightAlign.selected) {
+ align = "right";
+ }
+ if (align) {
+ globalElement.setAttribute("align", align);
+ } else {
+ try {
+ GetCurrentEditor().removeAttributeOrEquivalent(
+ globalElement,
+ "align",
+ true
+ );
+ } catch (e) {}
+ }
+
+ if (gDialog.shading.checked) {
+ shading = true;
+ globalElement.removeAttribute("noshade");
+ } else {
+ shading = false;
+ globalElement.setAttribute("noshade", "noshade");
+ }
+ return true;
+}
+
+function onAccept(event) {
+ if (ValidateData()) {
+ // Copy attributes from the globalElement to the document element
+ try {
+ GetCurrentEditor().cloneAttributes(gHLineElement, globalElement);
+ } catch (e) {}
+ return;
+ }
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdHLineProps.xhtml b/comm/suite/editor/components/dialogs/content/EdHLineProps.xhtml
new file mode 100644
index 0000000000..fbcfa3b594
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdHLineProps.xhtml
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edHLineProperties SYSTEM "chrome://editor/locale/EditorHLineProperties.dtd">
+%edHLineProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <!--- Element-specific methods -->
+ <script src="chrome://editor/content/EdHLineProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&dimensionsBox.label;</label>
+ </hbox>
+ <grid>
+ <columns><column/><column/><column /></columns>
+ <rows>
+ <row align="center">
+ <label control="width"
+ value="&widthEditField.label;"
+ accesskey="&widthEditField.accessKey;"/>
+ <textbox class="narrow" id="width" flex="1" oninput="forceInteger('width')"/>
+ <menulist id="pixelOrPercentMenulist" />
+ <!-- menupopup and menuitems added by JS -->
+ </row>
+ <row align="center">
+ <label control="height"
+ value="&heightEditField.label;"
+ accesskey="&heightEditField.accessKey;"/>
+ <textbox class="narrow" id="height" oninput="forceInteger('height')"/>
+ <label value="&pixelsPopup.value;" />
+ </row>
+ </rows>
+ </grid>
+ <checkbox id="3dShading" label="&threeDShading.label;" accesskey="&threeDShading.accessKey;"/>
+ </groupbox>
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&alignmentBox.label;</label>
+ </hbox>
+ <radiogroup id="alignmentGroup" orient="horizontal">
+ <spacer class="spacer"/>
+ <radio id="leftAlign" label="&leftRadio.label;" accesskey="&leftRadio.accessKey;"/>
+ <radio id="centerAlign" label="&centerRadio.label;" accesskey="&centerRadio.accessKey;"/>
+ <radio id="rightAlign" label="&rightRadio.label;" accesskey="&rightRadio.accessKey;"/>
+ </radiogroup>
+ </groupbox>
+ <spacer class="spacer"/>
+ <hbox>
+ <button id="SaveDefault" label="&saveSettings.label;"
+ accesskey="&saveSettings.accessKey;"
+ oncommand="onSaveDefault()"
+ tooltiptext="&saveSettings.tooltip;" />
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdImageDialog.js b/comm/suite/editor/components/dialogs/content/EdImageDialog.js
new file mode 100644
index 0000000000..0e697dbd6f
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdImageDialog.js
@@ -0,0 +1,661 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ Note: We encourage non-empty alt text for images inserted into a page.
+ When there's no alt text, we always write 'alt=""' as the attribute, since "alt" is a required attribute.
+ We allow users to not have alt text by checking a "Don't use alterate text" radio button,
+ and we don't accept spaces as valid alt text. A space used to be required to avoid the error message
+ if user didn't enter alt text, but is unnecessary now that we no longer annoy the user
+ with the error dialog if alt="" is present on an img element.
+ We trim all spaces at the beginning and end of user's alt text
+*/
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var gInsertNewImage = true;
+var gDoAltTextError = false;
+var gConstrainOn = false;
+// Note used in current version, but these are set correctly
+// and could be used to reset width and height used for constrain ratio
+var gConstrainWidth = 0;
+var gConstrainHeight = 0;
+var imageElement;
+var gImageMap = 0;
+var gCanRemoveImageMap = false;
+var gRemoveImageMap = false;
+var gImageMapDisabled = false;
+var gActualWidth = "";
+var gActualHeight = "";
+var gOriginalSrc = "";
+var gTimerID;
+var gValidateTab;
+var gInsertNewIMap;
+
+// These must correspond to values in EditorDialog.css for each theme
+// (unfortunately, setting "style" attribute here doesn't work!)
+var gPreviewImageWidth = 80;
+var gPreviewImageHeight = 50;
+
+// dialog initialization code
+
+function ImageStartup() {
+ gDialog.tabBox = document.getElementById("TabBox");
+ gDialog.tabLocation = document.getElementById("imageLocationTab");
+ gDialog.tabDimensions = document.getElementById("imageDimensionsTab");
+ gDialog.tabBorder = document.getElementById("imageBorderTab");
+ gDialog.srcInput = document.getElementById("srcInput");
+ gDialog.titleInput = document.getElementById("titleInput");
+ gDialog.altTextInput = document.getElementById("altTextInput");
+ gDialog.altTextRadioGroup = document.getElementById("altTextRadioGroup");
+ gDialog.altTextRadio = document.getElementById("altTextRadio");
+ gDialog.noAltTextRadio = document.getElementById("noAltTextRadio");
+ gDialog.actualSizeRadio = document.getElementById("actualSizeRadio");
+ gDialog.constrainCheckbox = document.getElementById("constrainCheckbox");
+ gDialog.widthInput = document.getElementById("widthInput");
+ gDialog.heightInput = document.getElementById("heightInput");
+ gDialog.widthUnitsMenulist = document.getElementById("widthUnitsMenulist");
+ gDialog.heightUnitsMenulist = document.getElementById("heightUnitsMenulist");
+ gDialog.imagelrInput = document.getElementById("imageleftrightInput");
+ gDialog.imagetbInput = document.getElementById("imagetopbottomInput");
+ gDialog.border = document.getElementById("border");
+ gDialog.alignTypeSelect = document.getElementById("alignTypeSelect");
+ gDialog.ImageHolder = document.getElementById("preview-image-holder");
+ gDialog.PreviewWidth = document.getElementById("PreviewWidth");
+ gDialog.PreviewHeight = document.getElementById("PreviewHeight");
+ gDialog.PreviewSize = document.getElementById("PreviewSize");
+ gDialog.PreviewImage = null;
+ gDialog.OkButton = document.documentElement.getButton("accept");
+}
+
+// Set dialog widgets with attribute data
+// We get them from globalElement copy so this can be used
+// by AdvancedEdit(), which is shared by all property dialogs
+function InitImage() {
+ // Set the controls to the image's attributes
+ var src = globalElement.getAttribute("src");
+
+ // For image insertion the 'src' attribute is null.
+ if (src) {
+ // Shorten data URIs for display.
+ shortenImageData(src, gDialog.srcInput);
+ }
+
+ // Set "Relativize" checkbox according to current URL state
+ SetRelativeCheckbox();
+
+ // Force loading of image from its source and show preview image
+ LoadPreviewImage();
+
+ gDialog.titleInput.value = globalElement.getAttribute("title");
+
+ var hasAltText = globalElement.hasAttribute("alt");
+ var altText = globalElement.getAttribute("alt");
+ gDialog.altTextInput.value = altText;
+ if (altText || (!hasAltText && globalElement.hasAttribute("src"))) {
+ gDialog.altTextRadioGroup.selectedItem = gDialog.altTextRadio;
+ } else if (hasAltText) {
+ gDialog.altTextRadioGroup.selectedItem = gDialog.noAltTextRadio;
+ }
+ SetAltTextDisabled(
+ gDialog.altTextRadioGroup.selectedItem == gDialog.noAltTextRadio
+ );
+
+ // setup the height and width widgets
+ var width = InitPixelOrPercentMenulist(
+ globalElement,
+ gInsertNewImage ? null : imageElement,
+ "width",
+ "widthUnitsMenulist",
+ gPixel
+ );
+ var height = InitPixelOrPercentMenulist(
+ globalElement,
+ gInsertNewImage ? null : imageElement,
+ "height",
+ "heightUnitsMenulist",
+ gPixel
+ );
+
+ // Set actual radio button if both set values are the same as actual
+ SetSizeWidgets(width, height);
+
+ gDialog.widthInput.value = gConstrainWidth = width || gActualWidth || "";
+ gDialog.heightInput.value = gConstrainHeight = height || gActualHeight || "";
+
+ // set spacing editfields
+ gDialog.imagelrInput.value = globalElement.getAttribute("hspace");
+ gDialog.imagetbInput.value = globalElement.getAttribute("vspace");
+
+ // dialog.border.value = globalElement.getAttribute("border");
+ var bv = GetHTMLOrCSSStyleValue(globalElement, "border", "border-top-width");
+ if (bv.includes("px")) {
+ // Strip out the px
+ bv = bv.substr(0, bv.indexOf("px"));
+ } else if (bv == "thin") {
+ bv = "1";
+ } else if (bv == "medium") {
+ bv = "3";
+ } else if (bv == "thick") {
+ bv = "5";
+ }
+ gDialog.border.value = bv;
+
+ // Get alignment setting
+ var align = globalElement.getAttribute("align");
+ if (align) {
+ align = align.toLowerCase();
+ }
+
+ switch (align) {
+ case "top":
+ case "middle":
+ case "right":
+ case "left":
+ gDialog.alignTypeSelect.value = align;
+ break;
+ default:
+ // Default or "bottom"
+ gDialog.alignTypeSelect.value = "bottom";
+ }
+
+ // Get image map for image
+ gImageMap = GetImageMap();
+
+ doOverallEnabling();
+ doDimensionEnabling();
+}
+
+function SetSizeWidgets(width, height) {
+ if (
+ !(width || height) ||
+ (gActualWidth &&
+ gActualHeight &&
+ width == gActualWidth &&
+ height == gActualHeight)
+ ) {
+ gDialog.actualSizeRadio.radioGroup.selectedItem = gDialog.actualSizeRadio;
+ }
+
+ if (!gDialog.actualSizeRadio.selected) {
+ // Decide if user's sizes are in the same ratio as actual sizes
+ if (gActualWidth && gActualHeight) {
+ if (gActualWidth > gActualHeight) {
+ gDialog.constrainCheckbox.checked =
+ Math.round((gActualHeight * width) / gActualWidth) == height;
+ } else {
+ gDialog.constrainCheckbox.checked =
+ Math.round((gActualWidth * height) / gActualHeight) == width;
+ }
+ }
+ }
+}
+
+// Disable alt text input when "Don't use alt" radio is checked
+function SetAltTextDisabled(disable) {
+ gDialog.altTextInput.disabled = disable;
+}
+
+function GetImageMap() {
+ var usemap = globalElement.getAttribute("usemap");
+ if (usemap) {
+ gCanRemoveImageMap = true;
+ let mapname = usemap.substr(1);
+ try {
+ return GetCurrentEditor().document.querySelector(
+ '[name="' + mapname + '"]'
+ );
+ } catch (e) {}
+ } else {
+ gCanRemoveImageMap = false;
+ }
+
+ return null;
+}
+
+function chooseFile() {
+ if (gTimerID) {
+ clearTimeout(gTimerID);
+ }
+
+ // Put focus into the input field
+ SetTextboxFocus(gDialog.srcInput);
+
+ GetLocalFileURL("img").then(fileURL => {
+ // Always try to relativize local file URLs
+ if (gHaveDocumentUrl) {
+ fileURL = MakeRelativeUrl(fileURL);
+ }
+
+ gDialog.srcInput.value = fileURL;
+
+ SetRelativeCheckbox();
+ doOverallEnabling();
+ LoadPreviewImage();
+ });
+}
+
+function PreviewImageLoaded() {
+ if (gDialog.PreviewImage) {
+ // Image loading has completed -- we can get actual width
+ gActualWidth = gDialog.PreviewImage.naturalWidth;
+ gActualHeight = gDialog.PreviewImage.naturalHeight;
+
+ if (gActualWidth && gActualHeight) {
+ // Use actual size or scale to fit preview if either dimension is too large
+ var width = gActualWidth;
+ var height = gActualHeight;
+ if (gActualWidth > gPreviewImageWidth) {
+ width = gPreviewImageWidth;
+ height = gActualHeight * (gPreviewImageWidth / gActualWidth);
+ }
+ if (height > gPreviewImageHeight) {
+ height = gPreviewImageHeight;
+ width = gActualWidth * (gPreviewImageHeight / gActualHeight);
+ }
+ gDialog.PreviewImage.width = width;
+ gDialog.PreviewImage.height = height;
+
+ gDialog.PreviewWidth.setAttribute("value", gActualWidth);
+ gDialog.PreviewHeight.setAttribute("value", gActualHeight);
+
+ gDialog.PreviewSize.collapsed = false;
+ gDialog.ImageHolder.collapsed = false;
+
+ SetSizeWidgets(gDialog.widthInput.value, gDialog.heightInput.value);
+ }
+
+ if (gDialog.actualSizeRadio.selected) {
+ SetActualSize();
+ }
+ }
+}
+
+function LoadPreviewImage() {
+ gDialog.PreviewSize.collapsed = true;
+ // XXXbz workaround for bug 265416 / bug 266284
+ gDialog.ImageHolder.collapsed = true;
+
+ var imageSrc = TrimString(gDialog.srcInput.value);
+ if (!imageSrc) {
+ return;
+ }
+ if (isImageDataShortened(imageSrc)) {
+ imageSrc = restoredImageData(gDialog.srcInput);
+ }
+
+ try {
+ // Remove the image URL from image cache so it loads fresh
+ // (if we don't do this, loads after the first will always use image cache
+ // and we won't see image edit changes or be able to get actual width and height)
+
+ // We must have an absolute URL to preview it or remove it from the cache
+ imageSrc = MakeAbsoluteUrl(imageSrc);
+
+ if (GetScheme(imageSrc)) {
+ let uri = Services.io.newURI(imageSrc);
+ if (uri) {
+ let imgCache = Cc["@mozilla.org/image/cache;1"].getService(
+ Ci.imgICache
+ );
+
+ // This returns error if image wasn't in the cache; ignore that
+ imgCache.removeEntry(uri);
+ }
+ }
+ } catch (e) {}
+
+ if (gDialog.PreviewImage) {
+ removeEventListener("load", PreviewImageLoaded, true);
+ }
+
+ if (gDialog.ImageHolder.hasChildNodes()) {
+ gDialog.ImageHolder.firstChild.remove();
+ }
+
+ gDialog.PreviewImage = document.createElementNS(
+ "http://www.w3.org/1999/xhtml",
+ "img"
+ );
+ if (gDialog.PreviewImage) {
+ // set the src before appending to the document -- see bug 198435 for why
+ // this is needed.
+ // XXXbz that bug is long-since fixed. Is this still needed?
+ gDialog.PreviewImage.addEventListener("load", PreviewImageLoaded, true);
+ gDialog.PreviewImage.src = imageSrc;
+ gDialog.ImageHolder.appendChild(gDialog.PreviewImage);
+ }
+}
+
+function SetActualSize() {
+ gDialog.widthInput.value = gActualWidth ? gActualWidth : "";
+ gDialog.widthUnitsMenulist.selectedIndex = 0;
+ gDialog.heightInput.value = gActualHeight ? gActualHeight : "";
+ gDialog.heightUnitsMenulist.selectedIndex = 0;
+ doDimensionEnabling();
+}
+
+function ChangeImageSrc() {
+ if (gTimerID) {
+ clearTimeout(gTimerID);
+ }
+
+ gTimerID = setTimeout(LoadPreviewImage, 800);
+
+ SetRelativeCheckbox();
+ doOverallEnabling();
+}
+
+function doDimensionEnabling() {
+ // Enabled unless "Actual Size" is selected
+ var enable = !gDialog.actualSizeRadio.selected;
+
+ // BUG 74145: After input field is disabled,
+ // setting it enabled causes blinking caret to appear
+ // even though focus isn't set to it.
+ SetElementEnabledById("heightInput", enable);
+ SetElementEnabledById("heightLabel", enable);
+ SetElementEnabledById("heightUnitsMenulist", enable);
+
+ SetElementEnabledById("widthInput", enable);
+ SetElementEnabledById("widthLabel", enable);
+ SetElementEnabledById("widthUnitsMenulist", enable);
+
+ var constrainEnable =
+ enable &&
+ gDialog.widthUnitsMenulist.selectedIndex == 0 &&
+ gDialog.heightUnitsMenulist.selectedIndex == 0;
+
+ SetElementEnabledById("constrainCheckbox", constrainEnable);
+}
+
+function doOverallEnabling() {
+ var enabled = TrimString(gDialog.srcInput.value) != "";
+
+ SetElementEnabled(gDialog.OkButton, enabled);
+ SetElementEnabledById("AdvancedEditButton1", enabled);
+ SetElementEnabledById("imagemapLabel", enabled);
+ SetElementEnabledById("removeImageMap", gCanRemoveImageMap);
+}
+
+function ToggleConstrain() {
+ // If just turned on, save the current width and height as basis for constrain ratio
+ // Thus clicking on/off lets user say "Use these values as aspect ration"
+ if (
+ gDialog.constrainCheckbox.checked &&
+ !gDialog.constrainCheckbox.disabled &&
+ gDialog.widthUnitsMenulist.selectedIndex == 0 &&
+ gDialog.heightUnitsMenulist.selectedIndex == 0
+ ) {
+ gConstrainWidth = Number(TrimString(gDialog.widthInput.value));
+ gConstrainHeight = Number(TrimString(gDialog.heightInput.value));
+ }
+}
+
+function constrainProportions(srcID, destID) {
+ var srcElement = document.getElementById(srcID);
+ if (!srcElement) {
+ return;
+ }
+
+ var destElement = document.getElementById(destID);
+ if (!destElement) {
+ return;
+ }
+
+ // always force an integer (whether we are constraining or not)
+ forceInteger(srcID);
+
+ if (
+ !gActualWidth ||
+ !gActualHeight ||
+ !(gDialog.constrainCheckbox.checked && !gDialog.constrainCheckbox.disabled)
+ ) {
+ return;
+ }
+
+ // double-check that neither width nor height is in percent mode; bail if so!
+ if (
+ gDialog.widthUnitsMenulist.selectedIndex != 0 ||
+ gDialog.heightUnitsMenulist.selectedIndex != 0
+ ) {
+ return;
+ }
+
+ // This always uses the actual width and height ratios
+ // which is kind of funky if you change one number without the constrain
+ // and then turn constrain on and change a number
+ // I prefer the old strategy (below) but I can see some merit to this solution
+ if (srcID == "widthInput") {
+ destElement.value = Math.round(
+ (srcElement.value * gActualHeight) / gActualWidth
+ );
+ } else {
+ destElement.value = Math.round(
+ (srcElement.value * gActualWidth) / gActualHeight
+ );
+ }
+
+ /*
+ // With this strategy, the width and height ratio
+ // can be reset to whatever the user entered.
+ if (srcID == "widthInput") {
+ destElement.value = Math.round( srcElement.value * gConstrainHeight / gConstrainWidth );
+ } else {
+ destElement.value = Math.round( srcElement.value * gConstrainWidth / gConstrainHeight );
+ }
+ */
+}
+
+function removeImageMap() {
+ gRemoveImageMap = true;
+ gCanRemoveImageMap = false;
+ SetElementEnabledById("removeImageMap", false);
+}
+
+function SwitchToValidatePanel() {
+ if (
+ gDialog.tabBox &&
+ gValidateTab &&
+ gDialog.tabBox.selectedTab != gValidateTab
+ ) {
+ gDialog.tabBox.selectedTab = gValidateTab;
+ }
+}
+
+// Get data from widgets, validate, and set for the global element
+// accessible to AdvancedEdit() [in EdDialogCommon.js]
+function ValidateImage() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ return false;
+ }
+
+ gValidateTab = gDialog.tabLocation;
+ if (!gDialog.srcInput.value) {
+ Services.prompt.alert(
+ window,
+ GetString("Alert"),
+ GetString("MissingImageError")
+ );
+ SwitchToValidatePanel();
+ gDialog.srcInput.focus();
+ return false;
+ }
+
+ // We must convert to "file:///" or "http://" format else image doesn't load!
+ let src = gDialog.srcInput.value.trim();
+
+ if (isImageDataShortened(src)) {
+ src = restoredImageData(gDialog.srcInput);
+ } else {
+ var checkbox = document.getElementById("MakeRelativeCheckbox");
+ try {
+ if (checkbox && !checkbox.checked) {
+ src = Services.uriFixup.getFixupURIInfo(
+ src,
+ Ci.nsIURIFixup.FIXUP_FLAG_NONE
+ ).preferredURI.spec;
+ }
+ } catch (e) {}
+
+ globalElement.setAttribute("src", src);
+ }
+
+ let title = gDialog.titleInput.value.trim();
+ if (title) {
+ globalElement.setAttribute("title", title);
+ } else {
+ globalElement.removeAttribute("title");
+ }
+
+ // Force user to enter Alt text only if "Alternate text" radio is checked
+ // Don't allow just spaces in alt text
+ var alt = "";
+ var useAlt = gDialog.altTextRadioGroup.selectedItem == gDialog.altTextRadio;
+ if (useAlt) {
+ alt = TrimString(gDialog.altTextInput.value);
+ }
+
+ if (alt || !useAlt) {
+ globalElement.setAttribute("alt", alt);
+ } else if (!gDoAltTextError) {
+ globalElement.removeAttribute("alt");
+ } else {
+ Services.prompt.alert(window, GetString("Alert"), GetString("NoAltText"));
+ SwitchToValidatePanel();
+ gDialog.altTextInput.focus();
+ return false;
+ }
+
+ var width = "";
+ var height = "";
+
+ gValidateTab = gDialog.tabDimensions;
+ if (!gDialog.actualSizeRadio.selected) {
+ // Get user values for width and height
+ width = ValidateNumber(
+ gDialog.widthInput,
+ gDialog.widthUnitsMenulist,
+ 1,
+ gMaxPixels,
+ globalElement,
+ "width",
+ false,
+ true
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ height = ValidateNumber(
+ gDialog.heightInput,
+ gDialog.heightUnitsMenulist,
+ 1,
+ gMaxPixels,
+ globalElement,
+ "height",
+ false,
+ true
+ );
+ if (gValidationError) {
+ return false;
+ }
+ }
+
+ // We always set the width and height attributes, even if same as actual.
+ // This speeds up layout of pages since sizes are known before image is loaded
+ if (!width) {
+ width = gActualWidth;
+ }
+ if (!height) {
+ height = gActualHeight;
+ }
+
+ // Remove existing width and height only if source changed
+ // and we couldn't obtain actual dimensions
+ var srcChanged = src != gOriginalSrc;
+ if (width) {
+ editor.setAttributeOrEquivalent(globalElement, "width", width, true);
+ } else if (srcChanged) {
+ editor.removeAttributeOrEquivalent(globalElement, "width", true);
+ }
+
+ if (height) {
+ editor.setAttributeOrEquivalent(globalElement, "height", height, true);
+ } else if (srcChanged) {
+ editor.removeAttributeOrEquivalent(globalElement, "height", true);
+ }
+
+ // spacing attributes
+ gValidateTab = gDialog.tabBorder;
+ ValidateNumber(
+ gDialog.imagelrInput,
+ null,
+ 0,
+ gMaxPixels,
+ globalElement,
+ "hspace",
+ false,
+ true,
+ true
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ ValidateNumber(
+ gDialog.imagetbInput,
+ null,
+ 0,
+ gMaxPixels,
+ globalElement,
+ "vspace",
+ false,
+ true
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ // note this is deprecated and should be converted to stylesheets
+ ValidateNumber(
+ gDialog.border,
+ null,
+ 0,
+ gMaxPixels,
+ globalElement,
+ "border",
+ false,
+ true
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ // Default or setting "bottom" means don't set the attribute
+ // Note that the attributes "left" and "right" are opposite
+ // of what we use in the UI, which describes where the TEXT wraps,
+ // not the image location (which is what the HTML describes)
+ switch (gDialog.alignTypeSelect.value) {
+ case "top":
+ case "middle":
+ case "right":
+ case "left":
+ editor.setAttributeOrEquivalent(
+ globalElement,
+ "align",
+ gDialog.alignTypeSelect.value,
+ true
+ );
+ break;
+ default:
+ try {
+ editor.removeAttributeOrEquivalent(globalElement, "align", true);
+ } catch (e) {}
+ }
+
+ return true;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdImageLinkLoader.js b/comm/suite/editor/components/dialogs/content/EdImageLinkLoader.js
new file mode 100755
index 0000000000..5b88a5703f
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdImageLinkLoader.js
@@ -0,0 +1,145 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var gMsgCompProcessLink = false;
+var gMsgCompInputElement = null;
+var gMsgCompPrevInputValue = null;
+var gMsgCompPrevMozDoNotSendAttribute;
+var gMsgCompAttachSourceElement = null;
+
+function OnLoadDialog() {
+ gMsgCompAttachSourceElement = document.getElementById("AttachSourceToMail");
+ var editor = GetCurrentEditor();
+ if (
+ gMsgCompAttachSourceElement &&
+ editor &&
+ editor.flags & Ci.nsIEditor.eEditorMailMask
+ ) {
+ SetRelativeCheckbox = function() {
+ SetAttachCheckbox();
+ };
+ // initialize the AttachSourceToMail checkbox
+ gMsgCompAttachSourceElement.hidden = false;
+
+ switch (document.documentElement.id) {
+ case "imageDlg":
+ gMsgCompInputElement = gDialog.srcInput;
+ gMsgCompProcessLink = false;
+ break;
+ case "linkDlg":
+ gMsgCompInputElement = gDialog.hrefInput;
+ gMsgCompProcessLink = true;
+ break;
+ }
+ if (gMsgCompInputElement) {
+ SetAttachCheckbox();
+ gMsgCompPrevMozDoNotSendAttribute = globalElement.getAttribute(
+ "moz-do-not-send"
+ );
+ }
+ }
+}
+addEventListener("load", OnLoadDialog, false);
+
+function OnAcceptDialog() {
+ // Auto-convert file URLs to data URLs. If we're in the link properties
+ // dialog convert only when requested - for the image dialog do it always.
+ if (gMsgCompInputElement &&
+ /^file:/i.test(gMsgCompInputElement.value.trim()) &&
+ (gMsgCompAttachSourceElement.checked || !gMsgCompProcessLink)) {
+ var dataURI = GenerateDataURL(gMsgCompInputElement.value.trim());
+ gMsgCompInputElement.value = dataURI;
+ gMsgCompAttachSourceElement.checked = true;
+ }
+ DoAttachSourceCheckbox();
+}
+document.addEventListener("dialogaccept", OnAcceptDialog, true);
+
+function SetAttachCheckbox() {
+ var resetCheckbox = false;
+ var mozDoNotSend = globalElement.getAttribute("moz-do-not-send");
+
+ // In case somebody played with the advanced property and changed the moz-do-not-send attribute
+ if (mozDoNotSend != gMsgCompPrevMozDoNotSendAttribute) {
+ gMsgCompPrevMozDoNotSendAttribute = mozDoNotSend;
+ resetCheckbox = true;
+ }
+
+ // Has the URL changed
+ if (
+ gMsgCompInputElement &&
+ gMsgCompInputElement.value != gMsgCompPrevInputValue
+ ) {
+ gMsgCompPrevInputValue = gMsgCompInputElement.value;
+ resetCheckbox = true;
+ }
+
+ if (gMsgCompInputElement && resetCheckbox) {
+ // Here is the rule about how to set the checkbox Attach Source To Message:
+ // If the attribute "moz-do-not-send" has not been set, we look at the scheme of the URL
+ // and at some preference to decide what is the best for the user.
+ // If it is set to "false", the checkbox is checked, otherwise unchecked.
+ var attach = false;
+ if (mozDoNotSend == null) {
+ // We haven't yet set the "moz-do-not-send" attribute.
+ var inputValue = gMsgCompInputElement.value.trim();
+ if (/^(file|data):/i.test(inputValue)) {
+ // For files or data URLs, default to attach them.
+ attach = true;
+ } else if (
+ !gMsgCompProcessLink && // Implies image dialogue.
+ /^https?:/i.test(inputValue)
+ ) {
+ // For images loaded via http(s) we default to the preference value.
+ attach = Services.prefs.getBoolPref("mail.compose.attach_http_images");
+ }
+ } else {
+ attach = mozDoNotSend == "false";
+ }
+
+ gMsgCompAttachSourceElement.checked = attach;
+ }
+}
+
+function DoAttachSourceCheckbox() {
+ gMsgCompPrevMozDoNotSendAttribute = (!gMsgCompAttachSourceElement.checked).toString();
+ globalElement.setAttribute(
+ "moz-do-not-send",
+ gMsgCompPrevMozDoNotSendAttribute
+ );
+}
+
+function GenerateDataURL(url) {
+ var file = Services.io.newURI(url).QueryInterface(Ci.nsIFileURL).file;
+ var contentType = Cc["@mozilla.org/mime;1"]
+ .getService(Ci.nsIMIMEService)
+ .getTypeFromFile(file);
+ var inputStream = Cc[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Ci.nsIFileInputStream);
+ inputStream.init(file, 0x01, 0o600, 0);
+ var stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
+ Ci.nsIBinaryInputStream
+ );
+ stream.setInputStream(inputStream);
+ let data = "";
+ while (stream.available() > 0) {
+ data += stream.readBytes(stream.available());
+ }
+ let encoded = btoa(data);
+ stream.close();
+ return (
+ "data:" +
+ contentType +
+ ";filename=" +
+ encodeURIComponent(file.leafName) +
+ ";base64," +
+ encoded
+ );
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdImageProps.js b/comm/suite/editor/components/dialogs/content/EdImageProps.js
new file mode 100644
index 0000000000..5b73488dcb
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdImageProps.js
@@ -0,0 +1,293 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+/* import-globals-from EdImageDialog.js */
+
+var gAnchorElement = null;
+var gLinkElement = null;
+var gOriginalHref = "";
+var gHNodeArray = {};
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ ImageStartup();
+ gDialog.hrefInput = document.getElementById("hrefInput");
+ gDialog.makeRelativeLink = document.getElementById("MakeRelativeLink");
+ gDialog.showLinkBorder = document.getElementById("showLinkBorder");
+ gDialog.linkTab = document.getElementById("imageLinkTab");
+ gDialog.linkAdvanced = document.getElementById("LinkAdvancedEditButton");
+
+ // Get a single selected image element
+ var tagName = "img";
+ if ("arguments" in window && window.arguments[0]) {
+ imageElement = window.arguments[0];
+ // We've been called from form field properties, so we can't insert a link
+ gDialog.linkTab.remove();
+ gDialog.linkTab = null;
+ } else {
+ // First check for <input type="image">
+ try {
+ imageElement = editor.getSelectedElement("input");
+
+ if (!imageElement || imageElement.getAttribute("type") != "image") {
+ // Get a single selected image element
+ imageElement = editor.getSelectedElement(tagName);
+ if (imageElement) {
+ gAnchorElement = editor.getElementOrParentByTagName(
+ "href",
+ imageElement
+ );
+ }
+ }
+ } catch (e) {}
+ }
+
+ if (imageElement) {
+ // We found an element and don't need to insert one
+ if (imageElement.hasAttribute("src")) {
+ gInsertNewImage = false;
+ gActualWidth = imageElement.naturalWidth;
+ gActualHeight = imageElement.naturalHeight;
+ }
+ } else {
+ gInsertNewImage = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+ try {
+ imageElement = editor.createElementWithDefaults(tagName);
+ } catch (e) {}
+
+ if (!imageElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+ try {
+ gAnchorElement = editor.getSelectedElement("href");
+ } catch (e) {}
+ }
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = imageElement.cloneNode(false);
+
+ // We only need to test for this once per dialog load
+ gHaveDocumentUrl = GetDocumentBaseUrl();
+
+ InitDialog();
+ if (gAnchorElement) {
+ gOriginalHref = gAnchorElement.getAttribute("href");
+ // Make a copy to use for AdvancedEdit
+ gLinkElement = gAnchorElement.cloneNode(false);
+ } else {
+ gLinkElement = editor.createElementWithDefaults("a");
+ }
+ gDialog.hrefInput.value = gOriginalHref;
+
+ FillLinkMenulist(gDialog.hrefInput, gHNodeArray);
+ ChangeLinkLocation();
+
+ // Save initial source URL
+ gOriginalSrc = gDialog.srcInput.value;
+
+ // By default turn constrain on, but both width and height must be in pixels
+ gDialog.constrainCheckbox.checked =
+ gDialog.widthUnitsMenulist.selectedIndex == 0 &&
+ gDialog.heightUnitsMenulist.selectedIndex == 0;
+
+ // Start in "Link" tab if 2nd argument is true
+ if (gDialog.linkTab && "arguments" in window && window.arguments[1]) {
+ document.getElementById("TabBox").selectedTab = gDialog.linkTab;
+ SetTextboxFocus(gDialog.hrefInput);
+ } else {
+ SetTextboxFocus(gDialog.srcInput);
+ }
+
+ SetWindowLocation();
+}
+
+// Set dialog widgets with attribute data
+// We get them from globalElement copy so this can be used
+// by AdvancedEdit(), which is shared by all property dialogs
+function InitDialog() {
+ InitImage();
+ var border = TrimString(gDialog.border.value);
+ gDialog.showLinkBorder.checked = border != "" && border > 0;
+}
+
+function ChangeLinkLocation() {
+ var href = TrimString(gDialog.hrefInput.value);
+ SetRelativeCheckbox(gDialog.makeRelativeLink);
+ gDialog.showLinkBorder.disabled = !href;
+ gDialog.linkAdvanced.disabled = !href;
+ gLinkElement.setAttribute("href", href);
+}
+
+function ToggleShowLinkBorder() {
+ if (gDialog.showLinkBorder.checked) {
+ var border = TrimString(gDialog.border.value);
+ if (!border || border == "0") {
+ gDialog.border.value = "2";
+ }
+ } else {
+ gDialog.border.value = "0";
+ }
+}
+
+// Get data from widgets, validate, and set for the global element
+// accessible to AdvancedEdit() [in EdDialogCommon.js]
+function ValidateData() {
+ return ValidateImage();
+}
+
+function onAccept(event) {
+ // Use this now (default = false) so Advanced Edit button dialog doesn't trigger error message
+ gDoAltTextError = true;
+
+ if (ValidateData()) {
+ if ("arguments" in window && window.arguments[0]) {
+ SaveWindowLocation();
+ return;
+ }
+
+ var editor = GetCurrentEditor();
+
+ editor.beginTransaction();
+
+ try {
+ if (gRemoveImageMap) {
+ globalElement.removeAttribute("usemap");
+ if (gImageMap) {
+ editor.deleteNode(gImageMap);
+ gInsertNewIMap = true;
+ gImageMap = null;
+ }
+ } else if (gImageMap) {
+ // un-comment to see that inserting image maps does not work!
+ /*
+ gImageMap = editor.createElementWithDefaults("map");
+ gImageMap.setAttribute("name", "testing");
+ var testArea = editor.createElementWithDefaults("area");
+ testArea.setAttribute("shape", "circle");
+ testArea.setAttribute("coords", "86,102,52");
+ testArea.setAttribute("href", "test");
+ gImageMap.appendChild(testArea);
+ */
+
+ // Assign to map if there is one
+ var mapName = gImageMap.getAttribute("name");
+ if (mapName != "") {
+ globalElement.setAttribute("usemap", "#" + mapName);
+ if (globalElement.getAttribute("border") == "") {
+ globalElement.setAttribute("border", 0);
+ }
+ }
+ }
+
+ // Create or remove the link as appropriate
+ var href = gDialog.hrefInput.value;
+ if (href != gOriginalHref) {
+ if (href && !gInsertNewImage) {
+ EditorSetTextProperty("a", "href", href);
+ // gAnchorElement is needed for cloning attributes later.
+ if (!gAnchorElement) {
+ gAnchorElement = editor.getElementOrParentByTagName(
+ "href",
+ imageElement
+ );
+ }
+ } else {
+ EditorRemoveTextProperty("href", "");
+ }
+ }
+
+ // If inside a link, always write the 'border' attribute
+ if (href) {
+ if (gDialog.showLinkBorder.checked) {
+ // Use default = 2 if border attribute is empty
+ if (!globalElement.hasAttribute("border")) {
+ globalElement.setAttribute("border", "2");
+ }
+ } else {
+ globalElement.setAttribute("border", "0");
+ }
+ }
+
+ if (gInsertNewImage) {
+ if (href) {
+ gLinkElement.appendChild(imageElement);
+ editor.insertElementAtSelection(gLinkElement, true);
+ } else {
+ // 'true' means delete the selection before inserting
+ editor.insertElementAtSelection(imageElement, true);
+ }
+ }
+
+ // Check to see if the link was to a heading
+ // Do this last because it moves the caret (BAD!)
+ if (href in gHNodeArray) {
+ var anchorNode = editor.createElementWithDefaults("a");
+ if (anchorNode) {
+ anchorNode.name = href.substr(1);
+ // Remember to use editor method so it is undoable!
+ editor.insertNode(anchorNode, gHNodeArray[href], 0);
+ }
+ }
+ // All values are valid - copy to actual element in doc or
+ // element we just inserted
+ editor.cloneAttributes(imageElement, globalElement);
+ if (gAnchorElement) {
+ editor.cloneAttributes(gAnchorElement, gLinkElement);
+ }
+
+ // If document is empty, the map element won't insert,
+ // so always insert the image first
+ if (gImageMap && gInsertNewIMap) {
+ // Insert the ImageMap element at beginning of document
+ var body = editor.rootElement;
+ editor.setShouldTxnSetSelection(false);
+ editor.insertNode(gImageMap, body, 0);
+ editor.setShouldTxnSetSelection(true);
+ }
+ } catch (e) {
+ dump(e);
+ }
+
+ editor.endTransaction();
+
+ SaveWindowLocation();
+ return;
+ }
+
+ gDoAltTextError = false;
+
+ event.preventDefault();
+}
+
+function onLinkAdvancedEdit() {
+ window.AdvancedEditOK = false;
+ window.openDialog(
+ "chrome://editor/content/EdAdvancedEdit.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal,resizable=yes",
+ "",
+ gLinkElement
+ );
+ window.focus();
+ if (window.AdvancedEditOK) {
+ gDialog.hrefInput.value = gLinkElement.getAttribute("href");
+ }
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdImageProps.xhtml b/comm/suite/editor/components/dialogs/content/EdImageProps.xhtml
new file mode 100644
index 0000000000..11d7d03026
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdImageProps.xhtml
@@ -0,0 +1,116 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edImageProperties SYSTEM "chrome://editor/locale/EditorImageProperties.dtd">
+%edImageProperties;
+<!ENTITY % composeEditorOverlayDTD SYSTEM "chrome://messenger/locale/messengercompose/mailComposeEditorOverlay.dtd">
+%composeEditorOverlayDTD;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<!-- dialog containing a control requiring initial setup -->
+<dialog id="imageDlg" title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()"
+ buttons="accept,cancel">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdImageProps.js"/>
+ <script src="chrome://editor/content/EdImageDialog.js"/>
+ <script src="chrome://editor/content/EdImageLinkLoader.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <tabbox id="TabBox">
+ <tabs flex="1">
+ <tab id="imageLocationTab" label="&imageLocationTab.label;"/>
+ <tab id="imageDimensionsTab" label="&imageDimensionsTab.label;"/>
+ <tab id="imageAppearanceTab" label="&imageAppearanceTab.label;"/>
+ <tab id="imageLinkTab" label="&imageLinkTab.label;"/>
+ </tabs>
+ <tabpanels>
+#include edImage.inc.xhtml
+ <vbox>
+ <spacer class="spacer"/>
+ <vbox id="LinkLocationBox">
+ <label control="hrefInput"
+ accesskey="&LinkURLEditField2.accessKey;"
+ width="1">&LinkURLEditField2.label;</label>
+ <textbox id="hrefInput" type="text"
+ class="uri-element padded" oninput="ChangeLinkLocation();"/>
+ <hbox align="center">
+ <checkbox id="MakeRelativeLink"
+ for="hrefInput"
+ label="&makeUrlRelative.label;"
+ accesskey="&makeUrlRelative.accessKey;"
+ oncommand="MakeInputValueRelativeOrAbsolute(this);"
+ tooltiptext="&makeUrlRelative.tooltip;"/>
+ <spacer flex="1"/>
+ <button label="&chooseFileLinkButton.label;" accesskey="&chooseFileLinkButton.accessKey;"
+ oncommand="chooseLinkFile();"/>
+ </hbox>
+ </vbox>
+ <spacer class="spacer"/>
+ <hbox>
+ <checkbox id="showLinkBorder"
+ label="&showImageLinkBorder.label;"
+ accesskey="&showImageLinkBorder.accessKey;"
+ oncommand="ToggleShowLinkBorder();"/>
+ <spacer flex="1"/>
+ <button id="LinkAdvancedEditButton"
+ label="&LinkAdvancedEditButton.label;"
+ accesskey="&LinkAdvancedEditButton.accessKey;"
+ tooltiptext="&LinkAdvancedEditButton.tooltip;"
+ oncommand="onLinkAdvancedEdit();"/>
+ </hbox>
+ </vbox>
+ </tabpanels>
+ </tabbox>
+
+ <hbox align="end">
+ <groupbox id="imagePreview" orient="horizontal" flex="1">
+ <hbox class="groupbox-title">
+ <label class="header">&previewBox.label;</label>
+ </hbox>
+ <hbox id="preview-image-box" align="center">
+ <spacer flex="1"/>
+ <description id="preview-image-holder"/>
+ <spacer flex="1"/>
+ </hbox>
+ <vbox id="PreviewSize" collapsed="true">
+ <spacer flex="1"/>
+ <label value="&actualSize.label;"/>
+ <hbox>
+ <label value="&widthEditField.label;"/>
+ <spacer flex="1"/>
+ <label id="PreviewWidth"/>
+ </hbox>
+ <hbox>
+ <label value="&heightEditField.label;"/>
+ <spacer flex="1"/>
+ <label id="PreviewHeight"/>
+ </hbox>
+ <spacer flex="1"/>
+ </vbox>
+ </groupbox>
+
+ <vbox id="AdvancedEdit">
+ <hbox flex="1" style="margin-top: 0.2em" align="center">
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ </vbox>
+ </hbox>
+ <separator class="groove"/>
+
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdInputImage.js b/comm/suite/editor/components/dialogs/content/EdInputImage.js
new file mode 100644
index 0000000000..556acc7b13
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInputImage.js
@@ -0,0 +1,189 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+/* import-globals-from EdImageDialog.js */
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ gDialog = {
+ inputName: document.getElementById("InputName"),
+ inputDisabled: document.getElementById("InputDisabled"),
+ inputTabIndex: document.getElementById("InputTabIndex"),
+ };
+
+ ImageStartup();
+
+ // Get a single selected input element
+ var tagName = "input";
+ try {
+ imageElement = editor.getSelectedElement(tagName);
+ } catch (e) {}
+
+ if (imageElement) {
+ // We found an element and don't need to insert one
+ gInsertNewImage = false;
+ } else {
+ gInsertNewImage = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+ try {
+ imageElement = editor.createElementWithDefaults(tagName);
+ } catch (e) {}
+
+ if (!imageElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+ var imgElement;
+ try {
+ imgElement = editor.getSelectedElement("img");
+ } catch (e) {}
+
+ if (imgElement) {
+ // We found an image element, convert it to an input type="image"
+ var attributes = [
+ "src",
+ "alt",
+ "width",
+ "height",
+ "hspace",
+ "vspace",
+ "border",
+ "align",
+ "usemap",
+ "ismap",
+ ];
+ for (let i in attributes) {
+ imageElement.setAttribute(
+ attributes[i],
+ imgElement.getAttribute(attributes[i])
+ );
+ }
+ }
+ }
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = imageElement.cloneNode(false);
+
+ // We only need to test for this once per dialog load
+ gHaveDocumentUrl = GetDocumentBaseUrl();
+
+ InitDialog();
+
+ // Save initial source URL
+ gOriginalSrc = gDialog.srcInput.value;
+
+ // By default turn constrain on, but both width and height must be in pixels
+ gDialog.constrainCheckbox.checked =
+ gDialog.widthUnitsMenulist.selectedIndex == 0 &&
+ gDialog.heightUnitsMenulist.selectedIndex == 0;
+
+ SetTextboxFocus(gDialog.inputName);
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ InitImage();
+ gDialog.inputName.value = globalElement.getAttribute("name");
+ gDialog.inputDisabled.setAttribute(
+ "checked",
+ globalElement.hasAttribute("disabled")
+ );
+ gDialog.inputTabIndex.value = globalElement.getAttribute("tabindex");
+}
+
+function ValidateData() {
+ if (!ValidateImage()) {
+ return false;
+ }
+ if (gDialog.inputName.value) {
+ globalElement.setAttribute("name", gDialog.inputName.value);
+ } else {
+ globalElement.removeAttribute("name");
+ }
+ if (gDialog.inputTabIndex.value) {
+ globalElement.setAttribute("tabindex", gDialog.inputTabIndex.value);
+ } else {
+ globalElement.removeAttribute("tabindex");
+ }
+ if (gDialog.inputDisabled.checked) {
+ globalElement.setAttribute("disabled", "");
+ } else {
+ globalElement.removeAttribute("disabled");
+ }
+ globalElement.setAttribute("type", "image");
+ return true;
+}
+
+function onAccept(event) {
+ // Show alt text error only once
+ // (we don't initialize doAltTextError=true
+ // so Advanced edit button dialog doesn't trigger that error message)
+ // Use this now (default = false) so Advanced Edit button dialog doesn't trigger error message
+ gDoAltTextError = true;
+
+ if (ValidateData()) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+
+ try {
+ if (gRemoveImageMap) {
+ globalElement.removeAttribute("usemap");
+ if (gImageMap) {
+ editor.deleteNode(gImageMap);
+ gInsertNewIMap = true;
+ gImageMap = null;
+ }
+ } else if (gImageMap) {
+ // Assign to map if there is one
+ var mapName = gImageMap.getAttribute("name");
+ if (mapName != "") {
+ globalElement.setAttribute("usemap", "#" + mapName);
+ if (globalElement.getAttribute("border") == "") {
+ globalElement.setAttribute("border", 0);
+ }
+ }
+ }
+
+ if (gInsertNewImage) {
+ // 'true' means delete the selection before inserting
+ // in case were are converting an image to an input type="image"
+ editor.insertElementAtSelection(imageElement, true);
+ }
+ editor.cloneAttributes(imageElement, globalElement);
+
+ // If document is empty, the map element won't insert,
+ // so always insert the image element first
+ if (gImageMap && gInsertNewIMap) {
+ // Insert the ImageMap element at beginning of document
+ var body = editor.rootElement;
+ editor.setShouldTxnSetSelection(false);
+ editor.insertNode(gImageMap, body, 0);
+ editor.setShouldTxnSetSelection(true);
+ }
+ } catch (e) {}
+
+ editor.endTransaction();
+
+ SaveWindowLocation();
+
+ return;
+ }
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdInputImage.xhtml b/comm/suite/editor/components/dialogs/content/EdInputImage.xhtml
new file mode 100644
index 0000000000..d3fc8c8270
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInputImage.xhtml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edInputProperties SYSTEM "chrome://editor/locale/EditorInputProperties.dtd">
+%edInputProperties;
+<!ENTITY % edImageProperties SYSTEM "chrome://editor/locale/EditorImageProperties.dtd">
+%edImageProperties;
+<!ENTITY % composeEditorOverlayDTD SYSTEM "chrome://messenger/locale/messengercompose/mailComposeEditorOverlay.dtd">
+%composeEditorOverlayDTD;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitleImage.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdInputImage.js"/>
+ <script src="chrome://editor/content/EdImageDialog.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <tabbox id="TabBox">
+ <tabs flex="1">
+ <tab id="imageInputTab" label="&imageInputTab.label;"/>
+ <tab id="imageLocationTab" label="&imageLocationTab.label;"/>
+ <tab id="imageDimensionsTab" label="&imageDimensionsTab.label;"/>
+ <tab id="imageAppearanceTab" label="&imageAppearanceTab.label;"/>
+ </tabs>
+ <tabpanels>
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&InputSettings.label;</label>
+ </hbox>
+ <grid><columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label value="&InputName.label;"/>
+ <textbox id="InputName"/>
+ </row>
+ <row>
+ <spacer/>
+ <checkbox id="InputDisabled" label="&InputDisabled.label;"/>
+ </row>
+ <row align="center">
+ <label value="&tabIndex.label;"/>
+ <hbox>
+ <textbox id="InputTabIndex" class="narrow" oninput="forceInteger(this.id);"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+#include edImage.inc.xhtml
+ </tabpanels>
+ </tabbox>
+
+ <hbox align="end">
+ <groupbox id="imagePreview" orient="horizontal" flex="1">
+ <hbox class="groupbox-title">
+ <label class="header">&previewBox.label;</label>
+ </hbox>
+ <hbox id="preview-image-box" align="center">
+ <spacer flex="1"/>
+ <description id="preview-image-holder"/>
+ <spacer flex="1"/>
+ </hbox>
+ <vbox id="PreviewSize" collapsed="true">
+ <spacer flex="1"/>
+ <label value="&actualSize.label;"/>
+ <hbox>
+ <label value="&widthEditField.label;"/>
+ <spacer flex="1"/>
+ <label id="PreviewWidth"/>
+ </hbox>
+ <hbox>
+ <label value="&heightEditField.label;"/>
+ <spacer flex="1"/>
+ <label id="PreviewHeight"/>
+ </hbox>
+ <spacer flex="1"/>
+ </vbox>
+ </groupbox>
+
+ <vbox id="AdvancedEdit">
+ <hbox flex="1" style="margin-top: 0.2em" align="center">
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator id="advancedSeparator" class="groove"/>
+ </vbox>
+ </hbox>
+
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdInputProps.js b/comm/suite/editor/components/dialogs/content/EdInputProps.js
new file mode 100644
index 0000000000..a737e263c7
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInputProps.js
@@ -0,0 +1,345 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var insertNew;
+var inputElement;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ dump("Failed to get active editor!\n");
+ window.close();
+ return;
+ }
+
+ gDialog = {
+ accept: document.documentElement.getButton("accept"),
+ inputType: document.getElementById("InputType"),
+ inputNameDeck: document.getElementById("InputNameDeck"),
+ inputName: document.getElementById("InputName"),
+ inputValueDeck: document.getElementById("InputValueDeck"),
+ inputValue: document.getElementById("InputValue"),
+ inputDeck: document.getElementById("InputDeck"),
+ inputChecked: document.getElementById("InputChecked"),
+ inputSelected: document.getElementById("InputSelected"),
+ inputReadOnly: document.getElementById("InputReadOnly"),
+ inputDisabled: document.getElementById("InputDisabled"),
+ inputTabIndex: document.getElementById("InputTabIndex"),
+ inputAccessKey: document.getElementById("InputAccessKey"),
+ inputSize: document.getElementById("InputSize"),
+ inputMaxLength: document.getElementById("InputMaxLength"),
+ inputAccept: document.getElementById("InputAccept"),
+ MoreSection: document.getElementById("MoreSection"),
+ MoreFewerButton: document.getElementById("MoreFewerButton"),
+ AdvancedEditButton: document.getElementById("AdvancedEditButton"),
+ AdvancedEditDeck: document.getElementById("AdvancedEditDeck"),
+ };
+
+ // Get a single selected input element
+ const kTagName = "input";
+ try {
+ inputElement = editor.getSelectedElement(kTagName);
+ } catch (e) {}
+
+ if (inputElement) {
+ // We found an element and don't need to insert one
+ insertNew = false;
+ } else {
+ insertNew = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+ try {
+ inputElement = editor.createElementWithDefaults(kTagName);
+ } catch (e) {}
+
+ if (!inputElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+
+ var imgElement = editor.getSelectedElement("img");
+ if (imgElement) {
+ // We found an image element, convert it to an input type="image"
+ inputElement.setAttribute("type", "image");
+
+ var attributes = [
+ "src",
+ "alt",
+ "width",
+ "height",
+ "hspace",
+ "vspace",
+ "border",
+ "align",
+ ];
+ for (let i in attributes) {
+ inputElement.setAttribute(
+ attributes[i],
+ imgElement.getAttribute(attributes[i])
+ );
+ }
+ } else {
+ inputElement.setAttribute("value", GetSelectionAsText());
+ }
+ }
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = inputElement.cloneNode(false);
+
+ InitDialog();
+
+ InitMoreFewer();
+
+ gDialog.inputType.focus();
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ var type = globalElement.getAttribute("type");
+ var index = 0;
+ switch (type) {
+ case "button":
+ index = 9;
+ break;
+ case "checkbox":
+ index = 2;
+ break;
+ case "file":
+ index = 6;
+ break;
+ case "hidden":
+ index = 7;
+ break;
+ case "image":
+ index = 8;
+ break;
+ case "password":
+ index = 1;
+ break;
+ case "radio":
+ index = 3;
+ break;
+ case "reset":
+ index = 5;
+ break;
+ case "submit":
+ index = 4;
+ break;
+ }
+ gDialog.inputType.selectedIndex = index;
+ gDialog.inputName.value = globalElement.getAttribute("name");
+ gDialog.inputValue.value = globalElement.getAttribute("value");
+ gDialog.inputChecked.setAttribute(
+ "checked",
+ globalElement.hasAttribute("checked")
+ );
+ gDialog.inputSelected.setAttribute(
+ "checked",
+ globalElement.hasAttribute("checked")
+ );
+ gDialog.inputReadOnly.setAttribute(
+ "checked",
+ globalElement.hasAttribute("readonly")
+ );
+ gDialog.inputDisabled.setAttribute(
+ "checked",
+ globalElement.hasAttribute("disabled")
+ );
+ gDialog.inputTabIndex.value = globalElement.getAttribute("tabindex");
+ gDialog.inputAccessKey.value = globalElement.getAttribute("accesskey");
+ gDialog.inputSize.value = globalElement.getAttribute("size");
+ gDialog.inputMaxLength.value = globalElement.getAttribute("maxlength");
+ gDialog.inputAccept.value = globalElement.getAttribute("accept");
+ SelectInputType();
+}
+
+function SelectInputType() {
+ var index = gDialog.inputType.selectedIndex;
+ gDialog.AdvancedEditDeck.setAttribute("selectedIndex", 0);
+ gDialog.inputNameDeck.setAttribute("selectedIndex", 0);
+ gDialog.inputValueDeck.setAttribute("selectedIndex", 0);
+ gDialog.inputValue.disabled = false;
+ gDialog.inputChecked.disabled = index != 2;
+ gDialog.inputSelected.disabled = index != 3;
+ gDialog.inputReadOnly.disabled = index > 1;
+ gDialog.inputTabIndex.disabled = index == 7;
+ gDialog.inputAccessKey.disabled = index == 7;
+ gDialog.inputSize.disabled = index > 1;
+ gDialog.inputMaxLength.disabled = index > 1;
+ gDialog.inputAccept.disabled = index != 6;
+ switch (index) {
+ case 0:
+ case 1:
+ gDialog.inputValueDeck.setAttribute("selectedIndex", 1);
+ gDialog.inputDeck.setAttribute("selectedIndex", 2);
+ break;
+ case 2:
+ gDialog.inputDeck.setAttribute("selectedIndex", 0);
+ break;
+ case 3:
+ gDialog.inputDeck.setAttribute("selectedIndex", 1);
+ gDialog.inputNameDeck.setAttribute("selectedIndex", 1);
+ break;
+ case 6:
+ gDialog.inputValue.disabled = true;
+ gDialog.inputAccept.disabled = false;
+ break;
+ case 8:
+ gDialog.inputValue.disabled = true;
+ gDialog.AdvancedEditDeck.setAttribute("selectedIndex", 1);
+ gDialog.inputName.removeEventListener("input", onInput);
+ break;
+ case 7:
+ gDialog.inputValueDeck.setAttribute("selectedIndex", 1);
+ break;
+ }
+ onInput();
+}
+
+function onInput() {
+ var disabled = false;
+ switch (gDialog.inputType.selectedIndex) {
+ case 3:
+ disabled = disabled || !gDialog.inputValue.value;
+ break;
+ case 4:
+ case 5:
+ break;
+ case 8:
+ disabled = !globalElement.hasAttribute("src");
+ break;
+ default:
+ disabled = !gDialog.inputName.value;
+ break;
+ }
+ if (gDialog.accept.disabled != disabled) {
+ gDialog.accept.disabled = disabled;
+ gDialog.AdvancedEditButton.disabled = disabled;
+ }
+}
+
+function doImageProperties() {
+ window.openDialog(
+ "chrome://editor/content/EdImageProps.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ globalElement
+ );
+ window.focus();
+ onInput();
+}
+
+function ValidateData() {
+ var attributes = {
+ type: "",
+ name: gDialog.inputName.value,
+ value: gDialog.inputValue.value,
+ tabindex: gDialog.inputTabIndex.value,
+ accesskey: "",
+ size: "",
+ maxlength: "",
+ accept: "",
+ };
+ var index = gDialog.inputType.selectedIndex;
+ var flags = {
+ checked: false,
+ readonly: false,
+ disabled: gDialog.inputDisabled.checked,
+ };
+ switch (index) {
+ case 1:
+ attributes.type = "password";
+ // Falls through
+ case 0:
+ flags.readonly = gDialog.inputReadOnly.checked;
+ attributes.size = gDialog.inputSize.value;
+ attributes.maxlength = gDialog.inputMaxLength.value;
+ break;
+ case 2:
+ attributes.type = "checkbox";
+ flags.checked = gDialog.inputChecked.checked;
+ break;
+ case 3:
+ attributes.type = "radio";
+ flags.checked = gDialog.inputSelected.checked;
+ break;
+ case 4:
+ attributes.type = "submit";
+ attributes.accesskey = gDialog.inputAccessKey.value;
+ break;
+ case 5:
+ attributes.type = "reset";
+ attributes.accesskey = gDialog.inputAccessKey.value;
+ break;
+ case 6:
+ attributes.type = "file";
+ attributes.accept = gDialog.inputAccept.value;
+ attributes.value = "";
+ break;
+ case 7:
+ attributes.type = "hidden";
+ attributes.tabindex = "";
+ break;
+ case 8:
+ attributes.type = "image";
+ attributes.value = "";
+ break;
+ case 9:
+ attributes.type = "button";
+ attributes.accesskey = gDialog.inputAccessKey.value;
+ break;
+ }
+ for (var a in attributes) {
+ if (attributes[a]) {
+ globalElement.setAttribute(a, attributes[a]);
+ } else {
+ globalElement.removeAttribute(a);
+ }
+ }
+ for (var f in flags) {
+ if (flags[f]) {
+ globalElement.setAttribute(f, "");
+ } else {
+ globalElement.removeAttribute(f);
+ }
+ }
+ return true;
+}
+
+function onAccept(event) {
+ if (ValidateData()) {
+ // All values are valid - copy to actual element in doc or
+ // element created to insert
+
+ var editor = GetCurrentEditor();
+
+ editor.cloneAttributes(inputElement, globalElement);
+
+ if (insertNew) {
+ try {
+ // 'true' means delete the selection before inserting
+ // in case were are converting an image to an input type="image"
+ editor.insertElementAtSelection(inputElement, true);
+ } catch (e) {
+ dump(e);
+ }
+ }
+
+ SaveWindowLocation();
+
+ return;
+ }
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdInputProps.xhtml b/comm/suite/editor/components/dialogs/content/EdInputProps.xhtml
new file mode 100644
index 0000000000..c6011ee896
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInputProps.xhtml
@@ -0,0 +1,135 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edInputProperties SYSTEM "chrome://editor/locale/EditorInputProperties.dtd">
+%edInputProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ buttons="accept,cancel">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdInputProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header" control="InputType" accesskey="&InputType.accesskey;">&InputType.label;</label>
+ </hbox>
+ <menulist id="InputType" oncommand="SelectInputType();">
+ <menupopup>
+ <menuitem label="&text.value;"/>
+ <menuitem label="&password.value;"/>
+ <menuitem label="&checkbox.value;"/>
+ <menuitem label="&radio.value;"/>
+ <menuitem label="&submit.value;"/>
+ <menuitem label="&reset.value;"/>
+ <menuitem label="&file.value;"/>
+ <menuitem label="&hidden.value;"/>
+ <menuitem label="&image.value;"/>
+ <menuitem label="&button.value;"/>
+ </menupopup>
+ </menulist>
+ </groupbox>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&InputSettings.label;</label>
+ </hbox>
+ <grid><columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <deck id="InputNameDeck">
+ <label control="InputName" value="&InputName.label;" accesskey="&InputName.accesskey;"/>
+ <label control="InputName" value="&GroupName.label;" accesskey="&GroupName.accesskey;"/>
+ </deck>
+ <textbox id="InputName" oninput="onInput();"/>
+ </row>
+ <row align="center">
+ <deck id="InputValueDeck">
+ <label control="InputValue" value="&InputValue.label;" accesskey="&InputValue.accesskey;"/>
+ <label control="InputValue" value="&InitialValue.label;" accesskey="&InitialValue.accesskey;"/>
+ </deck>
+ <textbox id="InputValue" oninput="onInput();"/>
+ </row>
+ <row>
+ <spacer/>
+ <deck id="InputDeck" persist="index">
+ <checkbox id="InputChecked" label="&InputChecked.label;" accesskey="&InputChecked.accesskey;"/>
+ <checkbox id="InputSelected" label="&InputSelected.label;" accesskey="&InputSelected.accesskey;"/>
+ <checkbox id="InputReadOnly" label="&InputReadOnly.label;" accesskey="&InputReadOnly.accesskey;"/>
+ </deck>
+ </row>
+ </rows>
+ </grid>
+ <hbox>
+ <button id="MoreFewerButton" oncommand="onMoreFewer();" persist="more"/>
+ </hbox>
+ <grid id="MoreSection" align="start">
+ <columns><column/><column/></columns>
+ <rows>
+ <row>
+ <spacer/>
+ <checkbox id="InputDisabled" label="&InputDisabled.label;" accesskey="&InputDisabled.accesskey;"/>
+ </row>
+ <row align="center">
+ <label control="InputTabIndex" value="&tabIndex.label;" accesskey="&tabIndex.accesskey;"/>
+ <hbox>
+ <textbox id="InputTabIndex" class="narrow" oninput="forceInteger(this.id);"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="InputAccessKey" value="&AccessKey.label;" accesskey="&AccessKey.accesskey;"/>
+ <hbox>
+ <textbox id="InputAccessKey" class="narrow"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="InputSize" value="&TextSize.label;" accesskey="&TextSize.accesskey;"/>
+ <hbox>
+ <textbox id="InputSize" class="narrow" oninput="forceInteger(this.id);"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="InputMaxLength" value="&TextLength.label;" accesskey="&TextLength.accesskey;"/>
+ <hbox>
+ <textbox id="InputMaxLength" class="narrow" oninput="forceInteger(this.id);"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="InputAccept" value="&Accept.label;" accesskey="&Accept.accesskey;"/>
+ <textbox id="InputAccept"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <!-- from EdDialogOverlay -->
+ <hbox flex="1" style="margin-top: 0.2em">
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <deck id="AdvancedEditDeck">
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ <button label="&ImageProperties.label;" accesskey="&ImageProperties.accesskey;" oncommand="doImageProperties();"/>
+ </deck>
+ </hbox>
+ <separator class="groove"/>
+
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdInsSrc.js b/comm/suite/editor/components/dialogs/content/EdInsSrc.js
new file mode 100644
index 0000000000..0f0304ef1e
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsSrc.js
@@ -0,0 +1,160 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Insert Source HTML dialog */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var gFullDataStrings = new Map();
+var gShortDataStrings = new Map();
+var gListenerAttached = false;
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ let editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ document.documentElement.getButton("accept").removeAttribute("default");
+
+ // Create dialog object to store controls for easy access
+ gDialog.srcInput = document.getElementById("srcInput");
+
+ // Attach a paste listener so we can detect pasted data URIs we need to shorten.
+ gDialog.srcInput.addEventListener("paste", onPaste);
+
+ let selection;
+ try {
+ selection = editor.outputToString(
+ "text/html",
+ kOutputFormatted | kOutputSelectionOnly | kOutputWrap
+ );
+ } catch (e) {}
+ if (selection) {
+ selection = selection.replace(/<body[^>]*>/, "").replace(/<\/body>/, "");
+
+ // Shorten data URIs for display.
+ selection = replaceDataURIs(selection);
+
+ if (selection) {
+ gDialog.srcInput.value = selection;
+ }
+ }
+ // Set initial focus
+ gDialog.srcInput.focus();
+ SetWindowLocation();
+}
+
+function replaceDataURIs(input) {
+ return input.replace(/(data:.+;base64,)([^"' >]+)/gi, function(
+ match,
+ nonDataPart,
+ dataPart
+ ) {
+ if (gShortDataStrings.has(dataPart)) {
+ // We found the exact same data URI, just return the shortened URI.
+ return nonDataPart + gShortDataStrings.get(dataPart);
+ }
+
+ let l = 5;
+ let key;
+ // Normally we insert the ellipsis after five characters but if it's not unique
+ // we include more data.
+ do {
+ key = dataPart.substr(0, l) + "…" + dataPart.substr(dataPart.length - 10);
+ l++;
+ } while (gFullDataStrings.has(key) && l < dataPart.length - 10);
+ gFullDataStrings.set(key, dataPart);
+ gShortDataStrings.set(dataPart, key);
+
+ // Attach listeners. In case anyone copies/cuts from the HTML window,
+ // we want to restore the data URI on the clipboard.
+ if (!gListenerAttached) {
+ gDialog.srcInput.addEventListener("copy", onCopyOrCut);
+ gDialog.srcInput.addEventListener("cut", onCopyOrCut);
+ gListenerAttached = true;
+ }
+
+ return nonDataPart + key;
+ });
+}
+
+function onCopyOrCut(event) {
+ let startPos = gDialog.srcInput.selectionStart;
+ if (startPos == undefined) {
+ return;
+ }
+ let endPos = gDialog.srcInput.selectionEnd;
+ let clipboard = gDialog.srcInput.value.substring(startPos, endPos);
+
+ // Add back the original data URIs we stashed away earlier.
+ clipboard = clipboard.replace(/(data:.+;base64,)([^"' >]+)/gi, function(
+ match,
+ nonDataPart,
+ key
+ ) {
+ if (!gFullDataStrings.has(key)) {
+ // User changed data URI.
+ return match;
+ }
+ return nonDataPart + gFullDataStrings.get(key);
+ });
+ event.clipboardData.setData("text/plain", clipboard);
+ if (event.type == "cut") {
+ // We have to cut the selection manually.
+ gDialog.srcInput.value =
+ gDialog.srcInput.value.substr(0, startPos) +
+ gDialog.srcInput.value.substr(endPos);
+ }
+ event.preventDefault();
+}
+
+function onPaste(event) {
+ let startPos = gDialog.srcInput.selectionStart;
+ if (startPos == undefined) {
+ return;
+ }
+ let endPos = gDialog.srcInput.selectionEnd;
+ let clipboard = event.clipboardData.getData("text/plain");
+
+ // We do out own paste by replacing the selection with the pre-processed
+ // clipboard data.
+ gDialog.srcInput.value =
+ gDialog.srcInput.value.substr(0, startPos) +
+ replaceDataURIs(clipboard) +
+ gDialog.srcInput.value.substr(endPos);
+ event.preventDefault();
+}
+
+function onAccept(event) {
+ let html = gDialog.srcInput.value;
+ if (!html) {
+ event.preventDefault();
+ return;
+ }
+
+ // Add back the original data URIs we stashed away earlier.
+ html = html.replace(/(data:.+;base64,)([^"' >]+)/gi, function(
+ match,
+ nonDataPart,
+ key
+ ) {
+ if (!gFullDataStrings.has(key)) {
+ // User changed data URI.
+ return match;
+ }
+ return nonDataPart + gFullDataStrings.get(key);
+ });
+
+ try {
+ GetCurrentEditor().insertHTML(html);
+ } catch (e) {}
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdInsSrc.xhtml b/comm/suite/editor/components/dialogs/content/EdInsSrc.xhtml
new file mode 100644
index 0000000000..32b89ebefd
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsSrc.xhtml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorInsertSource.dtd">
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload = "Startup()"
+ buttonlabelaccept="&insertButton.label;"
+ buttonaccesskeyaccept="&insertButton.accesskey;">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://global/content/globalOverlay.js"/>
+ <script src="chrome://global/content/editMenuOverlay.js"/>
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdInsSrc.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <label id="srcMessage" value="&sourceEditField.label;"/>
+ <vbox flex="1" style="width: 30em; height: 20em;">
+ <html:textarea id="srcInput" rows="18" flex="1"/>
+ </vbox>
+ <!-- Will this accept the embedded HTML tags? -->
+ <hbox>
+ <spacer class="bigspacer"/>
+ <label value="&example.label;"/>
+ <label class="bold" value="&exampleOpenTag.label;"/>
+ <label class="bold italic" value="&exampleText.label;"/>
+ <label class="bold" value="&exampleCloseTag.label;"/>
+ </hbox>
+ <spacer class="spacer"/>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdInsertChars.js b/comm/suite/editor/components/dialogs/content/EdInsertChars.js
new file mode 100644
index 0000000000..6ee88afcdd
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsertChars.js
@@ -0,0 +1,409 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// ------------------------------------------------------------------
+// From Unicode 3.0 Page 54. 3.11 Conjoining Jamo Behavior
+var SBase = 0xac00;
+var LBase = 0x1100;
+var VBase = 0x1161;
+var TBase = 0x11a7;
+var LCount = 19;
+var VCount = 21;
+var TCount = 28;
+var NCount = VCount * TCount;
+// End of Unicode 3.0
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onClose);
+
+// dialog initialization code
+function Startup() {
+ if (!GetCurrentEditor()) {
+ window.close();
+ return;
+ }
+
+ StartupLatin();
+
+ // Set a variable on the opener window so we
+ // can track ownership of close this window with it
+ window.opener.InsertCharWindow = window;
+ window.sizeToContent();
+
+ SetWindowLocation();
+}
+
+function onAccept(event) {
+ // Insert the character
+ try {
+ GetCurrentEditor().insertText(LatinM.label);
+ } catch (e) {}
+
+ // Set persistent attributes to save
+ // which category, letter, and character modifier was used
+ CategoryGroup.setAttribute("category", category);
+ CategoryGroup.setAttribute("letter_index", indexL);
+ CategoryGroup.setAttribute("char_index", indexM);
+
+ // Don't close the dialog
+ event.preventDefault();
+}
+
+// Don't allow inserting in HTML Source Mode
+function onFocus() {
+ var enable = true;
+ if ("gEditorDisplayMode" in window.opener) {
+ enable = !window.opener.IsInHTMLSourceMode();
+ }
+
+ SetElementEnabled(document.documentElement.getButton("accept"), enable);
+}
+
+function onClose() {
+ window.opener.InsertCharWindow = null;
+ SaveWindowLocation();
+}
+
+// ------------------------------------------------------------------
+var LatinL;
+var LatinM;
+var LatinL_Label;
+var LatinM_Label;
+var indexL = 0;
+var indexM = 0;
+var indexM_AU = 0;
+var indexM_AL = 0;
+var indexM_U = 0;
+var indexM_L = 0;
+var indexM_S = 0;
+var LItems = 0;
+var category;
+var CategoryGroup;
+var initialize = true;
+
+function StartupLatin() {
+ LatinL = document.getElementById("LatinL");
+ LatinM = document.getElementById("LatinM");
+ LatinL_Label = document.getElementById("LatinL_Label");
+ LatinM_Label = document.getElementById("LatinM_Label");
+
+ var Symbol = document.getElementById("Symbol");
+ var AccentUpper = document.getElementById("AccentUpper");
+ var AccentLower = document.getElementById("AccentLower");
+ var Upper = document.getElementById("Upper");
+ var Lower = document.getElementById("Lower");
+ CategoryGroup = document.getElementById("CatGrp");
+
+ // Initialize which radio button is set from persistent attribute...
+ var category = CategoryGroup.getAttribute("category");
+
+ // ...as well as indexes into the letter and character lists
+ var index = Number(CategoryGroup.getAttribute("letter_index"));
+ if (index && index >= 0) {
+ indexL = index;
+ }
+ index = Number(CategoryGroup.getAttribute("char_index"));
+ if (index && index >= 0) {
+ indexM = index;
+ }
+
+ switch (category) {
+ case "AccentUpper": // Uppercase Diacritical
+ CategoryGroup.selectedItem = AccentUpper;
+ indexM_AU = indexM;
+ break;
+ case "AccentLower": // Lowercase Diacritical
+ CategoryGroup.selectedItem = AccentLower;
+ indexM_AL = indexM;
+ break;
+ case "Upper": // Uppercase w/o Diacritical
+ CategoryGroup.selectedItem = Upper;
+ indexM_U = indexM;
+ break;
+ case "Lower": // Lowercase w/o Diacritical
+ CategoryGroup.selectedItem = Lower;
+ indexM_L = indexM;
+ break;
+ default:
+ category = "Symbol";
+ CategoryGroup.selectedItem = Symbol;
+ indexM_S = indexM;
+ break;
+ }
+
+ ChangeCategory(category);
+ initialize = false;
+}
+
+function ChangeCategory(newCategory) {
+ if (category != newCategory || initialize) {
+ category = newCategory;
+ // Note: Must do L before M to set LatinL.selectedIndex
+ UpdateLatinL();
+ UpdateLatinM();
+ UpdateCharacter();
+ }
+}
+
+function SelectLatinLetter() {
+ if (LatinL.selectedIndex != indexL) {
+ indexL = LatinL.selectedIndex;
+ UpdateLatinM();
+ UpdateCharacter();
+ }
+}
+
+function SelectLatinModifier() {
+ if (LatinM.selectedIndex != indexM) {
+ indexM = LatinM.selectedIndex;
+ UpdateCharacter();
+ }
+}
+function DisableLatinL(disable) {
+ if (disable) {
+ LatinL_Label.setAttribute("disabled", "true");
+ LatinL.setAttribute("disabled", "true");
+ } else {
+ LatinL_Label.removeAttribute("disabled");
+ LatinL.removeAttribute("disabled");
+ }
+}
+
+function UpdateLatinL() {
+ LatinL.removeAllItems();
+ if (category == "AccentUpper" || category == "AccentLower") {
+ DisableLatinL(false);
+ // No Q or q
+ var alphabet =
+ category == "AccentUpper"
+ ? "ABCDEFGHIJKLMNOPRSTUVWXYZ"
+ : "abcdefghijklmnoprstuvwxyz";
+ for (var letter = 0; letter < alphabet.length; letter++) {
+ LatinL.appendItem(alphabet.charAt(letter));
+ }
+
+ LatinL.selectedIndex = indexL;
+ } else {
+ // Other categories don't hinge on a "letter"
+ DisableLatinL(true);
+ // Note: don't change the indexL so it can be used next time
+ }
+}
+
+function UpdateLatinM() {
+ LatinM.removeAllItems();
+ var i, accent;
+ switch (category) {
+ case "AccentUpper": // Uppercase Diacritical
+ accent = upper[indexL];
+ for (i = 0; i < accent.length; i++) {
+ LatinM.appendItem(accent.charAt(i));
+ }
+
+ if (indexM_AU < accent.length) {
+ indexM = indexM_AU;
+ } else {
+ indexM = accent.length - 1;
+ }
+ indexM_AU = indexM;
+ break;
+
+ case "AccentLower": // Lowercase Diacritical
+ accent = lower[indexL];
+ for (i = 0; i < accent.length; i++) {
+ LatinM.appendItem(accent.charAt(i));
+ }
+
+ if (indexM_AL < accent.length) {
+ indexM = indexM_AL;
+ } else {
+ indexM = lower[indexL].length - 1;
+ }
+ indexM_AL = indexM;
+ break;
+
+ case "Upper": // Uppercase w/o Diacritical
+ for (i = 0; i < otherupper.length; i++) {
+ LatinM.appendItem(otherupper.charAt(i));
+ }
+
+ if (indexM_U < otherupper.length) {
+ indexM = indexM_U;
+ } else {
+ indexM = otherupper.length - 1;
+ }
+ indexM_U = indexM;
+ break;
+
+ case "Lower": // Lowercase w/o Diacritical
+ for (i = 0; i < otherlower.length; i++) {
+ LatinM.appendItem(otherlower.charAt(i));
+ }
+
+ if (indexM_L < otherlower.length) {
+ indexM = indexM_L;
+ } else {
+ indexM = otherlower.length - 1;
+ }
+ indexM_L = indexM;
+ break;
+
+ case "Symbol": // Symbol
+ for (i = 0; i < symbol.length; i++) {
+ LatinM.appendItem(symbol.charAt(i));
+ }
+
+ if (indexM_S < symbol.length) {
+ indexM = indexM_S;
+ } else {
+ indexM = symbol.length - 1;
+ }
+ indexM_S = indexM;
+ break;
+ }
+ LatinM.selectedIndex = indexM;
+}
+
+function UpdateCharacter() {
+ indexM = LatinM.selectedIndex;
+
+ switch (category) {
+ case "AccentUpper": // Uppercase Diacritical
+ indexM_AU = indexM;
+ break;
+ case "AccentLower": // Lowercase Diacritical
+ indexM_AL = indexM;
+ break;
+ case "Upper": // Uppercase w/o Diacritical
+ indexM_U = indexM;
+ break;
+ case "Lower": // Lowercase w/o Diacritical
+ indexM_L = indexM;
+ break;
+ case "Symbol":
+ indexM_S = indexM;
+ break;
+ }
+ // dump("Letter Index="+indexL+", Character Index="+indexM+", Character = "+LatinM.label+"\n");
+}
+
+const upper = [
+ // A
+ "\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u0100\u0102\u0104\u01cd\u01de\u01de\u01e0\u01fa\u0200\u0202\u0226\u1e00\u1ea0\u1ea2\u1ea4\u1ea6\u1ea8\u1eaa\u1eac\u1eae\u1eb0\u1eb2\u1eb4\u1eb6",
+ // B
+ "\u0181\u0182\u0184\u1e02\u1e04\u1e06",
+ // C
+ "\u00c7\u0106\u0108\u010a\u010c\u0187\u1e08",
+ // D
+ "\u010e\u0110\u0189\u018a\u1e0a\u1e0c\u1e0e\u1e10\u1e12",
+ // E
+ "\u00C8\u00C9\u00CA\u00CB\u0112\u0114\u0116\u0118\u011A\u0204\u0206\u0228\u1e14\u1e16\u1e18\u1e1a\u1e1c\u1eb8\u1eba\u1ebc\u1ebe\u1ec0\u1ec2\u1ec4\u1ec6",
+ // F
+ "\u1e1e",
+ // G
+ "\u011c\u011E\u0120\u0122\u01e4\u01e6\u01f4\u1e20",
+ // H
+ "\u0124\u0126\u021e\u1e22\u1e24\u1e26\u1e28\u1e2a",
+ // I
+ "\u00CC\u00CD\u00CE\u00CF\u0128\u012a\u012C\u012e\u0130\u0208\u020a\u1e2c\u1e2e\u1ec8\u1eca",
+ // J
+ "\u0134\u01f0",
+ // K
+ "\u0136\u0198\u01e8\u1e30\u1e32\u1e34",
+ // L
+ "\u0139\u013B\u013D\u013F\u0141\u1e36\u1e38\u1e3a\u1e3c",
+ // M
+ "\u1e3e\u1e40\u1e42",
+ // N
+ "\u00D1\u0143\u0145\u0147\u014A\u01F8\u1e44\u1e46\u1e48\u1e4a",
+ // O
+ "\u00D2\u00D3\u00D4\u00D5\u00D6\u014C\u014E\u0150\u01ea\u01ec\u020c\u020e\u022A\u022C\u022E\u0230\u1e4c\u1e4e\u1e50\u1e52\u1ecc\u1ece\u1ed0\u1ed2\u1ed4\u1ed6\u1ed8\u1eda\u1edc\u1ede\u1ee0\u1ee2",
+ // P
+ "\u1e54\u1e56",
+ // No Q
+ // R
+ "\u0154\u0156\u0158\u0210\u0212\u1e58\u1e5a\u1e5c\u1e5e",
+ // S
+ "\u015A\u015C\u015E\u0160\u0218\u1e60\u1e62\u1e64\u1e66\u1e68",
+ // T
+ "\u0162\u0164\u0166\u021A\u1e6a\u1e6c\u1e6e\u1e70",
+ // U
+ "\u00D9\u00DA\u00DB\u00DC\u0168\u016A\u016C\u016E\u0170\u0172\u0214\u0216\u1e72\u1e74\u1e76\u1e78\u1e7a\u1ee4\u1ee6\u1ee8\u1eea\u1eec\u1eee\u1ef0",
+ // V
+ "\u1e7c\u1e7e",
+ // W
+ "\u0174\u1e80\u1e82\u1e84\u1e86\u1e88",
+ // X
+ "\u1e8a\u1e8c",
+ // Y
+ "\u00DD\u0176\u0178\u0232\u1e8e\u1ef2\u1ef4\u1ef6\u1ef8",
+ // Z
+ "\u0179\u017B\u017D\u0224\u1e90\u1e92\u1e94",
+];
+
+const lower = [
+ // a
+ "\u00e0\u00e1\u00e2\u00e3\u00e4\u00e5\u0101\u0103\u0105\u01ce\u01df\u01e1\u01fb\u0201\u0203\u0227\u1e01\u1e9a\u1ea1\u1ea3\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7",
+ // b
+ "\u0180\u0183\u0185\u1e03\u1e05\u1e07",
+ // c
+ "\u00e7\u0107\u0109\u010b\u010d\u0188\u1e09",
+ // d
+ "\u010f\u0111\u1e0b\u1e0d\u1e0f\u1e11\u1e13",
+ // e
+ "\u00e8\u00e9\u00ea\u00eb\u0113\u0115\u0117\u0119\u011b\u0205\u0207\u0229\u1e15\u1e17\u1e19\u1e1b\u1e1d\u1eb9\u1ebb\u1ebd\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7",
+ // f
+ "\u1e1f",
+ // g
+ "\u011d\u011f\u0121\u0123\u01e5\u01e7\u01f5\u1e21",
+ // h
+ "\u0125\u0127\u021f\u1e23\u1e25\u1e27\u1e29\u1e2b\u1e96",
+ // i
+ "\u00ec\u00ed\u00ee\u00ef\u0129\u012b\u012d\u012f\u0131\u01d0\u0209\u020b\u1e2d\u1e2f\u1ec9\u1ecb",
+ // j
+ "\u0135",
+ // k
+ "\u0137\u0138\u01e9\u1e31\u1e33\u1e35",
+ // l
+ "\u013a\u013c\u013e\u0140\u0142\u1e37\u1e39\u1e3b\u1e3d",
+ // m
+ "\u1e3f\u1e41\u1e43",
+ // n
+ "\u00f1\u0144\u0146\u0148\u0149\u014b\u01f9\u1e45\u1e47\u1e49\u1e4b",
+ // o
+ "\u00f2\u00f3\u00f4\u00f5\u00f6\u014d\u014f\u0151\u01d2\u01eb\u01ed\u020d\u020e\u022b\u022d\u022f\u0231\u1e4d\u1e4f\u1e51\u1e53\u1ecd\u1ecf\u1ed1\u1ed3\u1ed5\u1ed7\u1ed9\u1edb\u1edd\u1edf\u1ee1\u1ee3",
+ // p
+ "\u1e55\u1e57",
+ // No q
+ // r
+ "\u0155\u0157\u0159\u0211\u0213\u1e59\u1e5b\u1e5d\u1e5f",
+ // s
+ "\u015b\u015d\u015f\u0161\u0219\u1e61\u1e63\u1e65\u1e67\u1e69",
+ // t
+ "\u0162\u0163\u0165\u0167\u021b\u1e6b\u1e6d\u1e6f\u1e71\u1e97",
+ // u
+ "\u00f9\u00fa\u00fb\u00fc\u0169\u016b\u016d\u016f\u0171\u0173\u01d4\u01d6\u01d8\u01da\u01dc\u0215\u0217\u1e73\u1e75\u1e77\u1e79\u1e7b\u1ee5\u1ee7\u1ee9\u1eeb\u1eed\u1eef\u1ef1",
+ // v
+ "\u1e7d\u1e7f",
+ // w
+ "\u0175\u1e81\u1e83\u1e85\u1e87\u1e89\u1e98",
+ // x
+ "\u1e8b\u1e8d",
+ // y
+ "\u00fd\u00ff\u0177\u0233\u1e8f\u1e99\u1ef3\u1ef5\u1ef7\u1ef9",
+ // z
+ "\u017a\u017c\u017e\u0225\u1e91\u1e93\u1e95",
+];
+
+const symbol =
+ "\u00a1\u00a2\u00a3\u00a4\u00a5\u20ac\u00a6\u00a7\u00a8\u00a9\u00aa\u00ab\u00ac\u00ae\u00af\u00b0\u00b1\u00b2\u00b3\u00b4\u00b5\u00b6\u00b7\u00b8\u00b9\u00ba\u00bb\u00bc\u00bd\u00be\u00bf\u00d7\u00f7";
+
+const otherupper =
+ "\u00c6\u00d0\u00d8\u00de\u0132\u0152\u0186\u01c4\u01c5\u01c7\u01c8\u01ca\u01cb\u01F1\u01f2";
+
+const otherlower =
+ "\u00e6\u00f0\u00f8\u00fe\u00df\u0133\u0153\u01c6\u01c9\u01cc\u01f3";
diff --git a/comm/suite/editor/components/dialogs/content/EdInsertChars.xhtml b/comm/suite/editor/components/dialogs/content/EdInsertChars.xhtml
new file mode 100644
index 0000000000..4e6c1020fa
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsertChars.xhtml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorInsertChars.dtd">
+
+<dialog id="insertCharsDlg" title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload = "Startup()"
+ onfocus = "onFocus()"
+ buttonlabelaccept="&insertButton.label;"
+ buttonlabelcancel="&closeButton.label;"
+ style = "width: 20em">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdInsertChars.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&category.label;</label>
+ </hbox>
+ <radiogroup id="CatGrp" persist="category letter_index char_index">
+ <radio id="AccentUpper" label="&accentUpper.label;" oncommand="ChangeCategory(this.id)"/>
+ <radio id="AccentLower" label="&accentLower.label;" oncommand="ChangeCategory(this.id)"/>
+ <radio id="Upper" label="&otherUpper.label;" oncommand="ChangeCategory(this.id)"/>
+ <radio id="Lower" label="&otherLower.label;" oncommand="ChangeCategory(this.id)"/>
+ <radio id="Symbol" label="&commonSymbols.label;" oncommand="ChangeCategory(this.id)"/>
+ </radiogroup>
+ <spacer class="spacer"/>
+ </groupbox>
+ <hbox equalsize="always">
+ <vbox flex="1">
+ <!-- value is set in JS from editor.properties strings -->
+ <label id="LatinL_Label" control="LatinL" value="&letter.label;" accesskey="&letter.accessKey;"/>
+ <menulist class="larger" flex="1" id="LatinL" oncommand="SelectLatinLetter()">
+ <menupopup/>
+ </menulist>
+ </vbox>
+ <vbox flex="1">
+ <label id="LatinM_Label" control="LatinM" value="&character.label;" accesskey="&character.accessKey;"/>
+ <menulist class="larger" flex="1" id="LatinM" oncommand="SelectLatinModifier()">
+ <menupopup/>
+ </menulist>
+ </vbox>
+ </hbox>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdInsertMath.js b/comm/suite/editor/components/dialogs/content/EdInsertMath.js
new file mode 100644
index 0000000000..c99bf8edac
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsertMath.js
@@ -0,0 +1,330 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Insert MathML dialog */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ // Create dialog object for easy access
+ gDialog.accept = document.documentElement.getButton("accept");
+ gDialog.mode = document.getElementById("optionMode");
+ gDialog.direction = document.getElementById("optionDirection");
+ gDialog.input = document.getElementById("input");
+ gDialog.output = document.getElementById("output");
+ gDialog.tabbox = document.getElementById("tabboxInsertLaTeXCommand");
+
+ // Set initial focus
+ gDialog.input.focus();
+
+ // Load TeXZilla
+ // TeXZilla.js contains non-ASCII characters and explicitly sets
+ // window.TeXZilla, so we have to specify the charset parameter but don't
+ // need to worry about the targetObj parameter.
+ /* globals TeXZilla */
+ Services.scriptloader.loadSubScript(
+ "chrome://editor/content/TeXZilla.js",
+ {},
+ "UTF-8"
+ );
+
+ // Verify if the selection is on a <math> and initialize the dialog.
+ gDialog.oldMath = editor.getElementOrParentByTagName("math", null);
+ if (gDialog.oldMath) {
+ // When these attributes are absent or invalid, they default to "inline" and "ltr" respectively.
+ gDialog.mode.selectedIndex =
+ gDialog.oldMath.getAttribute("display") == "block" ? 1 : 0;
+ gDialog.direction.selectedIndex =
+ gDialog.oldMath.getAttribute("dir") == "rtl" ? 1 : 0;
+ gDialog.input.value = TeXZilla.getTeXSource(gDialog.oldMath);
+ }
+
+ // Create the tabbox with LaTeX commands.
+ createCommandPanel({
+ "√⅗²": [
+ "{⋯}^{⋯}",
+ "{⋯}_{⋯}",
+ "{⋯}_{⋯}^{⋯}",
+ "\\underset{⋯}{⋯}",
+ "\\overset{⋯}{⋯}",
+ "\\underoverset{⋯}{⋯}{⋯}",
+ "\\left(⋯\\right)",
+ "\\left[⋯\\right]",
+ "\\frac{⋯}{⋯}",
+ "\\binom{⋯}{⋯}",
+ "\\sqrt{⋯}",
+ "\\sqrt[⋯]{⋯}",
+ "\\cos\\left({⋯}\\right)",
+ "\\sin\\left({⋯}\\right)",
+ "\\tan\\left({⋯}\\right)",
+ "\\exp\\left({⋯}\\right)",
+ "\\ln\\left({⋯}\\right)",
+ "\\underbrace{⋯}",
+ "\\underline{⋯}",
+ "\\overbrace{⋯}",
+ "\\widevec{⋯}",
+ "\\widetilde{⋯}",
+ "\\widehat{⋯}",
+ "\\widecheck{⋯}",
+ "\\widebar{⋯}",
+ "\\dot{⋯}",
+ "\\ddot{⋯}",
+ "\\boxed{⋯}",
+ "\\slash{⋯}",
+ ],
+ "(▦)": [
+ "\\begin{matrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{matrix}",
+ "\\begin{pmatrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{pmatrix}",
+ "\\begin{bmatrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{bmatrix}",
+ "\\begin{Bmatrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{Bmatrix}",
+ "\\begin{vmatrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{vmatrix}",
+ "\\begin{Vmatrix} ⋯ & ⋯ \\\\ ⋯ & ⋯ \\end{Vmatrix}",
+ "\\begin{cases} ⋯ \\\\ ⋯ \\end{cases}",
+ "\\begin{aligned} ⋯ &= ⋯ \\\\ ⋯ &= ⋯ \\end{aligned}",
+ ],
+ });
+ createSymbolPanels([
+ "∏∐∑∫∬∭⨌∮⊎⊕⊖⊗⊘⊙⋀⋁⋂⋃⌈⌉⌊⌋⎰⎱⟨⟩⟪⟫∥⫼⨀⨁⨂⨄⨅⨆ðıȷℏℑℓ℘ℜℵℶ",
+ "∀∃∄∅∉∊∋∌⊂⊃⊄⊅⊆⊇⊈⊈⊉⊊⊊⊋⊋⊏⊐⊑⊒⊓⊔⊥⋐⋑⋔⫅⫆⫋⫋⫌⫌…⋮⋯⋰⋱♭♮♯∂∇",
+ "±×÷†‡•∓∔∗∘∝∠∡∢∧∨∴∵∼∽≁≃≅≇≈≈≊≍≎≏≐≑≒≓≖≗≜≡≢≬⊚⊛⊞⊡⊢⊣⊤⊥",
+ "⊨⊩⊪⊫⊬⊭⊯⊲⊲⊳⊴⊵⊸⊻⋄⋅⋇⋈⋉⋊⋋⋌⋍⋎⋏⋒⋓⌅⌆⌣△▴▵▸▹▽▾▿◂◃◊○★♠♡♢♣⧫",
+ "≦≧≨≩≩≪≫≮≯≰≱≲≳≶≷≺≻≼≽≾≿⊀⊁⋖⋗⋘⋙⋚⋛⋞⋟⋦⋧⋨⋩⩽⩾⪅⪆⪇⪈⪉⪊⪋⪌⪕⪯⪰⪷⪸⪹⪺",
+ "←↑→↓↔↕↖↗↘↙↜↝↞↠↢↣↦↩↪↫↬↭↭↰↱↼↽↾↿⇀⇁⇂⇃⇄⇆⇇⇈⇉⇊⇋⇌⇐⇑⇒⇓⇕⇖⇗⇘⇙⟺",
+ "αβγδϵ϶εζηθϑικϰλμνξℴπϖρϱσςτυϕφχψωΓΔΘΛΞΠΣϒΦΨΩϝ℧",
+ "𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ",
+ "𝒶𝒷𝒸𝒹ℯ𝒻ℊ𝒽𝒾𝒿𝓀𝓁𝓂𝓃ℴ𝓅𝓆𝓇𝓈𝓉𝓊𝓋𝓌𝓍𝓎𝓏𝒜ℬ𝒞𝒟ℰℱ𝒢ℋℐ𝒥𝒦ℒℳ𝒩𝒪𝒫𝒬ℛ𝒮𝒯𝒰𝒱𝒲𝒳𝒴𝒵",
+ "𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ",
+ ]);
+ gDialog.tabbox.selectedIndex = 0;
+
+ updateMath();
+
+ SetWindowLocation();
+}
+
+function insertLaTeXCommand(aButton) {
+ gDialog.input.focus();
+
+ // For a single math symbol, just use the insertText command.
+ if (aButton.label) {
+ gDialog.input.editor.insertText(aButton.label);
+ return;
+ }
+
+ // Otherwise, it's a LaTeX command with at least one argument...
+ var latex = TeXZilla.getTeXSource(aButton.firstChild);
+ var selectionStart = gDialog.input.selectionStart;
+ var selectionEnd = gDialog.input.selectionEnd;
+
+ // If the selection is not empty, we replace the first argument of the LaTeX
+ // command with the current selection.
+ var selection = gDialog.input.value.substring(selectionStart, selectionEnd);
+ if (selection != "") {
+ latex = latex.replace("⋯", selection);
+ }
+
+ // Try and move to the next position.
+ var latexNewStart = latex.indexOf("⋯"),
+ latexNewEnd;
+ if (latexNewStart == -1) {
+ // This is a unary function and the selection was used as an argument above.
+ // We select the expression again so that one can choose to apply further
+ // command to it or just move the caret after that text.
+ latexNewStart = 0;
+ latexNewEnd = latex.length;
+ } else {
+ // Otherwise, select the dots representing the next argument.
+ latexNewEnd = latexNewStart + 1;
+ }
+
+ // Update the input text and selection.
+ gDialog.input.editor.insertText(latex);
+ gDialog.input.setSelectionRange(
+ selectionStart + latexNewStart,
+ selectionStart + latexNewEnd
+ );
+
+ updateMath();
+}
+
+function createCommandPanel(aCommandPanelList) {
+ const columnCount = 10;
+
+ for (var label in aCommandPanelList) {
+ var commands = aCommandPanelList[label];
+
+ // Create a <rows> element with some LaTeX commands.
+ var rows = document.createXULElement("rows");
+
+ var i = 0,
+ row;
+ for (var command of commands) {
+ if (i % columnCount == 0) {
+ // Create a new row.
+ row = document.createXULElement("row");
+ rows.appendChild(row);
+ }
+
+ // Create a new button to insert the symbol.
+ var button = document.createXULElement("toolbarbutton");
+ button.setAttribute("class", "tabbable");
+ button.appendChild(TeXZilla.toMathML(command));
+ row.appendChild(button);
+
+ i++;
+ }
+
+ // Create a <columns> element with the desired number of columns.
+ var columns = document.createXULElement("columns");
+ for (i = 0; i < columnCount; i++) {
+ var column = document.createXULElement("column");
+ column.setAttribute("flex", "1");
+ columns.appendChild(column);
+ }
+
+ // Create the <grid> element with the <rows> and <columns> children.
+ var grid = document.createXULElement("grid");
+ grid.appendChild(columns);
+ grid.appendChild(rows);
+
+ // Create a new <tab> element.
+ var tab = document.createXULElement("tab");
+ tab.setAttribute("label", label);
+ gDialog.tabbox.tabs.appendChild(tab);
+
+ // Append the new tab panel.
+ gDialog.tabbox.tabpanels.appendChild(grid);
+ }
+}
+
+function createSymbolPanels(aSymbolPanelList) {
+ const columnCount = 13,
+ tabLabelLength = 3;
+
+ for (var symbols of aSymbolPanelList) {
+ // Create a <rows> element with the symbols of the i-th panel.
+ var rows = document.createXULElement("rows");
+ var i = 0,
+ tabLabel = "",
+ row;
+ for (var symbol of symbols) {
+ if (i % columnCount == 0) {
+ // Create a new row.
+ row = document.createXULElement("row");
+ rows.appendChild(row);
+ }
+
+ // Build the tab label from the first symbols of this tab.
+ if (i < tabLabelLength) {
+ tabLabel += symbol;
+ }
+
+ // Create a new button to insert the symbol.
+ var button = document.createXULElement("toolbarbutton");
+ button.setAttribute("label", symbol);
+ button.setAttribute("class", "tabbable");
+ row.appendChild(button);
+
+ i++;
+ }
+
+ // Create a <columns> element with the desired number of columns.
+ var columns = document.createXULElement("columns");
+ for (i = 0; i < columnCount; i++) {
+ var column = document.createXULElement("column");
+ column.setAttribute("flex", "1");
+ columns.appendChild(column);
+ }
+
+ // Create the <grid> element with the <rows> and <columns> children.
+ var grid = document.createXULElement("grid");
+ grid.appendChild(columns);
+ grid.appendChild(rows);
+
+ // Create a new <tab> element with the label determined above.
+ var tab = document.createXULElement("tab");
+ tab.setAttribute("label", tabLabel);
+ gDialog.tabbox.tabs.appendChild(tab);
+
+ // Append the new tab panel.
+ gDialog.tabbox.tabpanels.appendChild(grid);
+ }
+}
+
+function onAccept(event) {
+ if (gDialog.output.firstChild) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+
+ try {
+ var newMath = editor.document.importNode(gDialog.output.firstChild, true);
+ if (gDialog.oldMath) {
+ // Replace the old <math> element with the new one.
+ editor.selectElement(gDialog.oldMath);
+ editor.insertElementAtSelection(newMath, true);
+ } else {
+ // Insert the new <math> element.
+ editor.insertElementAtSelection(newMath, false);
+ }
+ } catch (e) {}
+
+ editor.endTransaction();
+ } else {
+ dump("Null value -- not inserting in MathML Source dialog\n");
+ event.preventDefault();
+ }
+ SaveWindowLocation();
+}
+
+function updateMath() {
+ // Remove the preview, if any.
+ if (gDialog.output.firstChild) {
+ gDialog.output.firstChild.remove();
+ }
+
+ // Try to convert the LaTeX source into MathML using TeXZilla.
+ // We use the placeholder text if no input is provided.
+ try {
+ var input = gDialog.input.value || gDialog.input.placeholder;
+ var newMath = TeXZilla.toMathML(
+ input,
+ gDialog.mode.selectedIndex,
+ gDialog.direction.selectedIndex,
+ true
+ );
+ gDialog.output.appendChild(document.importNode(newMath, true));
+ gDialog.output.style.opacity = gDialog.input.value ? 1 : 0.5;
+ } catch (e) {}
+ // Disable the accept button if parsing fails or when the placeholder is used.
+ gDialog.accept.disabled = !gDialog.input.value || !gDialog.output.firstChild;
+}
+
+function updateMode() {
+ if (gDialog.output.firstChild) {
+ gDialog.output.firstChild.setAttribute(
+ "display",
+ gDialog.mode.selectedIndex ? "block" : "inline"
+ );
+ }
+}
+
+function updateDirection() {
+ if (gDialog.output.firstChild) {
+ gDialog.output.firstChild.setAttribute(
+ "dir",
+ gDialog.direction.selectedIndex ? "rtl" : "ltr"
+ );
+ }
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdInsertMath.xhtml b/comm/suite/editor/components/dialogs/content/EdInsertMath.xhtml
new file mode 100644
index 0000000000..9138d00846
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsertMath.xhtml
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+- License, v. 2.0. If a copy of the MPL was not distributed with this
+- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorInsertMath.dtd">
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ buttonlabelaccept="&insertButton.label;"
+ buttonaccesskeyaccept="&insertButton.accesskey;">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://global/content/globalOverlay.js"/>
+ <script src="chrome://global/content/editMenuOverlay.js"/>
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdInsertMath.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <label id="srcMessage" value="&sourceEditField.label;"/>
+ <html:textarea id="input" rows="5" oninput="updateMath();"
+ placeholder="\sqrt{x_1} + \frac{π^3}{2}"/>
+ <vbox flex="1" style="overflow: auto; width: 30em; height: 5em;">
+ <description id="output"/>
+ </vbox>
+ <tabbox id="tabboxInsertLaTeXCommand">
+ <tabs/>
+ <tabpanels oncommand="insertLaTeXCommand(event.target);"/>
+ </tabbox>
+ <spacer class="spacer"/>
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&options.label;</label>
+ </hbox>
+ <hbox>
+ <radiogroup id="optionMode" oncommand="updateMode();">
+ <radio label="&optionInline.label;"
+ accesskey="&optionInline.accesskey;"/>
+ <radio label="&optionDisplay.label;"
+ accesskey="&optionDisplay.accesskey;"/>
+ </radiogroup>
+ <radiogroup id="optionDirection" oncommand="updateDirection();">
+ <radio label="&optionLTR.label;"
+ accesskey="&optionLTR.accesskey;"/>
+ <radio label="&optionRTL.label;"
+ accesskey="&optionRTL.accesskey;"/>
+ </radiogroup>
+ </hbox>
+ </groupbox>
+ <spacer class="spacer"/>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdInsertTOC.js b/comm/suite/editor/components/dialogs/content/EdInsertTOC.js
new file mode 100644
index 0000000000..3ec386f7c9
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsertTOC.js
@@ -0,0 +1,378 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// tocHeadersArray is the array containing the pairs tag/class
+// defining TOC entries
+var tocHeadersArray = new Array(6);
+
+// a global used when building the TOC
+var currentHeaderLevel = 0;
+
+// a global set to true if the TOC is to be readonly
+var readonly = false;
+
+// a global set to true if user wants indexes in the TOC
+var orderedList = true;
+
+// constants
+const kMozToc = "mozToc";
+const kMozTocLength = 6;
+const kMozTocIdPrefix = "mozTocId";
+const kMozTocIdPrefixLength = 8;
+const kMozTocClassPrefix = "mozToc";
+const kMozTocClassPrefixLength = 6;
+
+document.addEventListener("dialogaccept", () => BuildTOC(true));
+
+// Startup() is called when EdInsertTOC.xhtml is opened
+function Startup() {
+ // early way out if if we have no editor
+ if (!GetCurrentEditor()) {
+ window.close();
+ return;
+ }
+
+ var i;
+ // clean the table of tag/class pairs we look for
+ for (i = 0; i < 6; ++i) {
+ tocHeadersArray[i] = ["", ""];
+ }
+
+ // reset all settings
+ for (i = 1; i < 7; ++i) {
+ var menulist = document.getElementById("header" + i + "Menulist");
+ var menuitem = document.getElementById("header" + i + "none");
+ var textbox = document.getElementById("header" + i + "Class");
+ menulist.selectedItem = menuitem;
+ textbox.setAttribute("disabled", "true");
+ }
+
+ var theDocument = GetCurrentEditor().document;
+
+ // do we already have a TOC in the document ? It should have "mozToc" ID
+ var toc = theDocument.getElementById(kMozToc);
+
+ // default TOC definition, use h1-h6 for TOC entry levels 1-6
+ var headers = "h1 1 h2 2 h3 3 h4 4 h5 5 h6 6";
+
+ var orderedListCheckbox = document.getElementById("orderedListCheckbox");
+ orderedListCheckbox.checked = true;
+
+ if (toc) {
+ // man, there is already a TOC here
+
+ if (toc.getAttribute("class") == "readonly") {
+ // and it's readonly
+ var checkbox = document.getElementById("readOnlyCheckbox");
+ checkbox.checked = true;
+ readonly = true;
+ }
+
+ // let's see if it's an OL or an UL
+ orderedList = toc.nodeName.toLowerCase() == "ol";
+ orderedListCheckbox.checked = orderedList;
+
+ var nodeList = toc.childNodes;
+ // let's look at the children of the TOC ; if we find a comment beginning
+ // with "mozToc", it contains the TOC definition
+ for (i = 0; i < nodeList.length; ++i) {
+ if (
+ nodeList.item(i).nodeType == Node.COMMENT_NODE &&
+ nodeList.item(i).data.startsWith(kMozToc)
+ ) {
+ // yep, there is already a definition here; parse it !
+ headers = nodeList
+ .item(i)
+ .data.substr(
+ kMozTocLength + 1,
+ nodeList.item(i).length - kMozTocLength - 1
+ );
+ break;
+ }
+ }
+ }
+
+ // let's get an array filled with the (tag.class, index level) pairs
+ var headersArray = headers.split(" ");
+
+ for (i = 0; i < headersArray.length; i += 2) {
+ var tag = headersArray[i],
+ className = "";
+ var index = headersArray[i + 1];
+ menulist = document.getElementById("header" + index + "Menulist");
+ if (menulist) {
+ var sep = tag.indexOf(".");
+ if (sep != -1) {
+ // the tag variable contains in fact "tag.className", let's parse
+ // the class and get the real tag name
+ var tmp = tag.substr(0, sep);
+ className = tag.substr(sep + 1, tag.length - sep - 1);
+ tag = tmp;
+ }
+
+ // update the dialog
+ menuitem = document.getElementById("header" + index + tag.toUpperCase());
+ textbox = document.getElementById("header" + index + "Class");
+ menulist.selectedItem = menuitem;
+ if (tag != "") {
+ textbox.removeAttribute("disabled");
+ }
+ if (className != "") {
+ textbox.value = className;
+ }
+ tocHeadersArray[index - 1] = [tag, className];
+ }
+ }
+}
+
+function BuildTOC(update) {
+ // controlClass() is a node filter that accepts a node if
+ // (a) we don't look for a class (b) we look for a class and
+ // node has it
+ function controlClass(node, index) {
+ currentHeaderLevel = index + 1;
+ if (tocHeadersArray[index][1] == "") {
+ // we are not looking for a specific class, this node is ok
+ return NodeFilter.FILTER_ACCEPT;
+ }
+ if (node.getAttribute("class")) {
+ // yep, we look for a class, let's look at all the classes
+ // the node has
+ var classArray = node.getAttribute("class").split(" ");
+ for (var j = 0; j < classArray.length; j++) {
+ if (classArray[j] == tocHeadersArray[index][1]) {
+ // hehe, we found it...
+ return NodeFilter.FILTER_ACCEPT;
+ }
+ }
+ }
+ return NodeFilter.FILTER_SKIP;
+ }
+
+ // the main node filter for our node iterator
+ // it selects the tag names as specified in the dialog
+ // then calls the controlClass filter above
+ function acceptNode(node) {
+ switch (node.nodeName.toLowerCase()) {
+ case tocHeadersArray[0][0]:
+ return controlClass(node, 0);
+ case tocHeadersArray[1][0]:
+ return controlClass(node, 1);
+ case tocHeadersArray[2][0]:
+ return controlClass(node, 2);
+ case tocHeadersArray[3][0]:
+ return controlClass(node, 3);
+ case tocHeadersArray[4][0]:
+ return controlClass(node, 4);
+ case tocHeadersArray[5][0]:
+ return controlClass(node, 5);
+ default:
+ return NodeFilter.FILTER_SKIP;
+ }
+ }
+
+ var editor = GetCurrentEditor();
+ var theDocument = editor.document;
+ // let's create a TreeWalker to look for our nodes
+ var treeWalker = theDocument.createTreeWalker(
+ theDocument.documentElement,
+ NodeFilter.SHOW_ELEMENT,
+ acceptNode,
+ true
+ );
+ // we need an array to store all TOC entries we find in the document
+ var tocArray = [];
+ if (treeWalker) {
+ var tocSourceNode = treeWalker.nextNode();
+ while (tocSourceNode) {
+ var headerIndex = currentHeaderLevel;
+
+ // we have a node, we need to get all its textual contents
+ var textTreeWalker = theDocument.createTreeWalker(
+ tocSourceNode,
+ NodeFilter.SHOW_TEXT,
+ null,
+ true
+ );
+ var textNode = textTreeWalker.nextNode(),
+ headerText = "";
+ while (textNode) {
+ headerText += textNode.data;
+ textNode = textTreeWalker.nextNode();
+ }
+
+ var anchor = tocSourceNode.firstChild,
+ id;
+ // do we have a named anchor as 1st child of our node ?
+ if (
+ anchor.nodeName.toLowerCase() == "a" &&
+ anchor.hasAttribute("name") &&
+ anchor.getAttribute("name").startsWith(kMozTocIdPrefix)
+ ) {
+ // yep, get its name
+ id = anchor.getAttribute("name");
+ } else {
+ // no we don't and we need to create one
+ anchor = theDocument.createElement("a");
+ tocSourceNode.insertBefore(anchor, tocSourceNode.firstChild);
+ // let's give it a random ID
+ var c = 1000000 * Math.random();
+ id = kMozTocIdPrefix + Math.round(c);
+ anchor.setAttribute("name", id);
+ anchor.setAttribute(
+ "class",
+ kMozTocClassPrefix + tocSourceNode.nodeName.toUpperCase()
+ );
+ }
+ // and store that new entry in our array
+ tocArray.push(headerIndex, headerText, id);
+ tocSourceNode = treeWalker.nextNode();
+ }
+ }
+
+ /* generate the TOC itself */
+ headerIndex = 0;
+ var item, toc;
+ for (var i = 0; i < tocArray.length; i += 3) {
+ if (!headerIndex) {
+ // do we need to create an ol/ul container for the first entry ?
+ ++headerIndex;
+ toc = theDocument.getElementById(kMozToc);
+ if (!toc || !update) {
+ // we need to create a list container for the table of contents
+ toc = GetCurrentEditor().createElementWithDefaults(
+ orderedList ? "ol" : "ul"
+ );
+ // grrr, we need to create a LI inside the list otherwise
+ // Composer will refuse an empty list and will remove it !
+ var pit = theDocument.createElement("li");
+ toc.appendChild(pit);
+ GetCurrentEditor().insertElementAtSelection(toc, true);
+ // ah, now it's inserted so let's remove the useless list item...
+ toc.removeChild(pit);
+ // we need to recognize later that this list is our TOC
+ toc.setAttribute("id", kMozToc);
+ } else if (orderedList != (toc.nodeName.toLowerCase() == "ol")) {
+ // we have to update an existing TOC, is the existing TOC of the
+ // desired type (ordered or not) ?
+
+ // nope, we have to recreate the list
+ var newToc = GetCurrentEditor().createElementWithDefaults(
+ orderedList ? "ol" : "ul"
+ );
+ toc.parentNode.insertBefore(newToc, toc);
+ // and remove the old one
+ toc.remove();
+ toc = newToc;
+ toc.setAttribute("id", kMozToc);
+ } else {
+ // we can keep the list itself but let's get rid of the TOC entries
+ while (toc.hasChildNodes()) {
+ toc.lastChild.remove();
+ }
+ }
+
+ var commentText = "mozToc ";
+ for (var j = 0; j < 6; j++) {
+ if (tocHeadersArray[j][0] != "") {
+ commentText += tocHeadersArray[j][0];
+ if (tocHeadersArray[j][1] != "") {
+ commentText += "." + tocHeadersArray[j][1];
+ }
+ commentText += " " + (j + 1) + " ";
+ }
+ }
+ // important, we have to remove trailing spaces
+ commentText = TrimStringRight(commentText);
+
+ // forge a comment we'll insert in the TOC ; that comment will hold
+ // the TOC definition for us
+ var ct = theDocument.createComment(commentText);
+ toc.appendChild(ct);
+
+ // assign a special class to the TOC top element if the TOC is readonly
+ // the definition of this class is in EditorOverride.css
+ if (readonly) {
+ toc.setAttribute("class", "readonly");
+ } else {
+ toc.removeAttribute("class");
+ }
+
+ // We need a new variable to hold the local ul/ol container
+ // The toplevel TOC element is not the parent element of a
+ // TOC entry if its depth is > 1...
+ var tocList = toc;
+ // create a list item
+ var tocItem = theDocument.createElement("li");
+ // and an anchor in this list item
+ var tocAnchor = theDocument.createElement("a");
+ // make it target the source of the TOC entry
+ tocAnchor.setAttribute("href", "#" + tocArray[i + 2]);
+ // and put the textual contents of the TOC entry in that anchor
+ var tocEntry = theDocument.createTextNode(tocArray[i + 1]);
+ // now, insert everything where it has to be inserted
+ tocAnchor.appendChild(tocEntry);
+ tocItem.appendChild(tocAnchor);
+ tocList.appendChild(tocItem);
+ item = tocList;
+ } else {
+ if (tocArray[i] < headerIndex) {
+ // if the depth of the new TOC entry is less than the depth of the
+ // last entry we created, find the good ul/ol ancestor
+ for (j = headerIndex - tocArray[i]; j > 0; --j) {
+ if (item != toc) {
+ item = item.parentNode.parentNode;
+ }
+ }
+ tocItem = theDocument.createElement("li");
+ } else if (tocArray[i] > headerIndex) {
+ // to the contrary, it's deeper than the last one
+ // we need to create sub ul/ol's and li's
+ for (j = tocArray[i] - headerIndex; j > 0; --j) {
+ tocList = theDocument.createElement(orderedList ? "ol" : "ul");
+ item.lastChild.appendChild(tocList);
+ tocItem = theDocument.createElement("li");
+ tocList.appendChild(tocItem);
+ item = tocList;
+ }
+ } else {
+ tocItem = theDocument.createElement("li");
+ }
+ tocAnchor = theDocument.createElement("a");
+ tocAnchor.setAttribute("href", "#" + tocArray[i + 2]);
+ tocEntry = theDocument.createTextNode(tocArray[i + 1]);
+ tocAnchor.appendChild(tocEntry);
+ tocItem.appendChild(tocAnchor);
+ item.appendChild(tocItem);
+ headerIndex = tocArray[i];
+ }
+ }
+ SaveWindowLocation();
+}
+
+function selectHeader(elt, index) {
+ var tag = elt.value;
+ tocHeadersArray[index - 1][0] = tag;
+ var textbox = document.getElementById("header" + index + "Class");
+ if (tag == "") {
+ textbox.setAttribute("disabled", "true");
+ } else {
+ textbox.removeAttribute("disabled");
+ }
+}
+
+function changeClass(elt, index) {
+ tocHeadersArray[index - 1][1] = elt.value;
+}
+
+function ToggleReadOnlyToc(elt) {
+ readonly = elt.checked;
+}
+
+function ToggleOrderedList(elt) {
+ orderedList = elt.checked;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdInsertTOC.xhtml b/comm/suite/editor/components/dialogs/content/EdInsertTOC.xhtml
new file mode 100644
index 0000000000..8d82bac046
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsertTOC.xhtml
@@ -0,0 +1,225 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorInsertTOC.dtd">
+
+<dialog title="&Window.title;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ oncancel="window.close(); return true;">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdInsertTOC.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+ <spacer id="dummy" style="display:none"/>
+ <vbox flex="1">
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&buildToc.label;</label>
+ </hbox>
+ <grid>
+ <columns><column/><column style="min-width: 6em"/><column/></columns>
+ <rows>
+ <row align="center">
+ <spacer/>
+ <label value="&tag.label;"/>
+ <label value="&class.label;"/>
+ </row>
+ <row align="center">
+ <label value="&header1.label;"/>
+ <menulist id="header1Menulist">
+ <menupopup>
+ <menuitem id="header1none" label="--" value=""
+ oncommand="selectHeader(this, 1)"/>
+ <menuseparator/>
+ <menuitem id="header1H1" label="h1" value="h1"
+ oncommand="selectHeader(this, 1)"/>
+ <menuitem id="header1H2" label="h2" value="h2"
+ oncommand="selectHeader(this, 1)"/>
+ <menuitem id="header1H3" label="h3" value="h3"
+ oncommand="selectHeader(this, 1)"/>
+ <menuitem id="header1H4" label="h4" value="h4"
+ oncommand="selectHeader(this, 1)"/>
+ <menuitem id="header1H5" label="h5" value="h5"
+ oncommand="selectHeader(this, 1)"/>
+ <menuitem id="header1H6" label="h6" value="h6"
+ oncommand="selectHeader(this, 1)"/>
+ <menuitem id="header1DIV" label="div" value="div"
+ oncommand="selectHeader(this, 1)"/>
+ <menuitem id="header1P" label="p" value="p"
+ oncommand="selectHeader(this, 1)"/>
+ </menupopup>
+ </menulist>
+ <textbox id="header1Class" size="10"
+ oninput="changeClass(this, 1)"/>
+ </row>
+
+ <row align="center">
+ <label value="&header2.label;"/>
+ <menulist id="header2Menulist">
+ <menupopup>
+ <menuitem id="header2none" label="--" value=""
+ oncommand="selectHeader(this, 2)"/>
+ <menuseparator/>
+ <menuitem id="header2H1" label="h1" value="h1"
+ oncommand="selectHeader(this, 2)"/>
+ <menuitem id="header2H2" label="h2" value="h2"
+ oncommand="selectHeader(this, 2)"/>
+ <menuitem id="header2H3" label="h3" value="h3"
+ oncommand="selectHeader(this, 2)"/>
+ <menuitem id="header2H4" label="h4" value="h4"
+ oncommand="selectHeader(this, 2)"/>
+ <menuitem id="header2H5" label="h5" value="h5"
+ oncommand="selectHeader(this, 2)"/>
+ <menuitem id="header2H6" label="h6" value="h6"
+ oncommand="selectHeader(this, 2)"/>
+ <menuitem id="header2DIV" label="div" value="div"
+ oncommand="selectHeader(this, 2)"/>
+ <menuitem id="header2P" label="p" value="p"
+ oncommand="selectHeader(this, 2)"/>
+ </menupopup>
+ </menulist>
+ <textbox id="header2Class" size="10"
+ oninput="changeClass(this, 2)"/>
+ </row>
+
+ <row align="center">
+ <label value="&header3.label;"/>
+ <menulist id="header3Menulist">
+ <menupopup>
+ <menuitem id="header3none" label="--" value=""
+ oncommand="selectHeader(this, 3)"/>
+ <menuseparator/>
+ <menuitem id="header3H1" label="h1" value="h1"
+ oncommand="selectHeader(this, 3)"/>
+ <menuitem id="header3H2" label="h2" value="h2"
+ oncommand="selectHeader(this, 3)"/>
+ <menuitem id="header3H3" label="h3" value="h3"
+ oncommand="selectHeader(this, 3)"/>
+ <menuitem id="header3H4" label="h4" value="h4"
+ oncommand="selectHeader(this, 3)"/>
+ <menuitem id="header3H5" label="h5" value="h5"
+ oncommand="selectHeader(this, 3)"/>
+ <menuitem id="header3H6" label="h6" value="h6"
+ oncommand="selectHeader(this, 3)"/>
+ <menuitem id="header3DIV" label="div" value="div"
+ oncommand="selectHeader(this, 3)"/>
+ <menuitem id="header3P" label="p" value="p"
+ oncommand="selectHeader(this, 3)"/>
+ </menupopup>
+ </menulist>
+ <textbox id="header3Class" size="10"
+ oninput="changeClass(this, 3)"/>
+ </row>
+
+ <row align="center">
+ <label value="&header4.label;"/>
+ <menulist id="header4Menulist">
+ <menupopup>
+ <menuitem id="header4none" label="--" value=""
+ oncommand="selectHeader(this, 4)"/>
+ <menuseparator/>
+ <menuitem id="header4H1" label="h1" value="h1"
+ oncommand="selectHeader(this, 4)"/>
+ <menuitem id="header4H2" label="h2" value="h2"
+ oncommand="selectHeader(this, 4)"/>
+ <menuitem id="header4H3" label="h3" value="h3"
+ oncommand="selectHeader(this, 4)"/>
+ <menuitem id="header4H4" label="h4" value="h4"
+ oncommand="selectHeader(this, 4)"/>
+ <menuitem id="header4H5" label="h5" value="h5"
+ oncommand="selectHeader(this, 4)"/>
+ <menuitem id="header4H6" label="h6" value="h6"
+ oncommand="selectHeader(this, 4)"/>
+ <menuitem id="header4DIV" label="div" value="div"
+ oncommand="selectHeader(this, 4)"/>
+ <menuitem id="header4P" label="p" value="p"
+ oncommand="selectHeader(this, 4)"/>
+ </menupopup>
+ </menulist>
+ <textbox id="header4Class" size="10"
+ oninput="changeClass(this, 4)"/>
+ </row>
+
+ <row align="center">
+ <label value="&header5.label;"/>
+ <menulist id="header5Menulist">
+ <menupopup>
+ <menuitem id="header5none" label="--" value=""
+ oncommand="selectHeader(this, 5)"/>
+ <menuseparator/>
+ <menuitem id="header5H1" label="h1" value="h1"
+ oncommand="selectHeader(this, 5)"/>
+ <menuitem id="header5H2" label="h2" value="h2"
+ oncommand="selectHeader(this, 5)"/>
+ <menuitem id="header5H3" label="h3" value="h3"
+ oncommand="selectHeader(this, 5)"/>
+ <menuitem id="header5H4" label="h4" value="h4"
+ oncommand="selectHeader(this, 5)"/>
+ <menuitem id="header5H5" label="h5" value="h5"
+ oncommand="selectHeader(this, 5)"/>
+ <menuitem id="header5H6" label="h6" value="h6"
+ oncommand="selectHeader(this, 5)"/>
+ <menuitem id="header5DIV" label="div" value="div"
+ oncommand="selectHeader(this, 5)"/>
+ <menuitem id="header5P" label="p" value="p"
+ oncommand="selectHeader(this, 5)"/>
+ </menupopup>
+ </menulist>
+ <textbox id="header5Class" size="10"
+ oninput="changeClass(this, 5)"/>
+ </row>
+
+ <row align="center">
+ <label value="&header6.label;"/>
+ <menulist id="header6Menulist">
+ <menupopup>
+ <menuitem id="header6none" label="--" value=""
+ oncommand="selectHeader(this, 6)"/>
+ <menuseparator/>
+ <menuitem id="header6H1" label="h1" value="h1"
+ oncommand="selectHeader(this, 6)"/>
+ <menuitem id="header6H2" label="h2" value="h2"
+ oncommand="selectHeader(this, 6)"/>
+ <menuitem id="header6H3" label="h3" value="h3"
+ oncommand="selectHeader(this, 6)"/>
+ <menuitem id="header6H4" label="h4" value="h4"
+ oncommand="selectHeader(this, 6)"/>
+ <menuitem id="header6H5" label="h5" value="h5"
+ oncommand="selectHeader(this, 6)"/>
+ <menuitem id="header6H6" label="h6" value="h6"
+ oncommand="selectHeader(this, 6)"/>
+ <menuitem id="header6DIV" label="div" value="div"
+ oncommand="selectHeader(this, 6)"/>
+ <menuitem id="header6P" label="p" value="p"
+ oncommand="selectHeader(this, 6)"/>
+ </menupopup>
+ </menulist>
+ <textbox id="header6Class" size="10"
+ oninput="changeClass(this, 6)"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+ <vbox>
+ <checkbox id="orderedListCheckbox"
+ label="&orderedList.label;"
+ oncommand="ToggleOrderedList(this)"/>
+ <checkbox id="readOnlyCheckbox"
+ label="&makeReadOnly.label;"
+ oncommand="ToggleReadOnlyToc(this)"/>
+ </vbox>
+ <separator class="groove"/>
+ </vbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdInsertTable.js b/comm/suite/editor/components/dialogs/content/EdInsertTable.js
new file mode 100644
index 0000000000..0053f9fd94
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsertTable.js
@@ -0,0 +1,254 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// Cancel() is in EdDialogCommon.js
+
+var gTableElement = null;
+var gRows;
+var gColumns;
+var gActiveEditor;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ gActiveEditor = GetCurrentTableEditor();
+ if (!gActiveEditor) {
+ dump("Failed to get active editor!\n");
+ window.close();
+ return;
+ }
+
+ try {
+ gTableElement = gActiveEditor.createElementWithDefaults("table");
+ } catch (e) {}
+
+ if (!gTableElement) {
+ dump("Failed to create a new table!\n");
+ window.close();
+ return;
+ }
+ gDialog.rowsInput = document.getElementById("rowsInput");
+ gDialog.columnsInput = document.getElementById("columnsInput");
+ gDialog.widthInput = document.getElementById("widthInput");
+ gDialog.borderInput = document.getElementById("borderInput");
+ gDialog.widthPixelOrPercentMenulist = document.getElementById(
+ "widthPixelOrPercentMenulist"
+ );
+ gDialog.OkButton = document.documentElement.getButton("accept");
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = gTableElement.cloneNode(false);
+ try {
+ if (
+ Services.prefs.getBoolPref("editor.use_css") &&
+ IsHTMLEditor() &&
+ !(gActiveEditor.flags & Ci.nsIEditor.eEditorMailMask)
+ ) {
+ // only for Composer and not for htmlmail
+ globalElement.setAttribute("style", "text-align: left;");
+ }
+ } catch (e) {}
+
+ // Initialize all widgets with image attributes
+ InitDialog();
+
+ // Set initial number to 2 rows, 2 columns:
+ // Note, these are not attributes on the table,
+ // so don't put them in InitDialog(),
+ // else the user's values will be trashed when they use
+ // the Advanced Edit dialog
+ gDialog.rowsInput.value = 2;
+ gDialog.columnsInput.value = 2;
+
+ // If no default value on the width, set to 100%
+ if (gDialog.widthInput.value.length == 0) {
+ gDialog.widthInput.value = "100";
+ gDialog.widthPixelOrPercentMenulist.selectedIndex = 1;
+ }
+
+ SetTextboxFocusById("rowsInput");
+
+ SetWindowLocation();
+}
+
+// Set dialog widgets with attribute data
+// We get them from globalElement copy so this can be used
+// by AdvancedEdit(), which is shared by all property dialogs
+function InitDialog() {
+ // Get default attributes set on the created table:
+ // Get the width attribute of the element, stripping out "%"
+ // This sets contents of menu combobox list
+ // 2nd param = null: Use current selection to find if parent is table cell or window
+ gDialog.widthInput.value = InitPixelOrPercentMenulist(
+ globalElement,
+ null,
+ "width",
+ "widthPixelOrPercentMenulist",
+ gPercent
+ );
+ gDialog.borderInput.value = globalElement.getAttribute("border");
+}
+
+function ChangeRowOrColumn(id) {
+ // Allow only integers
+ forceInteger(id);
+
+ // Enable OK only if both rows and columns have a value > 0
+ var enable =
+ gDialog.rowsInput.value.length > 0 &&
+ gDialog.rowsInput.value > 0 &&
+ gDialog.columnsInput.value.length > 0 &&
+ gDialog.columnsInput.value > 0;
+
+ SetElementEnabled(gDialog.OkButton, enable);
+ SetElementEnabledById("AdvancedEditButton1", enable);
+}
+
+// Get and validate data from widgets.
+// Set attributes on globalElement so they can be accessed by AdvancedEdit()
+function ValidateData() {
+ gRows = ValidateNumber(
+ gDialog.rowsInput,
+ null,
+ 1,
+ gMaxRows,
+ null,
+ null,
+ true
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ gColumns = ValidateNumber(
+ gDialog.columnsInput,
+ null,
+ 1,
+ gMaxColumns,
+ null,
+ null,
+ true
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ // Set attributes: NOTE: These may be empty strings (last param = false)
+ ValidateNumber(
+ gDialog.borderInput,
+ null,
+ 0,
+ gMaxPixels,
+ globalElement,
+ "border",
+ false
+ );
+ // TODO: Deal with "BORDER" without value issue
+ if (gValidationError) {
+ return false;
+ }
+
+ ValidateNumber(
+ gDialog.widthInput,
+ gDialog.widthPixelOrPercentMenulist,
+ 1,
+ gMaxTableSize,
+ globalElement,
+ "width",
+ false
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ return true;
+}
+
+function onAccept(event) {
+ if (ValidateData()) {
+ gActiveEditor.beginTransaction();
+ try {
+ gActiveEditor.cloneAttributes(gTableElement, globalElement);
+
+ // Create necessary rows and cells for the table
+ var tableBody = gActiveEditor.createElementWithDefaults("tbody");
+ if (tableBody) {
+ gTableElement.appendChild(tableBody);
+
+ // Create necessary rows and cells for the table
+ for (var i = 0; i < gRows; i++) {
+ var newRow = gActiveEditor.createElementWithDefaults("tr");
+ if (newRow) {
+ tableBody.appendChild(newRow);
+ for (var j = 0; j < gColumns; j++) {
+ var newCell = gActiveEditor.createElementWithDefaults("td");
+ if (newCell) {
+ newRow.appendChild(newCell);
+ }
+ }
+ }
+ }
+ }
+ // Detect when entire cells are selected:
+ // Get number of cells selected
+ var tagNameObj = { value: "" };
+ var countObj = { value: 0 };
+ var element = gActiveEditor.getSelectedOrParentTableElement(
+ tagNameObj,
+ countObj
+ );
+ var deletePlaceholder = false;
+
+ if (tagNameObj.value == "table") {
+ // Replace entire selected table with new table, so delete the table
+ gActiveEditor.deleteTable();
+ } else if (tagNameObj.value == "td") {
+ if (countObj.value >= 1) {
+ if (countObj.value > 1) {
+ // Assume user wants to replace a block of
+ // contiguous cells with a table, so
+ // join the selected cells
+ gActiveEditor.joinTableCells(false);
+
+ // Get the cell everything was merged into
+ element = gActiveEditor.getFirstSelectedCell();
+
+ // Collapse selection into just that cell
+ gActiveEditor.selection.collapse(element, 0);
+ }
+
+ if (element) {
+ // Empty just the contents of the cell
+ gActiveEditor.deleteTableCellContents();
+
+ // Collapse selection to start of empty cell...
+ gActiveEditor.selection.collapse(element, 0);
+ // ...but it will contain a <br> placeholder
+ deletePlaceholder = true;
+ }
+ }
+ }
+
+ // true means delete selection when inserting
+ gActiveEditor.insertElementAtSelection(gTableElement, true);
+
+ if (deletePlaceholder && gTableElement && gTableElement.nextSibling) {
+ // Delete the placeholder <br>
+ gActiveEditor.deleteNode(gTableElement.nextSibling);
+ }
+ } catch (e) {}
+
+ gActiveEditor.endTransaction();
+
+ SaveWindowLocation();
+ return;
+ }
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdInsertTable.xhtml b/comm/suite/editor/components/dialogs/content/EdInsertTable.xhtml
new file mode 100644
index 0000000000..b376d5f0be
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdInsertTable.xhtml
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edInsertTable SYSTEM "chrome://editor/locale/EditorInsertTable.dtd">
+%edInsertTable;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload = "Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdInsertTable.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&size.label;</label>
+ </hbox>
+ <grid>
+ <columns>
+ <column flex="1"/>
+ <column flex="1"/>
+ <column flex="6"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label control="rowsInput" class="align-right"
+ value="&numRowsEditField.label;"
+ accesskey="&numRowsEditField.accessKey;"/>
+ <textbox class="narrow" id="rowsInput" oninput="ChangeRowOrColumn(this.id)" />
+ <spacer/>
+ </row>
+ <row align="center">
+ <label control="columnsInput" class="align-right"
+ value="&numColumnsEditField.label;"
+ accesskey="&numColumnsEditField.accessKey;"/>
+ <textbox class="narrow" id="columnsInput" oninput="ChangeRowOrColumn(this.id)" />
+ <spacer/>
+ </row>
+ <row align="center">
+ <label control="widthInput" class="align-right"
+ value="&widthEditField.label;"
+ accesskey="&widthEditField.accessKey;"/>
+ <textbox class="narrow" id="widthInput" oninput="forceInteger(this.id)" />
+ <menulist id="widthPixelOrPercentMenulist" flex="1"/>
+ <!-- child elements are appended by JS -->
+ </row>
+ </rows>
+ </grid>
+ <spacer class="spacer"/>
+ </groupbox>
+ <spacer class="spacer"/>
+ <hbox align="center">
+ <label control="borderInput" class="align-right"
+ value="&borderEditField.label;"
+ accesskey="&borderEditField.accessKey;"
+ tooltiptext="&borderEditField.tooltip;" />
+ <textbox class="narrow" id="borderInput" oninput="forceInteger(this.id)" />
+ <label value="&pixels.label;"/>
+ </hbox>
+ <vbox id="AdvancedEdit">
+ <hbox flex="1" style="margin-top: 0.2em" align="center">
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator id="advancedSeparator" class="groove"/>
+ </vbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdLabelProps.js b/comm/suite/editor/components/dialogs/content/EdLabelProps.js
new file mode 100644
index 0000000000..ec96878ea6
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdLabelProps.js
@@ -0,0 +1,118 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var labelElement;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ dump("Failed to get active editor!\n");
+ window.close();
+ return;
+ }
+
+ gDialog.editText = document.getElementById("EditText");
+ gDialog.labelText = document.getElementById("LabelText");
+ gDialog.labelFor = document.getElementById("LabelFor");
+ gDialog.labelAccessKey = document.getElementById("LabelAccessKey");
+
+ labelElement = window.arguments[0];
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = labelElement.cloneNode(false);
+
+ InitDialog();
+
+ var range = editor.document.createRange();
+ range.selectNode(labelElement);
+ gDialog.labelText.value = range.toString();
+
+ if (labelElement.innerHTML.includes("<")) {
+ gDialog.editText.checked = false;
+ gDialog.editText.disabled = false;
+ gDialog.labelText.disabled = true;
+ gDialog.editText.addEventListener(
+ "command",
+ () =>
+ Services.prompt.alert(
+ window,
+ GetString("Alert"),
+ GetString("EditTextWarning")
+ ),
+ { capture: false, once: true }
+ );
+ SetTextboxFocus(gDialog.labelFor);
+ } else {
+ SetTextboxFocus(gDialog.labelText);
+ }
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ gDialog.labelFor.value = globalElement.getAttribute("for");
+ gDialog.labelAccessKey.value = globalElement.getAttribute("accesskey");
+}
+
+function RemoveLabel() {
+ RemoveContainer(labelElement);
+ SaveWindowLocation();
+ window.close();
+}
+
+function ValidateData() {
+ if (gDialog.labelFor.value) {
+ globalElement.setAttribute("for", gDialog.labelFor.value);
+ } else {
+ globalElement.removeAttribute("for");
+ }
+ if (gDialog.labelAccessKey.value) {
+ globalElement.setAttribute("accesskey", gDialog.labelAccessKey.value);
+ } else {
+ globalElement.removeAttribute("accesskey");
+ }
+ return true;
+}
+
+function onAccept() {
+ // All values are valid - copy to actual element in doc
+ ValidateData();
+
+ var editor = GetCurrentEditor();
+
+ editor.beginTransaction();
+
+ try {
+ if (gDialog.editText.checked) {
+ editor.setShouldTxnSetSelection(false);
+
+ while (labelElement.firstChild) {
+ editor.deleteNode(labelElement.firstChild);
+ }
+ if (gDialog.labelText.value) {
+ editor.insertNode(
+ editor.document.createTextNode(gDialog.labelText.value),
+ labelElement,
+ 0
+ );
+ }
+
+ editor.setShouldTxnSetSelection(true);
+ }
+
+ editor.cloneAttributes(labelElement, globalElement);
+ } catch (e) {}
+
+ editor.endTransaction();
+
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdLabelProps.xhtml b/comm/suite/editor/components/dialogs/content/EdLabelProps.xhtml
new file mode 100644
index 0000000000..21d964f1fd
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdLabelProps.xhtml
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edLabelProperties SYSTEM "chrome://editor/locale/EditorLabelProperties.dtd">
+%edLabelProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ buttons="accept,cancel">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdLabelProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header" accesskey="&Settings.accesskey;">&Settings.label;</label>
+ </hbox>
+ <grid><columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <checkbox id="EditText" label="&EditLabelText.label;" accesskey="&EditLabelText.accesskey;" checked="true" disabled="true"
+ oncommand="gDialog.labelText.disabled = !gDialog.editText.checked;"/>
+ <textbox id="LabelText" accesskey="&Settings.accesskey;"/>
+ </row>
+ <row align="center">
+ <label control="LabelFor" value="&LabelFor.label;" accesskey="&LabelFor.accesskey;"/>
+ <textbox id="LabelFor"/>
+ </row>
+ <row align="center">
+ <label control="LabelAccessKey" value="&AccessKey.label;" accesskey="&AccessKey.accesskey;"/>
+ <hbox>
+ <textbox id="LabelAccessKey" class="narrow"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <!-- from EdDialogOverlay -->
+ <hbox flex="1" style="margin-top: 0.2em">
+ <button id="RemoveLabel" label="&RemoveLabel.label;" accesskey="&RemoveLabel.accesskey;" oncommand="RemoveLabel();"/>
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator class="groove"/>
+
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdLinkProps.js b/comm/suite/editor/components/dialogs/content/EdLinkProps.js
new file mode 100644
index 0000000000..6504496a51
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdLinkProps.js
@@ -0,0 +1,331 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var gActiveEditor;
+var anchorElement = null;
+var imageElement = null;
+var insertNew = false;
+var replaceExistingLink = false;
+var insertLinkAtCaret;
+var needLinkText = false;
+var href;
+var newLinkText;
+var gHNodeArray = {};
+var gHaveNamedAnchors = false;
+var gHaveHeadings = false;
+var gCanChangeHeadingSelected = true;
+var gCanChangeAnchorSelected = true;
+
+// NOTE: Use "href" instead of "a" to distinguish from Named Anchor
+// The returned node is has an "a" tagName
+var tagName = "href";
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ gActiveEditor = GetCurrentEditor();
+ if (!gActiveEditor) {
+ dump("Failed to get active editor!\n");
+ window.close();
+ return;
+ }
+ // Message was wrapped in a <label> or <div>, so actual text is a child text node
+ gDialog.linkTextCaption = document.getElementById("linkTextCaption");
+ gDialog.linkTextMessage = document.getElementById("linkTextMessage");
+ gDialog.linkTextInput = document.getElementById("linkTextInput");
+ gDialog.hrefInput = document.getElementById("hrefInput");
+ gDialog.makeRelativeLink = document.getElementById("MakeRelativeLink");
+ gDialog.AdvancedEditSection = document.getElementById("AdvancedEdit");
+
+ // See if we have a single selected image
+ imageElement = gActiveEditor.getSelectedElement("img");
+
+ if (imageElement) {
+ // Get the parent link if it exists -- more efficient than GetSelectedElement()
+ anchorElement = gActiveEditor.getElementOrParentByTagName(
+ "href",
+ imageElement
+ );
+ if (anchorElement) {
+ if (anchorElement.childNodes.length > 1) {
+ // If there are other children, then we want to break
+ // this image away by inserting a new link around it,
+ // so make a new node and copy existing attributes
+ anchorElement = anchorElement.cloneNode(false);
+ // insertNew = true;
+ replaceExistingLink = true;
+ }
+ }
+ } else {
+ // Get an anchor element if caret or
+ // entire selection is within the link.
+ anchorElement = gActiveEditor.getSelectedElement(tagName);
+
+ if (anchorElement) {
+ // Select the entire link
+ gActiveEditor.selectElement(anchorElement);
+ } else {
+ // If selection starts in a link, but extends beyond it,
+ // the user probably wants to extend existing link to new selection,
+ // so check if either end of selection is within a link
+ // POTENTIAL PROBLEM: This prevents user from selecting text in an existing
+ // link and making 2 links.
+ // Note that this isn't a problem with images, handled above
+
+ anchorElement = gActiveEditor.getElementOrParentByTagName(
+ "href",
+ gActiveEditor.selection.anchorNode
+ );
+ if (!anchorElement) {
+ anchorElement = gActiveEditor.getElementOrParentByTagName(
+ "href",
+ gActiveEditor.selection.focusNode
+ );
+ }
+
+ if (anchorElement) {
+ // But clone it for reinserting/merging around existing
+ // link that only partially overlaps the selection
+ anchorElement = anchorElement.cloneNode(false);
+ // insertNew = true;
+ replaceExistingLink = true;
+ }
+ }
+ }
+
+ if (!anchorElement) {
+ // No existing link -- create a new one
+ anchorElement = gActiveEditor.createElementWithDefaults(tagName);
+ insertNew = true;
+ // Hide message about removing existing link
+ // document.getElementById("RemoveLinkMsg").hidden = true;
+ }
+ if (!anchorElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+
+ // We insert at caret only when nothing is selected
+ insertLinkAtCaret = gActiveEditor.selection.isCollapsed;
+
+ var selectedText;
+ if (insertLinkAtCaret) {
+ // Groupbox caption:
+ gDialog.linkTextCaption.setAttribute("label", GetString("LinkText"));
+
+ // Message above input field:
+ gDialog.linkTextMessage.setAttribute("value", GetString("EnterLinkText"));
+ gDialog.linkTextMessage.setAttribute(
+ "accesskey",
+ GetString("EnterLinkTextAccessKey")
+ );
+ } else {
+ if (!imageElement) {
+ // We get here if selection is exactly around a link node
+ // Check if selection has some text - use that first
+ selectedText = GetSelectionAsText();
+ if (!selectedText) {
+ // No text, look for first image in the selection
+ var children = anchorElement.childNodes;
+ if (children) {
+ for (var i = 0; i < children.length; i++) {
+ var nodeName = children.item(i).nodeName.toLowerCase();
+ if (nodeName == "img") {
+ imageElement = children.item(i);
+ break;
+ }
+ }
+ }
+ }
+ }
+ // Set "caption" for link source and the source text or image URL
+ if (imageElement) {
+ gDialog.linkTextCaption.setAttribute("label", GetString("LinkImage"));
+ // Link source string is the source URL of image
+ // TODO: THIS DOESN'T HANDLE MULTIPLE SELECTED IMAGES!
+ gDialog.linkTextMessage.setAttribute("value", imageElement.src);
+ } else {
+ gDialog.linkTextCaption.setAttribute("label", GetString("LinkText"));
+ if (selectedText) {
+ // Use just the first 60 characters and add "..."
+ gDialog.linkTextMessage.setAttribute(
+ "value",
+ TruncateStringAtWordEnd(
+ ReplaceWhitespace(selectedText, " "),
+ 60,
+ true
+ )
+ );
+ } else {
+ gDialog.linkTextMessage.setAttribute(
+ "value",
+ GetString("MixedSelection")
+ );
+ }
+ }
+ }
+
+ // Make a copy to use for AdvancedEdit and onSaveDefault
+ globalElement = anchorElement.cloneNode(false);
+
+ // Get the list of existing named anchors and headings
+ FillLinkMenulist(gDialog.hrefInput, gHNodeArray);
+
+ // We only need to test for this once per dialog load
+ gHaveDocumentUrl = GetDocumentBaseUrl();
+
+ // Set data for the dialog controls
+ InitDialog();
+
+ // Search for a URI pattern in the selected text
+ // as candidate href
+ selectedText = TrimString(selectedText);
+ if (!gDialog.hrefInput.value && TextIsURI(selectedText)) {
+ gDialog.hrefInput.value = selectedText;
+ }
+
+ // Set initial focus
+ if (insertLinkAtCaret) {
+ // We will be using the HREF inputbox, so text message
+ SetTextboxFocus(gDialog.linkTextInput);
+ } else {
+ SetTextboxFocus(gDialog.hrefInput);
+
+ // We will not insert a new link at caret, so remove link text input field
+ gDialog.linkTextInput.hidden = true;
+ gDialog.linkTextInput = null;
+ }
+
+ // This sets enable state on OK button
+ doEnabling();
+
+ SetWindowLocation();
+}
+
+// Set dialog widgets with attribute data
+// We get them from globalElement copy so this can be used
+// by AdvancedEdit(), which is shared by all property dialogs
+function InitDialog() {
+ // Must use getAttribute, not "globalElement.href",
+ // or foreign chars aren't converted correctly!
+ gDialog.hrefInput.value = globalElement.getAttribute("href");
+
+ // Set "Relativize" checkbox according to current URL state
+ SetRelativeCheckbox(gDialog.makeRelativeLink);
+}
+
+function doEnabling() {
+ // We disable Ok button when there's no href text only if inserting a new link
+ var enable = insertNew
+ ? TrimString(gDialog.hrefInput.value).length > 0
+ : true;
+
+ // anon. content, so can't use SetElementEnabledById here
+ var dialogNode = document.getElementById("linkDlg");
+ dialogNode.getButton("accept").disabled = !enable;
+
+ SetElementEnabledById("AdvancedEditButton1", enable);
+}
+
+function ChangeLinkLocation() {
+ SetRelativeCheckbox(gDialog.makeRelativeLink);
+ // Set OK button enable state
+ doEnabling();
+}
+
+// Get and validate data from widgets.
+// Set attributes on globalElement so they can be accessed by AdvancedEdit()
+function ValidateData() {
+ href = TrimString(gDialog.hrefInput.value);
+ if (href) {
+ // Set the HREF directly on the editor document's anchor node
+ // or on the newly-created node if insertNew is true
+ globalElement.setAttribute("href", href);
+ } else if (insertNew) {
+ // We must have a URL to insert a new link
+ // NOTE: We accept an empty HREF on existing link to indicate removing the link
+ ShowInputErrorMessage(GetString("EmptyHREFError"));
+ return false;
+ }
+ if (gDialog.linkTextInput) {
+ // The text we will insert isn't really an attribute,
+ // but it makes sense to validate it
+ newLinkText = TrimString(gDialog.linkTextInput.value);
+ if (!newLinkText) {
+ if (href) {
+ newLinkText = href;
+ } else {
+ ShowInputErrorMessage(GetString("EmptyLinkTextError"));
+ SetTextboxFocus(gDialog.linkTextInput);
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+function onAccept(event) {
+ if (ValidateData()) {
+ if (href.length > 0) {
+ // Copy attributes to element we are changing or inserting
+ gActiveEditor.cloneAttributes(anchorElement, globalElement);
+
+ // Coalesce into one undo transaction
+ gActiveEditor.beginTransaction();
+
+ // Get text to use for a new link
+ if (insertLinkAtCaret) {
+ // Append the link text as the last child node
+ // of the anchor node
+ var textNode = gActiveEditor.document.createTextNode(newLinkText);
+ if (textNode) {
+ anchorElement.appendChild(textNode);
+ }
+ try {
+ gActiveEditor.insertElementAtSelection(anchorElement, false);
+ } catch (e) {
+ dump("Exception occurred in InsertElementAtSelection\n");
+ return;
+ }
+ } else if (insertNew || replaceExistingLink) {
+ // Link source was supplied by the selection,
+ // so insert a link node as parent of this
+ // (may be text, image, or other inline content)
+ try {
+ gActiveEditor.insertLinkAroundSelection(anchorElement);
+ } catch (e) {
+ dump("Exception occurred in InsertElementAtSelection\n");
+ return;
+ }
+ }
+ // Check if the link was to a heading
+ if (href in gHNodeArray) {
+ var anchorNode = gActiveEditor.createElementWithDefaults("a");
+ if (anchorNode) {
+ anchorNode.name = href.substr(1);
+
+ // Insert the anchor into the document,
+ // but don't let the transaction change the selection
+ gActiveEditor.setShouldTxnSetSelection(false);
+ gActiveEditor.insertNode(anchorNode, gHNodeArray[href], 0);
+ gActiveEditor.setShouldTxnSetSelection(true);
+ }
+ }
+ gActiveEditor.endTransaction();
+ } else if (!insertNew) {
+ // We already had a link, but empty HREF means remove it
+ EditorRemoveTextProperty("href", "");
+ }
+ SaveWindowLocation();
+ return;
+ }
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdLinkProps.xhtml b/comm/suite/editor/components/dialogs/content/EdLinkProps.xhtml
new file mode 100644
index 0000000000..04e147db4c
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdLinkProps.xhtml
@@ -0,0 +1,79 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % linkPropertiesDTD SYSTEM "chrome://editor/locale/EditorLinkProperties.dtd">
+%linkPropertiesDTD;
+<!ENTITY % composeEditorOverlayDTD SYSTEM "chrome://messenger/locale/messengercompose/mailComposeEditorOverlay.dtd">
+%composeEditorOverlayDTD;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog id="linkDlg" title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload = "Startup()">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdLinkProps.js"/>
+ <script src="chrome://editor/content/EdImageLinkLoader.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <vbox style="min-width: 20em">
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label id="linkTextCaption" class="header"/>
+ </hbox>
+ <vbox>
+ <label id="linkTextMessage" control="linkTextInput"/>
+ <textbox id="linkTextInput"/>
+ </vbox>
+ </groupbox>
+
+ <groupbox id="LinkURLBox">
+ <hbox class="groupbox-title">
+ <label class="header">&LinkURLBox.label;</label>
+ </hbox>
+ <vbox id="LinkLocationBox">
+ <label control="hrefInput"
+ accesskey="&LinkURLEditField2.accessKey;"
+ width="1">&LinkURLEditField2.label;</label>
+ <textbox id="hrefInput" type="text"
+ class="uri-element padded" oninput="ChangeLinkLocation();"/>
+ <hbox align="center">
+ <checkbox id="MakeRelativeLink"
+ for="hrefInput"
+ label="&makeUrlRelative.label;"
+ accesskey="&makeUrlRelative.accessKey;"
+ oncommand="MakeInputValueRelativeOrAbsolute(this);"
+ tooltiptext="&makeUrlRelative.tooltip;"/>
+ <spacer flex="1"/>
+ <button label="&chooseFileLinkButton.label;" accesskey="&chooseFileLinkButton.accessKey;"
+ oncommand="chooseLinkFile();"/>
+ </hbox>
+ </vbox>
+ <checkbox id="AttachSourceToMail"
+ hidden="true"
+ label="&attachLinkSource.label;"
+ accesskey="&attachLinkSource.accesskey;"
+ oncommand="DoAttachSourceCheckbox()"/>
+ </groupbox>
+ </vbox>
+ <vbox id="AdvancedEdit">
+ <hbox flex="1" style="margin-top: 0.2em" align="center">
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator id="advancedSeparator" class="groove"/>
+ </vbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdListProps.js b/comm/suite/editor/components/dialogs/content/EdListProps.js
new file mode 100644
index 0000000000..8d4b78536b
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdListProps.js
@@ -0,0 +1,455 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// Cancel() is in EdDialogCommon.js
+var gBulletStyleType = "";
+var gNumberStyleType = "";
+var gListElement;
+var gOriginalListType = "";
+var gListType = "";
+var gMixedListSelection = false;
+var gStyleType = "";
+var gOriginalStyleType = "";
+const gOnesArray = ["", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"];
+const gTensArray = ["", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"];
+const gHundredsArray = [
+ "",
+ "C",
+ "CC",
+ "CCC",
+ "CD",
+ "D",
+ "DC",
+ "DCC",
+ "DCCC",
+ "CM",
+];
+const gThousandsArray = [
+ "",
+ "M",
+ "MM",
+ "MMM",
+ "MMMM",
+ "MMMMM",
+ "MMMMMM",
+ "MMMMMMM",
+ "MMMMMMMM",
+ "MMMMMMMMM",
+];
+const gRomanDigits = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 };
+const A = "A".charCodeAt(0);
+const gArabic = "1";
+const gUpperRoman = "I";
+const gLowerRoman = "i";
+const gUpperLetters = "A";
+const gLowerLetters = "a";
+const gDecimalCSS = "decimal";
+const gUpperRomanCSS = "upper-roman";
+const gLowerRomanCSS = "lower-roman";
+const gUpperAlphaCSS = "upper-alpha";
+const gLowerAlphaCSS = "lower-alpha";
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+ gDialog.ListTypeList = document.getElementById("ListType");
+ gDialog.BulletStyleList = document.getElementById("BulletStyle");
+ gDialog.BulletStyleLabel = document.getElementById("BulletStyleLabel");
+ gDialog.StartingNumberInput = document.getElementById("StartingNumber");
+ gDialog.StartingNumberLabel = document.getElementById("StartingNumberLabel");
+ gDialog.AdvancedEditButton = document.getElementById("AdvancedEditButton1");
+ gDialog.RadioGroup = document.getElementById("RadioGroup");
+ gDialog.ChangeAllRadio = document.getElementById("ChangeAll");
+ gDialog.ChangeSelectedRadio = document.getElementById("ChangeSelected");
+
+ // Try to get an existing list(s)
+ var mixedObj = { value: null };
+ try {
+ gListType = editor.getListState(mixedObj, {}, {}, {});
+
+ // We may have mixed list and non-list, or > 1 list type in selection
+ gMixedListSelection = mixedObj.value;
+
+ // Get the list element at the anchor node
+ gListElement = editor.getElementOrParentByTagName("list", null);
+ } catch (e) {}
+
+ // The copy to use in AdvancedEdit
+ if (gListElement) {
+ globalElement = gListElement.cloneNode(false);
+ }
+
+ // Show extra options for changing entire list if we have one already.
+ gDialog.RadioGroup.collapsed = !gListElement;
+ if (gListElement) {
+ // Radio button index is persistent
+ if (gDialog.RadioGroup.getAttribute("index") == "1") {
+ gDialog.RadioGroup.selectedItem = gDialog.ChangeSelectedRadio;
+ } else {
+ gDialog.RadioGroup.selectedItem = gDialog.ChangeAllRadio;
+ }
+ }
+
+ InitDialog();
+
+ gOriginalListType = gListType;
+
+ gDialog.ListTypeList.focus();
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ // Note that if mixed, we we pay attention
+ // only to the anchor node's list type
+ // (i.e., don't confuse user with "mixed" designation)
+ if (gListElement) {
+ gListType = gListElement.nodeName.toLowerCase();
+ } else {
+ gListType = "";
+ }
+
+ gDialog.ListTypeList.value = gListType;
+ gDialog.StartingNumberInput.value = "";
+
+ // Last param = true means attribute value is case-sensitive
+ var type = globalElement
+ ? GetHTMLOrCSSStyleValue(globalElement, "type", "list-style-type")
+ : null;
+
+ if (gListType == "ul") {
+ if (type) {
+ type = type.toLowerCase();
+ gBulletStyleType = type;
+ gOriginalStyleType = type;
+ }
+ } else if (gListType == "ol") {
+ // Translate CSS property strings
+ switch (type.toLowerCase()) {
+ case gDecimalCSS:
+ type = gArabic;
+ break;
+ case gUpperRomanCSS:
+ type = gUpperRoman;
+ break;
+ case gLowerRomanCSS:
+ type = gLowerRoman;
+ break;
+ case gUpperAlphaCSS:
+ type = gUpperLetters;
+ break;
+ case gLowerAlphaCSS:
+ type = gLowerLetters;
+ break;
+ }
+ if (type) {
+ gNumberStyleType = type;
+ gOriginalStyleType = type;
+ }
+
+ // Convert attribute number to appropriate letter or roman numeral
+ gDialog.StartingNumberInput.value = ConvertStartAttrToUserString(
+ globalElement.getAttribute("start"),
+ type
+ );
+ }
+ BuildBulletStyleList();
+}
+
+// Convert attribute number to appropriate letter or roman numeral
+function ConvertStartAttrToUserString(startAttr, type) {
+ switch (type) {
+ case gUpperRoman:
+ startAttr = ConvertArabicToRoman(startAttr);
+ break;
+ case gLowerRoman:
+ startAttr = ConvertArabicToRoman(startAttr).toLowerCase();
+ break;
+ case gUpperLetters:
+ startAttr = ConvertArabicToLetters(startAttr);
+ break;
+ case gLowerLetters:
+ startAttr = ConvertArabicToLetters(startAttr).toLowerCase();
+ break;
+ }
+ return startAttr;
+}
+
+function BuildBulletStyleList() {
+ gDialog.BulletStyleList.removeAllItems();
+ var label;
+
+ if (gListType == "ul") {
+ gDialog.BulletStyleList.removeAttribute("disabled");
+ gDialog.BulletStyleLabel.removeAttribute("disabled");
+ gDialog.StartingNumberInput.setAttribute("disabled", "true");
+ gDialog.StartingNumberLabel.setAttribute("disabled", "true");
+
+ label = GetString("BulletStyle");
+
+ gDialog.BulletStyleList.appendItem(GetString("Automatic"), "");
+ gDialog.BulletStyleList.appendItem(GetString("SolidCircle"), "disc");
+ gDialog.BulletStyleList.appendItem(GetString("OpenCircle"), "circle");
+ gDialog.BulletStyleList.appendItem(GetString("SolidSquare"), "square");
+
+ gDialog.BulletStyleList.value = gBulletStyleType;
+ } else if (gListType == "ol") {
+ gDialog.BulletStyleList.removeAttribute("disabled");
+ gDialog.BulletStyleLabel.removeAttribute("disabled");
+ gDialog.StartingNumberInput.removeAttribute("disabled");
+ gDialog.StartingNumberLabel.removeAttribute("disabled");
+ label = GetString("NumberStyle");
+
+ gDialog.BulletStyleList.appendItem(GetString("Automatic"), "");
+ gDialog.BulletStyleList.appendItem(GetString("Style_1"), gArabic);
+ gDialog.BulletStyleList.appendItem(GetString("Style_I"), gUpperRoman);
+ gDialog.BulletStyleList.appendItem(GetString("Style_i"), gLowerRoman);
+ gDialog.BulletStyleList.appendItem(GetString("Style_A"), gUpperLetters);
+ gDialog.BulletStyleList.appendItem(GetString("Style_a"), gLowerLetters);
+
+ gDialog.BulletStyleList.value = gNumberStyleType;
+ } else {
+ gDialog.BulletStyleList.setAttribute("disabled", "true");
+ gDialog.BulletStyleLabel.setAttribute("disabled", "true");
+ gDialog.StartingNumberInput.setAttribute("disabled", "true");
+ gDialog.StartingNumberLabel.setAttribute("disabled", "true");
+ }
+
+ // Disable advanced edit button if changing to "normal"
+ if (gListType) {
+ gDialog.AdvancedEditButton.removeAttribute("disabled");
+ } else {
+ gDialog.AdvancedEditButton.setAttribute("disabled", "true");
+ }
+
+ if (label) {
+ gDialog.BulletStyleLabel.setAttribute("label", label);
+ }
+}
+
+function SelectListType() {
+ // Each list type is stored in the "value" of each menuitem
+ var NewType = gDialog.ListTypeList.value;
+
+ if (NewType == "ol") {
+ SetTextboxFocus(gDialog.StartingNumberInput);
+ }
+
+ if (gListType != NewType) {
+ gListType = NewType;
+
+ // Create a newlist object for Advanced Editing
+ try {
+ if (gListType) {
+ globalElement = GetCurrentEditor().createElementWithDefaults(gListType);
+ }
+ } catch (e) {}
+
+ BuildBulletStyleList();
+ }
+}
+
+function SelectBulletStyle() {
+ // Save the selected index so when user changes
+ // list style, restore index to associated list
+ // Each bullet or number type is stored in the "value" of each menuitem
+ if (gListType == "ul") {
+ gBulletStyleType = gDialog.BulletStyleList.value;
+ } else if (gListType == "ol") {
+ var type = gDialog.BulletStyleList.value;
+ if (gNumberStyleType != type) {
+ // Convert existing input value to attr number first,
+ // then convert to the appropriate format for the newly-selected
+ gDialog.StartingNumberInput.value = ConvertStartAttrToUserString(
+ ConvertUserStringToStartAttr(gNumberStyleType),
+ type
+ );
+
+ gNumberStyleType = type;
+ SetTextboxFocus(gDialog.StartingNumberInput);
+ }
+ }
+}
+
+function ValidateData() {
+ gBulletStyleType = gDialog.BulletStyleList.value;
+ // globalElement should already be of the correct type
+
+ if (globalElement) {
+ var editor = GetCurrentEditor();
+ if (gListType == "ul") {
+ if (gBulletStyleType && gDialog.ChangeAllRadio.selected) {
+ globalElement.setAttribute("type", gBulletStyleType);
+ } else {
+ try {
+ editor.removeAttributeOrEquivalent(globalElement, "type", true);
+ } catch (e) {}
+ }
+ } else if (gListType == "ol") {
+ if (gBulletStyleType) {
+ globalElement.setAttribute("type", gBulletStyleType);
+ } else {
+ try {
+ editor.removeAttributeOrEquivalent(globalElement, "type", true);
+ } catch (e) {}
+ }
+
+ var startingNumber = ConvertUserStringToStartAttr(gBulletStyleType);
+ if (startingNumber) {
+ globalElement.setAttribute("start", startingNumber);
+ } else {
+ globalElement.removeAttribute("start");
+ }
+ }
+ }
+ return true;
+}
+
+function ConvertUserStringToStartAttr(type) {
+ var startingNumber = TrimString(gDialog.StartingNumberInput.value);
+
+ switch (type) {
+ case gUpperRoman:
+ case gLowerRoman:
+ // If the input isn't an integer, assume it's a roman numeral. Convert it.
+ if (!Number(startingNumber)) {
+ startingNumber = ConvertRomanToArabic(startingNumber);
+ }
+ break;
+ case gUpperLetters:
+ case gLowerLetters:
+ // Get the number equivalent of the letters
+ if (!Number(startingNumber)) {
+ startingNumber = ConvertLettersToArabic(startingNumber);
+ }
+ break;
+ }
+ return startingNumber;
+}
+
+function ConvertRomanToArabic(num) {
+ num = num.toUpperCase();
+ if (num && !/[^MDCLXVI]/i.test(num)) {
+ var Arabic = 0;
+ var last_digit = 1000;
+ for (var i = 0; i < num.length; i++) {
+ var digit = gRomanDigits[num.charAt(i)];
+ if (last_digit < digit) {
+ Arabic -= 2 * last_digit;
+ }
+
+ last_digit = digit;
+ Arabic += last_digit;
+ }
+ return Arabic;
+ }
+
+ return "";
+}
+
+function ConvertArabicToRoman(num) {
+ if (/^\d{1,4}$/.test(num)) {
+ var digits = ("000" + num).substr(-4);
+ return (
+ gThousandsArray[digits.charAt(0)] +
+ gHundredsArray[digits.charAt(1)] +
+ gTensArray[digits.charAt(2)] +
+ gOnesArray[digits.charAt(3)]
+ );
+ }
+ return "";
+}
+
+function ConvertLettersToArabic(letters) {
+ letters = letters.toUpperCase();
+ if (!letters || /[^A-Z]/.test(letters)) {
+ return "";
+ }
+
+ var num = 0;
+ for (var i = 0; i < letters.length; i++) {
+ num = num * 26 + letters.charCodeAt(i) - A + 1;
+ }
+ return num;
+}
+
+function ConvertArabicToLetters(num) {
+ var letters = "";
+ while (num) {
+ num--;
+ letters = String.fromCharCode(A + (num % 26)) + letters;
+ num = Math.floor(num / 26);
+ }
+ return letters;
+}
+
+function onAccept(event) {
+ if (ValidateData()) {
+ // Coalesce into one undo transaction
+ var editor = GetCurrentEditor();
+
+ editor.beginTransaction();
+
+ var changeEntireList =
+ gDialog.RadioGroup.selectedItem == gDialog.ChangeAllRadio;
+
+ // Remember which radio button was selected
+ if (gListElement) {
+ gDialog.RadioGroup.setAttribute("index", changeEntireList ? "0" : "1");
+ }
+
+ var changeList;
+ if (gListElement && gDialog.ChangeAllRadio.selected) {
+ changeList = true;
+ } else {
+ changeList =
+ gMixedListSelection ||
+ gListType != gOriginalListType ||
+ gBulletStyleType != gOriginalStyleType;
+ }
+ if (changeList) {
+ try {
+ if (gListType) {
+ editor.makeOrChangeList(
+ gListType,
+ changeEntireList,
+ gBulletStyleType != gOriginalStyleType ? gBulletStyleType : null
+ );
+
+ // Get the new list created:
+ gListElement = editor.getElementOrParentByTagName(gListType, null);
+
+ editor.cloneAttributes(gListElement, globalElement);
+ } else {
+ // Remove all existing lists
+ if (gListElement && changeEntireList) {
+ editor.selectElement(gListElement);
+ }
+
+ editor.removeList("ol");
+ editor.removeList("ul");
+ editor.removeList("dl");
+ }
+ } catch (e) {}
+ }
+
+ editor.endTransaction();
+
+ SaveWindowLocation();
+
+ return;
+ }
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdListProps.xhtml b/comm/suite/editor/components/dialogs/content/EdListProps.xhtml
new file mode 100644
index 0000000000..cae9829c97
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdListProps.xhtml
@@ -0,0 +1,73 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edListProperties SYSTEM "chrome://editor/locale/EditorListProperties.dtd">
+%edListProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdListProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox flex="1">
+ <hbox class="groupbox-title">
+ <label class="header">&ListType.label;</label>
+ </hbox>
+ <menulist id="ListType" oncommand="SelectListType()">
+ <menupopup>
+ <menuitem label="&none.value;"/>
+ <menuitem value="ul" label="&bulletList.value;"/>
+ <menuitem value="ol" label="&numberList.value;"/>
+ <menuitem value="dl" label="&definitionList.value;"/>
+ </menupopup>
+ </menulist>
+ </groupbox>
+ <spacer class="spacer"/>
+
+ <!-- message text and list items are set in JS
+ text value should be identical to string with id=BulletStyle in editor.properties
+ -->
+ <groupbox flex="1">
+ <hbox class="groupbox-title">
+ <label id="BulletStyleLabel" class="header">&bulletStyle.label;</label>
+ </hbox>
+ <menulist class="MinWidth10em" id="BulletStyle" oncommand="SelectBulletStyle()">
+ <menupopup/>
+ </menulist>
+ <spacer class="spacer"/>
+ <hbox>
+ <label id="StartingNumberLabel" control="StartingNumber"
+ value="&startingNumber.label;" accesskey="&startingNumber.accessKey;"/>
+ <textbox class="narrow" id="StartingNumber"/>
+ <spacer/>
+ </hbox>
+ </groupbox>
+ <radiogroup id="RadioGroup" index="0" persist="index">
+ <radio id="ChangeAll" label="&changeEntireListRadio.label;" accesskey="&changeEntireListRadio.accessKey;"/>
+ <radio id="ChangeSelected" label="&changeSelectedRadio.label;" accesskey="&changeSelectedRadio.accessKey;"/>
+ </radiogroup>
+ <vbox id="AdvancedEdit">
+ <hbox flex="1" style="margin-top: 0.2em" align="center">
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator id="advancedSeparator" class="groove"/>
+ </vbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.js b/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.js
new file mode 100644
index 0000000000..0433e58872
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.js
@@ -0,0 +1,159 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var gInsertNew = true;
+var gAnchorElement = null;
+var gOriginalName = "";
+const kTagName = "anchor";
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ gDialog.OkButton = document.documentElement.getButton("accept");
+ gDialog.NameInput = document.getElementById("nameInput");
+
+ // Get a single selected element of the desired type
+ gAnchorElement = editor.getSelectedElement(kTagName);
+
+ if (gAnchorElement) {
+ // We found an element and don't need to insert one
+ gInsertNew = false;
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = gAnchorElement.cloneNode(false);
+ gOriginalName = ConvertToCDATAString(gAnchorElement.name);
+ } else {
+ gInsertNew = true;
+ // We don't have an element selected,
+ // so create one with default attributes
+ gAnchorElement = editor.createElementWithDefaults(kTagName);
+ if (gAnchorElement) {
+ // Use the current selection as suggested name
+ var name = GetSelectionAsText();
+ // Get 40 characters of the selected text and don't add "...",
+ // replace whitespace with "_" and strip non-word characters
+ name = ConvertToCDATAString(TruncateStringAtWordEnd(name, 40, false));
+ // Be sure the name is unique to the document
+ if (AnchorNameExists(name)) {
+ name += "_";
+ }
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = gAnchorElement.cloneNode(false);
+ globalElement.setAttribute("name", name);
+ }
+ }
+ if (!gAnchorElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+
+ InitDialog();
+
+ DoEnabling();
+ SetTextboxFocus(gDialog.NameInput);
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ gDialog.NameInput.value = globalElement.getAttribute("name");
+}
+
+function ChangeName() {
+ if (gDialog.NameInput.value.length > 0) {
+ // Replace spaces with "_" and strip other non-URL characters
+ // Note: we could use ConvertAndEscape, but then we'd
+ // have to UnEscapeAndConvert beforehand - too messy!
+ gDialog.NameInput.value = ConvertToCDATAString(gDialog.NameInput.value);
+ }
+ DoEnabling();
+}
+
+function DoEnabling() {
+ var enable = gDialog.NameInput.value.length > 0;
+ SetElementEnabled(gDialog.OkButton, enable);
+ SetElementEnabledById("AdvancedEditButton1", enable);
+}
+
+function AnchorNameExists(name) {
+ var anchorList;
+ try {
+ anchorList = GetCurrentEditor().document.anchors;
+ } catch (e) {}
+
+ if (anchorList) {
+ for (var i = 0; i < anchorList.length; i++) {
+ if (anchorList[i].name == name) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// Get and validate data from widgets.
+// Set attributes on globalElement so they can be accessed by AdvancedEdit()
+function ValidateData() {
+ var name = TrimString(gDialog.NameInput.value);
+ if (!name) {
+ ShowInputErrorMessage(GetString("MissingAnchorNameError"));
+ SetTextboxFocus(gDialog.NameInput);
+ return false;
+ }
+ // Replace spaces with "_" and strip other characters
+ // Note: we could use ConvertAndEscape, but then we'd
+ // have to UnConverAndEscape beforehand - too messy!
+ name = ConvertToCDATAString(name);
+
+ if (gOriginalName != name && AnchorNameExists(name)) {
+ ShowInputErrorMessage(
+ GetString("DuplicateAnchorNameError").replace(/%name%/, name)
+ );
+ SetTextboxFocus(gDialog.NameInput);
+ return false;
+ }
+ globalElement.name = name;
+
+ return true;
+}
+
+function onAccept(event) {
+ if (ValidateData()) {
+ if (gOriginalName != globalElement.name) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+
+ try {
+ // "false" = don't delete selected text when inserting
+ if (gInsertNew) {
+ // We must insert element before copying CSS style attribute,
+ // but we must set the name else it won't insert at all
+ gAnchorElement.name = globalElement.name;
+ editor.insertElementAtSelection(gAnchorElement, false);
+ }
+
+ // Copy attributes to element we are changing or inserting
+ editor.cloneAttributes(gAnchorElement, globalElement);
+ } catch (e) {}
+
+ editor.endTransaction();
+ }
+ SaveWindowLocation();
+ return;
+ }
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.xhtml b/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.xhtml
new file mode 100644
index 0000000000..3a38256222
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdNamedAnchorProps.xhtml
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edNamedAnchorProperties SYSTEM "chrome://editor/locale/EdNamedAnchorProperties.dtd">
+%edNamedAnchorProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdNamedAnchorProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <label control="nameInput"
+ value="&anchorNameEditField.label;"
+ accesskey="&anchorNameEditField.accessKey;"/>
+ <textbox class="MinWidth20em" id="nameInput" oninput="ChangeName()"
+ tooltiptext="&nameInput.tooltip;"/>
+ <spacer class="spacer"/>
+ <vbox id="AdvancedEdit">
+ <hbox flex="1" style="margin-top: 0.2em" align="center">
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator id="advancedSeparator" class="groove"/>
+ </vbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdPageProps.js b/comm/suite/editor/components/dialogs/content/EdPageProps.js
new file mode 100644
index 0000000000..568eff66ec
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdPageProps.js
@@ -0,0 +1,159 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var gNewTitle = "";
+var gAuthor = "";
+var gDescription = "";
+var gAuthorElement;
+var gDescriptionElement;
+var gInsertNewAuthor = false;
+var gInsertNewDescription = false;
+var gTitleWasEdited = false;
+var gAuthorWasEdited = false;
+var gDescWasEdited = false;
+
+// Cancel() is in EdDialogCommon.js
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ gDialog.PageLocation = document.getElementById("PageLocation");
+ gDialog.PageModDate = document.getElementById("PageModDate");
+ gDialog.TitleInput = document.getElementById("TitleInput");
+ gDialog.AuthorInput = document.getElementById("AuthorInput");
+ gDialog.DescriptionInput = document.getElementById("DescriptionInput");
+
+ // Default string for new page is set from DTD string in XUL,
+ // so set only if not new doc URL
+ var location = GetDocumentUrl();
+ var lastmodString = GetString("Unknown");
+
+ if (!IsUrlAboutBlank(location)) {
+ // NEVER show username and password in clear text
+ gDialog.PageLocation.setAttribute("value", StripPassword(location));
+
+ // Get last-modified file date+time
+ // TODO: Convert this to local time?
+ var lastmod;
+ try {
+ lastmod = editor.document.lastModified; // get string of last modified date
+ } catch (e) {}
+ // Convert modified string to date (0 = unknown date or January 1, 1970 GMT)
+ if (Date.parse(lastmod)) {
+ try {
+ const dateTimeFormatter = new Services.intl.DateTimeFormat(undefined, {
+ dateStyle: "long",
+ timeStyle: "short",
+ });
+
+ var lastModDate = new Date();
+ lastModDate.setTime(Date.parse(lastmod));
+ lastmodString = dateTimeFormatter.format(lastModDate);
+ } catch (e) {}
+ }
+ }
+ gDialog.PageModDate.value = lastmodString;
+
+ gAuthorElement = GetMetaElementByAttribute("name", "author");
+ if (!gAuthorElement) {
+ gAuthorElement = CreateMetaElementWithAttribute("name", "author");
+ if (!gAuthorElement) {
+ window.close();
+ return;
+ }
+ gInsertNewAuthor = true;
+ }
+
+ gDescriptionElement = GetMetaElementByAttribute("name", "description");
+ if (!gDescriptionElement) {
+ gDescriptionElement = CreateMetaElementWithAttribute("name", "description");
+ if (!gDescriptionElement) {
+ window.close();
+ }
+
+ gInsertNewDescription = true;
+ }
+
+ InitDialog();
+
+ SetTextboxFocus(gDialog.TitleInput);
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ gDialog.TitleInput.value = GetDocumentTitle();
+
+ var gAuthor = TrimString(gAuthorElement.getAttribute("content"));
+ if (!gAuthor) {
+ // Fill in with value from editor prefs
+ gAuthor = Services.prefs.getCharPref("editor.author");
+ }
+ gDialog.AuthorInput.value = gAuthor;
+ gDialog.DescriptionInput.value = gDescriptionElement.getAttribute("content");
+}
+
+function TextboxChanged(ID) {
+ switch (ID) {
+ case "TitleInput":
+ gTitleWasEdited = true;
+ break;
+ case "AuthorInput":
+ gAuthorWasEdited = true;
+ break;
+ case "DescriptionInput":
+ gDescWasEdited = true;
+ break;
+ }
+}
+
+function ValidateData() {
+ gNewTitle = TrimString(gDialog.TitleInput.value);
+ gAuthor = TrimString(gDialog.AuthorInput.value);
+ gDescription = TrimString(gDialog.DescriptionInput.value);
+ return true;
+}
+
+function onAccept(event) {
+ if (ValidateData()) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+
+ // Set title contents even if string is empty
+ // because TITLE is a required HTML element
+ if (gTitleWasEdited) {
+ SetDocumentTitle(gNewTitle);
+ }
+
+ if (gAuthorWasEdited) {
+ SetMetaElementContent(gAuthorElement, gAuthor, gInsertNewAuthor, false);
+ }
+
+ if (gDescWasEdited) {
+ SetMetaElementContent(
+ gDescriptionElement,
+ gDescription,
+ gInsertNewDescription,
+ false
+ );
+ }
+
+ editor.endTransaction();
+
+ SaveWindowLocation();
+ return; // do close the window
+ }
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdPageProps.xhtml b/comm/suite/editor/components/dialogs/content/EdPageProps.xhtml
new file mode 100644
index 0000000000..4891baa04a
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdPageProps.xhtml
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorPageProperties.dtd">
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdPageProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+ <grid>
+ <columns><column flex="1"/><column flex="2"/></columns>
+ <rows>
+ <row>
+ <label value="&location.label;"/>
+ <label value="&locationNewPage.label;" id="PageLocation"/>
+ </row>
+ <row>
+ <label value="&lastModified.label;"/>
+ <label id="PageModDate"/>
+ </row>
+ <spacer class="spacer"/>
+ <row align="center">
+ <label value="&titleInput.label;" accesskey="&titleInput.accessKey;" control="TitleInput"/>
+ <textbox class="MinWidth20em" id="TitleInput" oninput="TextboxChanged(this.id)"/>
+ </row>
+ <row align="center">
+ <label value="&authorInput.label;" accesskey="&authorInput.accessKey;" control="AuthorInput"/>
+ <textbox class="MinWidth20em" id="AuthorInput" oninput="TextboxChanged(this.id)"/>
+ </row>
+ <row align="center">
+ <label value="&descriptionInput.label;" accesskey="&descriptionInput.accessKey;" control="DescriptionInput"/>
+ <textbox class="MinWidth20em" id="DescriptionInput" oninput="TextboxChanged(this.id)"/>
+ </row>
+ </rows>
+ </grid>
+ <spacer class="bigspacer"/>
+ <label value="&EditHEADSource1.label;"/>
+ <description class="wrap" flex="1">&EditHEADSource2.label;</description>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdReplace.js b/comm/suite/editor/components/dialogs/content/EdReplace.js
new file mode 100644
index 0000000000..c0daea29de
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdReplace.js
@@ -0,0 +1,382 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var gReplaceDialog; // Quick access to document/form elements.
+var gFindInst; // nsIWebBrowserFind that we're going to use
+var gFindService; // Global service which remembers find params
+var gEditor; // the editor we're using
+
+document.addEventListener("dialogaccept", event => {
+ onFindNext();
+ event.preventDefault();
+});
+
+function initDialogObject() {
+ // Create gReplaceDialog object and initialize.
+ gReplaceDialog = {};
+ gReplaceDialog.findInput = document.getElementById("dialog.findInput");
+ gReplaceDialog.replaceInput = document.getElementById("dialog.replaceInput");
+ gReplaceDialog.caseSensitive = document.getElementById(
+ "dialog.caseSensitive"
+ );
+ gReplaceDialog.wrap = document.getElementById("dialog.wrap");
+ gReplaceDialog.searchBackwards = document.getElementById(
+ "dialog.searchBackwards"
+ );
+ gReplaceDialog.findNext = document.getElementById("findNext");
+ gReplaceDialog.replace = document.getElementById("replace");
+ gReplaceDialog.replaceAndFind = document.getElementById("replaceAndFind");
+ gReplaceDialog.replaceAll = document.getElementById("replaceAll");
+}
+
+function loadDialog() {
+ // Set initial dialog field contents.
+ // Set initial dialog field contents. Use the gFindInst attributes first,
+ // this is necessary for window.find()
+ gReplaceDialog.findInput.value = gFindInst.searchString
+ ? gFindInst.searchString
+ : gFindService.searchString;
+ gReplaceDialog.replaceInput.value = gFindService.replaceString;
+ gReplaceDialog.caseSensitive.checked = gFindInst.matchCase
+ ? gFindInst.matchCase
+ : gFindService.matchCase;
+ gReplaceDialog.wrap.checked = gFindInst.wrapFind
+ ? gFindInst.wrapFind
+ : gFindService.wrapFind;
+ gReplaceDialog.searchBackwards.checked = gFindInst.findBackwards
+ ? gFindInst.findBackwards
+ : gFindService.findBackwards;
+
+ doEnabling();
+}
+
+function onLoad() {
+ // Get the xul <editor> element:
+ var editorElement = window.arguments[0];
+
+ // If we don't get the editor, then we won't allow replacing.
+ gEditor = editorElement.getEditor(editorElement.contentWindow);
+ if (!gEditor) {
+ window.close();
+ return;
+ }
+
+ // Get the nsIWebBrowserFind service:
+ gFindInst = editorElement.webBrowserFind;
+
+ try {
+ // get the find service, which stores global find state
+ gFindService = Cc["@mozilla.org/find/find_service;1"].getService(
+ Ci.nsIFindService
+ );
+ } catch (e) {
+ dump("No find service!\n");
+ gFindService = 0;
+ }
+
+ // Init gReplaceDialog.
+ initDialogObject();
+
+ // Change "OK" to "Find".
+ // dialog.find.label = document.getElementById("fBLT").getAttribute("label");
+
+ // Fill dialog.
+ loadDialog();
+
+ if (gReplaceDialog.findInput.value) {
+ gReplaceDialog.findInput.select();
+ } else {
+ gReplaceDialog.findInput.focus();
+ }
+}
+
+function saveFindData() {
+ // Set data attributes per user input.
+ if (gFindService) {
+ gFindService.searchString = gReplaceDialog.findInput.value;
+ gFindService.matchCase = gReplaceDialog.caseSensitive.checked;
+ gFindService.wrapFind = gReplaceDialog.wrap.checked;
+ gFindService.findBackwards = gReplaceDialog.searchBackwards.checked;
+ }
+}
+
+function setUpFindInst() {
+ gFindInst.searchString = gReplaceDialog.findInput.value;
+ gFindInst.matchCase = gReplaceDialog.caseSensitive.checked;
+ gFindInst.wrapFind = gReplaceDialog.wrap.checked;
+ gFindInst.findBackwards = gReplaceDialog.searchBackwards.checked;
+}
+
+function onFindNext() {
+ // Transfer dialog contents to the find service.
+ saveFindData();
+ // set up the find instance
+ setUpFindInst();
+
+ // Search.
+ var result = gFindInst.findNext();
+
+ if (!result) {
+ var bundle = document.getElementById("findBundle");
+ Services.prompt.alert(
+ window,
+ GetString("Alert"),
+ bundle.getString("notFoundWarning")
+ );
+ SetTextboxFocus(gReplaceDialog.findInput);
+ gReplaceDialog.findInput.select();
+ gReplaceDialog.findInput.focus();
+ return false;
+ }
+ return true;
+}
+
+function onReplace() {
+ if (!gEditor) {
+ return false;
+ }
+
+ // Does the current selection match the find string?
+ var selection = gEditor.selection;
+
+ var selStr = selection.toString();
+ var specStr = gReplaceDialog.findInput.value;
+ if (!gReplaceDialog.caseSensitive.checked) {
+ selStr = selStr.toLowerCase();
+ specStr = specStr.toLowerCase();
+ }
+ // Unfortunately, because of whitespace we can't just check
+ // whether (selStr == specStr), but have to loop ourselves.
+ // N chars of whitespace in specStr can match any M >= N in selStr.
+ var matches = true;
+ var specLen = specStr.length;
+ var selLen = selStr.length;
+ if (selLen < specLen) {
+ matches = false;
+ } else {
+ var specArray = specStr.match(/\S+|\s+/g);
+ var selArray = selStr.match(/\S+|\s+/g);
+ if (specArray.length != selArray.length) {
+ matches = false;
+ } else {
+ for (var i = 0; i < selArray.length; i++) {
+ if (selArray[i] != specArray[i]) {
+ if (/\S/.test(selArray[i][0]) || /\S/.test(specArray[i][0])) {
+ // not a space chunk -- match fails
+ matches = false;
+ break;
+ } else if (selArray[i].length < specArray[i].length) {
+ // if it's a space chunk then we only care that sel be
+ // at least as long as spec
+ matches = false;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // If the current selection doesn't match the pattern,
+ // then we want to find the next match, but not do the replace.
+ // That's what most other apps seem to do.
+ // So here, just return.
+ if (!matches) {
+ return false;
+ }
+
+ // Transfer dialog contents to the find service.
+ saveFindData();
+
+ // For reverse finds, need to remember the caret position
+ // before current selection
+ var newRange;
+ if (gReplaceDialog.searchBackwards.checked && selection.rangeCount > 0) {
+ newRange = selection.getRangeAt(0).cloneRange();
+ newRange.collapse(true);
+ }
+
+ // nsPlaintextEditor::InsertText fails if the string is empty,
+ // so make that a special case:
+ var replStr = gReplaceDialog.replaceInput.value;
+ if (replStr == "") {
+ gEditor.deleteSelection(gEditor.eNone, gEditor.eStrip);
+ } else {
+ gEditor.insertText(replStr);
+ }
+
+ // For reverse finds, need to move caret just before the replaced text
+ if (gReplaceDialog.searchBackwards.checked && newRange) {
+ gEditor.selection.removeAllRanges();
+ gEditor.selection.addRange(newRange);
+ }
+
+ return true;
+}
+
+function onReplaceAll() {
+ if (!gEditor) {
+ return;
+ }
+
+ var findStr = gReplaceDialog.findInput.value;
+ var repStr = gReplaceDialog.replaceInput.value;
+
+ // Transfer dialog contents to the find service.
+ saveFindData();
+
+ var finder = Cc["@mozilla.org/embedcomp/rangefind;1"]
+ .createInstance()
+ .QueryInterface(Ci.nsIFind);
+
+ finder.caseSensitive = gReplaceDialog.caseSensitive.checked;
+ finder.findBackwards = gReplaceDialog.searchBackwards.checked;
+
+ // We want the whole operation to be undoable in one swell foop,
+ // so start a transaction:
+ gEditor.beginTransaction();
+
+ // and to make sure we close the transaction, guard against exceptions:
+ try {
+ // Make a range containing the current selection,
+ // so we don't go past it when we wrap.
+ var selection = gEditor.selection;
+ var selecRange;
+ if (selection.rangeCount > 0) {
+ selecRange = selection.getRangeAt(0);
+ }
+ var origRange = selecRange.cloneRange();
+
+ // We'll need a range for the whole document:
+ var wholeDocRange = gEditor.document.createRange();
+ var rootNode = gEditor.rootElement;
+ wholeDocRange.selectNodeContents(rootNode);
+
+ // And start and end points:
+ var endPt = gEditor.document.createRange();
+
+ if (gReplaceDialog.searchBackwards.checked) {
+ endPt.setStart(wholeDocRange.startContainer, wholeDocRange.startOffset);
+ endPt.setEnd(wholeDocRange.startContainer, wholeDocRange.startOffset);
+ } else {
+ endPt.setStart(wholeDocRange.endContainer, wholeDocRange.endOffset);
+ endPt.setEnd(wholeDocRange.endContainer, wholeDocRange.endOffset);
+ }
+
+ // Find and replace from here to end (start) of document:
+ var foundRange;
+ var searchRange = wholeDocRange.cloneRange();
+ while (
+ (foundRange = finder.Find(findStr, searchRange, selecRange, endPt)) !=
+ null
+ ) {
+ gEditor.selection.removeAllRanges();
+ gEditor.selection.addRange(foundRange);
+
+ // The editor will leave the caret at the end of the replaced text.
+ // For reverse finds, we need it at the beginning,
+ // so save the next position now.
+ if (gReplaceDialog.searchBackwards.checked) {
+ selecRange = foundRange.cloneRange();
+ selecRange.setEnd(selecRange.startContainer, selecRange.startOffset);
+ }
+
+ // nsPlaintextEditor::InsertText fails if the string is empty,
+ // so make that a special case:
+ if (repStr == "") {
+ gEditor.deleteSelection(gEditor.eNone, gEditor.eStrip);
+ } else {
+ gEditor.insertText(repStr);
+ }
+
+ // If we're going forward, we didn't save selecRange before, so do it now:
+ if (!gReplaceDialog.searchBackwards.checked) {
+ selection = gEditor.selection;
+ if (selection.rangeCount <= 0) {
+ gEditor.endTransaction();
+ return;
+ }
+ selecRange = selection.getRangeAt(0).cloneRange();
+ }
+ }
+
+ // If no wrapping, then we're done
+ if (!gReplaceDialog.wrap.checked) {
+ gEditor.endTransaction();
+ return;
+ }
+
+ // If wrapping, find from start/end of document back to start point.
+ if (gReplaceDialog.searchBackwards.checked) {
+ // Collapse origRange to end
+ origRange.setStart(origRange.endContainer, origRange.endOffset);
+ // Set current position to document end
+ selecRange.setEnd(wholeDocRange.endContainer, wholeDocRange.endOffset);
+ selecRange.setStart(wholeDocRange.endContainer, wholeDocRange.endOffset);
+ } else {
+ // Collapse origRange to start
+ origRange.setEnd(origRange.startContainer, origRange.startOffset);
+ // Set current position to document start
+ selecRange.setStart(
+ wholeDocRange.startContainer,
+ wholeDocRange.startOffset
+ );
+ selecRange.setEnd(
+ wholeDocRange.startContainer,
+ wholeDocRange.startOffset
+ );
+ }
+
+ while (
+ (foundRange = finder.Find(
+ findStr,
+ wholeDocRange,
+ selecRange,
+ origRange
+ )) != null
+ ) {
+ gEditor.selection.removeAllRanges();
+ gEditor.selection.addRange(foundRange);
+
+ // Save insert point for backward case
+ if (gReplaceDialog.searchBackwards.checked) {
+ selecRange = foundRange.cloneRange();
+ selecRange.setEnd(selecRange.startContainer, selecRange.startOffset);
+ }
+
+ // nsPlaintextEditor::InsertText fails if the string is empty,
+ // so make that a special case:
+ if (repStr == "") {
+ gEditor.deleteSelection(gEditor.eNone, gEditor.eStrip);
+ } else {
+ gEditor.insertText(repStr);
+ }
+
+ // Get insert point for forward case
+ if (!gReplaceDialog.searchBackwards.checked) {
+ selection = gEditor.selection;
+ if (selection.rangeCount <= 0) {
+ gEditor.endTransaction();
+ return;
+ }
+ selecRange = selection.getRangeAt(0);
+ }
+ }
+ } catch (e) {}
+
+ gEditor.endTransaction();
+}
+
+function doEnabling() {
+ var findStr = gReplaceDialog.findInput.value;
+ gReplaceDialog.enabled = findStr;
+ gReplaceDialog.findNext.disabled = !findStr;
+ gReplaceDialog.replace.disabled = !findStr;
+ gReplaceDialog.replaceAndFind.disabled = !findStr;
+ gReplaceDialog.replaceAll.disabled = !findStr;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdReplace.xhtml b/comm/suite/editor/components/dialogs/content/EdReplace.xhtml
new file mode 100644
index 0000000000..54a9d81ba3
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdReplace.xhtml
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorReplace.dtd">
+
+<dialog id="replaceDlg" title="&replaceDialog.title;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ persist="screenX screenY"
+ buttons="cancel"
+ onload="onLoad()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdReplace.js"/>
+ <stringbundle id="findBundle" src="chrome://global/locale/finddialog.properties"/>
+
+ <hbox>
+ <vbox>
+ <spacer class="spacer"/>
+ <grid align="start">
+ <columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label value="&findField.label;" accesskey="&findField.accesskey;" control="dialog.findInput"/>
+ <textbox id="dialog.findInput" oninput="doEnabling();"/>
+ </row>
+ <row align="center">
+ <label value="&replaceField.label;" accesskey="&replaceField.accesskey;" control="dialog.replaceInput"/>
+ <textbox id="dialog.replaceInput" oninput="doEnabling();"/>
+ </row>
+ <row align="start">
+ <spacer/>
+ <vbox align="start">
+ <spacer class="bigspacer"/>
+ <checkbox id="dialog.caseSensitive" label="&caseSensitiveCheckbox.label;"
+ accesskey="&caseSensitiveCheckbox.accesskey;"/>
+ <checkbox id="dialog.wrap" label="&wrapCheckbox.label;"
+ accesskey="&wrapCheckbox.accesskey;"/>
+ <checkbox id="dialog.searchBackwards" label="&backwardsCheckbox.label;"
+ accesskey="&backwardsCheckbox.accesskey;"/>
+ </vbox>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+ <vbox>
+ <button id="findNext" label="&findNextButton.label;" accesskey="&findNextButton.accesskey;"
+ oncommand="onFindNext();" default="true"/>
+ <button id="replace" label="&replaceButton.label;" accesskey="&replaceButton.accesskey;"
+ oncommand="onReplace();"/>
+ <button id="replaceAndFind" label="&replaceAndFindButton.label;"
+ accesskey="&replaceAndFindButton.accesskey;" oncommand="onReplace(); onFindNext();"/>
+ <button id="replaceAll" label="&replaceAllButton.label;"
+ accesskey="&replaceAllButton.accesskey;" oncommand="onReplaceAll();"/>
+ <button dlgtype="cancel" label="&closeButton.label;" accesskey="&closeButton.accesskey;"/>
+ </vbox>
+ </hbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdSelectProps.js b/comm/suite/editor/components/dialogs/content/EdSelectProps.js
new file mode 100644
index 0000000000..c03fd73a67
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdSelectProps.js
@@ -0,0 +1,770 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// Global variables
+
+var hasValue;
+var oldValue;
+var insertNew;
+var itemArray;
+var theTree;
+var treeSelection;
+var selectElement;
+var currentItem = null;
+var selectedOption = null;
+var selectedOptionCount = 0;
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+// Utility functions
+
+function getParentIndex(index) {
+ switch (itemArray[index].level) {
+ case 0:
+ return -1;
+ case 1:
+ return 0;
+ }
+ // eslint-disable-next-line curly
+ while (itemArray[--index].level > 1);
+ return index;
+}
+
+function UpdateSelectMultiple() {
+ if (selectedOptionCount > 1) {
+ gDialog.selectMultiple.checked = true;
+ gDialog.selectMultiple.disabled = true;
+ } else {
+ gDialog.selectMultiple.disabled = false;
+ }
+}
+
+/* wrapper objects:
+ * readonly attribute Node element; // DOM node (select/optgroup/option)
+ * readonly attribute int level; // tree depth
+ * readonly attribute boolean container; // can contain options
+ * string getCellText(string col); // tree view helper
+ * string cycleCell(int currentIndex); // tree view helper
+ * void onFocus(); // load data into deck
+ * void onBlur(); // save data from deck
+ * boolean canDestroy(boolean prompt); // NB prompt not used
+ * void destroy(); // post remove callback
+ * void moveUp();
+ * boolean canMoveDown();
+ * void moveDown();
+ * void appendOption(newElement, currentIndex);
+ */
+
+// OPTION element wrapper object
+
+// Create a wrapper for the given element at the given level
+function optionObject(option, level) {
+ // select an added option (when loading from document)
+ if (option.hasAttribute("selected")) {
+ selectedOptionCount++;
+ }
+ this.level = level;
+ this.element = option;
+}
+
+optionObject.prototype.container = false;
+
+optionObject.prototype.getCellText = function(column) {
+ if (column.id == "SelectSelCol") {
+ return "";
+ }
+ if (column.id == "SelectValCol" && this.element.hasAttribute("value")) {
+ return this.element.getAttribute("value");
+ }
+ return this.element.text;
+};
+
+optionObject.prototype.cycleCell = function(index) {
+ if (this.element.hasAttribute("selected")) {
+ this.element.removeAttribute("selected");
+ selectedOptionCount--;
+ selectedOption = null;
+ } else {
+ // Different handling for multiselect lists
+ if (gDialog.selectMultiple.checked || !selectedOption) {
+ selectedOptionCount++;
+ } else if (selectedOption) {
+ selectedOption.removeAttribute("selected");
+ let column = theTree.columns.SelectSelCol;
+ theTree.invalidateColumn(column);
+ selectedOption = null;
+ }
+ this.element.setAttribute("selected", "");
+ selectedOption = this.element;
+ let column = theTree.columns.SelectSelCol;
+ theTree.invalidateCell(index, column);
+ }
+ if (currentItem == this) {
+ // Also update the deck
+ gDialog.optionSelected.setAttribute(
+ "checked",
+ this.element.hasAttribute("selected")
+ );
+ }
+ UpdateSelectMultiple();
+};
+
+optionObject.prototype.onFocus = function() {
+ gDialog.optionText.value = this.element.text;
+ hasValue = this.element.hasAttribute("value");
+ oldValue = this.element.value;
+ gDialog.optionHasValue.checked = hasValue;
+ gDialog.optionValue.value = hasValue ? this.element.value : this.element.text;
+ gDialog.optionSelected.checked = this.element.hasAttribute("selected");
+ gDialog.optionDisabled.checked = this.element.hasAttribute("disabled");
+ gDialog.selectDeck.setAttribute("selectedIndex", "2");
+};
+
+optionObject.prototype.onBlur = function() {
+ this.element.text = gDialog.optionText.value;
+ if (gDialog.optionHasValue.checked) {
+ this.element.value = gDialog.optionValue.value;
+ } else {
+ this.element.removeAttribute("value");
+ }
+ if (gDialog.optionSelected.checked) {
+ this.element.setAttribute("selected", "");
+ } else {
+ this.element.removeAttribute("selected");
+ }
+ if (gDialog.optionDisabled.checked) {
+ this.element.setAttribute("disabled", "");
+ } else {
+ this.element.removeAttribute("disabled");
+ }
+};
+
+optionObject.prototype.canDestroy = function(prompt) {
+ return true;
+ /* return !prompt ||
+ ConfirmWithTitle(GetString("DeleteOption"),
+ GetString("DeleteOptionMsg"),
+ GetString("DeleteOption"));*/
+};
+
+optionObject.prototype.destroy = function() {
+ // Deselect a removed option
+ if (this.element.hasAttribute("selected")) {
+ selectedOptionCount--;
+ selectedOption = null;
+ UpdateSelectMultiple();
+ }
+};
+
+/* 4 cases:
+ * a) optgroup -> optgroup
+ * ... ...
+ * option option
+ * b) optgroup -> option
+ * option optgroup
+ * ... ...
+ * c) option
+ * option
+ * d) option
+ * option
+ */
+
+optionObject.prototype.moveUp = function() {
+ var index = treeSelection.currentIndex;
+ if (
+ itemArray[index].level <
+ itemArray[index - 1].level + itemArray[index - 1].container
+ ) {
+ // we need to repaint the tree's lines
+ theTree.invalidateRange(getParentIndex(index), index);
+ // a) option is just after an optgroup, so it becomes the last child
+ itemArray[index].level = 2;
+ theTree.view.selectionChanged();
+ } else {
+ // otherwise new option level is now the same as the previous item
+ itemArray[index].level = itemArray[index - 1].level;
+ // swap the option with the previous item
+ itemArray.splice(index, 0, itemArray.splice(--index, 1)[0]);
+ }
+ selectTreeIndex(index, true);
+};
+
+optionObject.prototype.canMoveDown = function() {
+ // move down is not allowed on the last option if its level is 1
+ return this.level > 1 || itemArray.length - treeSelection.currentIndex > 1;
+};
+
+optionObject.prototype.moveDown = function() {
+ var index = treeSelection.currentIndex;
+ if (
+ index + 1 == itemArray.length ||
+ itemArray[index].level > itemArray[index + 1].level
+ ) {
+ // we need to repaint the tree's lines
+ theTree.invalidateRange(getParentIndex(index), index);
+ // a) option is last child of an optgroup, so it moves just after
+ itemArray[index].level = 1;
+ theTree.view.selectionChanged();
+ } else {
+ // level increases if the option was preceding an optgroup
+ itemArray[index].level += itemArray[index + 1].container;
+ // swap the option with the next item
+ itemArray.splice(index, 0, itemArray.splice(++index, 1)[0]);
+ }
+ selectTreeIndex(index, true);
+};
+
+optionObject.prototype.appendOption = function(child, parent) {
+ // special case quick check
+ if (this.level == 1) {
+ return gDialog.appendOption(child, 0);
+ }
+
+ // append the option to the parent element
+ parent = getParentIndex(parent);
+ return itemArray[parent].appendOption(child, parent);
+};
+
+// OPTGROUP element wrapper object
+
+function optgroupObject(optgroup) {
+ this.element = optgroup;
+}
+
+optgroupObject.prototype.level = 1;
+
+optgroupObject.prototype.container = true;
+
+optgroupObject.prototype.getCellText = function(column) {
+ return column.id == "SelectTextCol" ? this.element.label : "";
+};
+
+optgroupObject.prototype.cycleCell = function(index) {};
+
+optgroupObject.prototype.onFocus = function() {
+ gDialog.optgroupLabel.value = this.element.label;
+ gDialog.optgroupDisabled.checked = this.element.disabled;
+ gDialog.selectDeck.setAttribute("selectedIndex", "1");
+};
+
+optgroupObject.prototype.onBlur = function() {
+ this.element.label = gDialog.optgroupLabel.value;
+ this.element.disabled = gDialog.optgroupDisabled.checked;
+};
+
+optgroupObject.prototype.canDestroy = function(prompt) {
+ // Only removing empty option groups for now
+ return (
+ gDialog.nextChild(treeSelection.currentIndex) -
+ treeSelection.currentIndex ==
+ 1
+ );
+ /* && (!prompt ||
+ ConfirmWithTitle(GetString("DeleteOptGroup"),
+ GetString("DeleteOptGroupMsg"),
+ GetString("DeleteOptGroup")));
+*/
+};
+
+optgroupObject.prototype.destroy = function() {};
+
+optgroupObject.prototype.moveUp = function() {
+ // Find the index of the previous and next elements at the same level
+ var index = treeSelection.currentIndex;
+ var i = index;
+ // eslint-disable-next-line curly
+ while (itemArray[--index].level > 1);
+ var j = gDialog.nextChild(i);
+ // Cut out the element, cut the array in two, then join together
+ var movedItems = itemArray.splice(i, j - i);
+ var endItems = itemArray.splice(index);
+ itemArray = itemArray.concat(movedItems).concat(endItems);
+ // Repaint the lot
+ theTree.invalidateRange(index, j);
+ selectTreeIndex(index, true);
+};
+
+optgroupObject.prototype.canMoveDown = function() {
+ return gDialog.lastChild() > treeSelection.currentIndex;
+};
+
+optgroupObject.prototype.moveDown = function() {
+ // Find the index of the next two elements at the same level
+ var index = treeSelection.currentIndex;
+ var i = gDialog.nextChild(index);
+ var j = gDialog.nextChild(i);
+ // Cut out the element, cut the array in two, then join together
+ var movedItems = itemArray.splice(i, j - 1);
+ var endItems = itemArray.splice(index);
+ itemArray = itemArray.concat(movedItems).concat(endItems);
+ // Repaint the lot
+ theTree.invalidateRange(index, j);
+ index += j - i;
+ selectTreeIndex(index, true);
+};
+
+optgroupObject.prototype.appendOption = function(child, parent) {
+ var index = gDialog.nextChild(parent);
+ // XXX need to repaint the lines, tree won't do this
+ var primaryCol = theTree.columns.getPrimaryColumn();
+ theTree.invalidateCell(index - 1, primaryCol);
+ // insert the wrapped object as the last child
+ itemArray.splice(index, 0, new optionObject(child, 2));
+ theTree.rowCountChanged(index, 1);
+ selectTreeIndex(index, false);
+};
+
+// dialog initialization code
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ dump("Failed to get active editor!\n");
+ window.close();
+ return;
+ }
+
+ // Get a single selected select element
+ const kTagName = "select";
+ try {
+ selectElement = editor.getSelectedElement(kTagName);
+ } catch (e) {}
+
+ if (selectElement) {
+ // We found an element and don't need to insert one
+ insertNew = false;
+ } else {
+ insertNew = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+ try {
+ selectElement = editor.createElementWithDefaults(kTagName);
+ } catch (e) {}
+
+ if (!selectElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+ }
+
+ // SELECT element wrapper object
+ gDialog = {
+ // useful elements
+ accept: document.documentElement.getButton("accept"),
+ selectDeck: document.getElementById("SelectDeck"),
+ selectName: document.getElementById("SelectName"),
+ selectSize: document.getElementById("SelectSize"),
+ selectMultiple: document.getElementById("SelectMultiple"),
+ selectDisabled: document.getElementById("SelectDisabled"),
+ selectTabIndex: document.getElementById("SelectTabIndex"),
+ optgroupLabel: document.getElementById("OptGroupLabel"),
+ optgroupDisabled: document.getElementById("OptGroupDisabled"),
+ optionText: document.getElementById("OptionText"),
+ optionHasValue: document.getElementById("OptionHasValue"),
+ optionValue: document.getElementById("OptionValue"),
+ optionSelected: document.getElementById("OptionSelected"),
+ optionDisabled: document.getElementById("OptionDisabled"),
+ removeButton: document.getElementById("RemoveButton"),
+ previousButton: document.getElementById("PreviousButton"),
+ nextButton: document.getElementById("NextButton"),
+ tree: document.getElementById("SelectTree"),
+ // wrapper methods (except MoveUp and MoveDown)
+ element: selectElement.cloneNode(false),
+ level: 0,
+ container: true,
+ getCellText(column) {
+ return column.id == "SelectTextCol"
+ ? this.element.getAttribute("name")
+ : "";
+ },
+ cycleCell(index) {},
+ onFocus() {
+ gDialog.selectName.value = this.element.getAttribute("name");
+ gDialog.selectSize.value = this.element.getAttribute("size");
+ gDialog.selectMultiple.checked = this.element.hasAttribute("multiple");
+ gDialog.selectDisabled.checked = this.element.hasAttribute("disabled");
+ gDialog.selectTabIndex.value = this.element.getAttribute("tabindex");
+ this.selectDeck.setAttribute("selectedIndex", "0");
+ onNameInput();
+ },
+ onBlur() {
+ this.element.setAttribute("name", gDialog.selectName.value);
+ if (gDialog.selectSize.value) {
+ this.element.setAttribute("size", gDialog.selectSize.value);
+ } else {
+ this.element.removeAttribute("size");
+ }
+ if (gDialog.selectMultiple.checked) {
+ this.element.setAttribute("multiple", "");
+ } else {
+ this.element.removeAttribute("multiple");
+ }
+ if (gDialog.selectDisabled.checked) {
+ this.element.setAttribute("disabled", "");
+ } else {
+ this.element.removeAttribute("disabled");
+ }
+ if (gDialog.selectTabIndex.value) {
+ this.element.setAttribute("tabindex", gDialog.selectTabIndex.value);
+ } else {
+ this.element.removeAttribute("tabindex");
+ }
+ },
+ appendOption(child, parent) {
+ var index = itemArray.length;
+ // XXX need to repaint the lines, tree won't do this
+ theTree.invalidateRange(this.lastChild(), index);
+ // append the wrapped object
+ itemArray.push(new optionObject(child, 1));
+ theTree.rowCountChanged(index, 1);
+ selectTreeIndex(index, false);
+ },
+ canDestroy(prompt) {
+ return false;
+ },
+ canMoveDown() {
+ return false;
+ },
+ // helper methods
+ // Find the index of the next immediate child of the select
+ nextChild(index) {
+ // eslint-disable-next-line curly
+ while (++index < itemArray.length && itemArray[index].level > 1);
+ return index;
+ },
+ // Find the index of the last immediate child of the select
+ lastChild() {
+ var index = itemArray.length;
+ // eslint-disable-next-line curly
+ while (itemArray[--index].level > 1);
+ return index;
+ },
+ };
+ // Start with the <select> wrapper
+ itemArray = [gDialog];
+
+ // We modify the actual option and optgroup elements so clone them first
+ for (var child = selectElement.firstChild; child; child = child.nextSibling) {
+ if (child.tagName == "OPTION") {
+ itemArray.push(new optionObject(child.cloneNode(true), 1));
+ } else if (child.tagName == "OPTGROUP") {
+ itemArray.push(new optgroupObject(child.cloneNode(false)));
+ for (
+ var grandchild = child.firstChild;
+ grandchild;
+ grandchild = grandchild.nextSibling
+ ) {
+ if (grandchild.tagName == "OPTION") {
+ itemArray.push(new optionObject(grandchild.cloneNode(true), 2));
+ }
+ }
+ }
+ }
+
+ UpdateSelectMultiple();
+
+ // Define a custom view for the tree
+ theTree = gDialog.tree;
+ theTree.view = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsITreeView",
+ "nsISupportsWeakReference",
+ ]),
+ // useful for debugging
+ get wrappedJSObject() {
+ return this;
+ },
+ get rowCount() {
+ return itemArray.length;
+ },
+ get selection() {
+ return treeSelection;
+ },
+ set selection(selection) {
+ return (treeSelection = selection);
+ },
+ getRowProperties(index) {
+ return "";
+ },
+ // could have used a wrapper for this
+ getCellProperties(index, column) {
+ if (column.id == "SelectSelCol" && !itemArray[index].container) {
+ return "checked-" + itemArray[index].element.hasAttribute("selected");
+ }
+ return "";
+ },
+ getColumnProperties(column) {
+ return "";
+ },
+ // get info from wrapper
+ isContainer(index) {
+ return itemArray[index].container;
+ },
+ isContainerOpen(index) {
+ return true;
+ },
+ isContainerEmpty(index) {
+ return true;
+ },
+ isSeparator(index) {
+ return false;
+ },
+ isSorted() {
+ return false;
+ },
+ // d&d not implemented yet!
+ canDrop(index, orientation) {
+ return false;
+ },
+ drop(index, orientation) {
+ alert("drop:" + index + "," + orientation);
+ },
+ // same as the global helper
+ getParentIndex,
+ // tree needs to know when to paint lines
+ hasNextSibling(index, after) {
+ if (!index) {
+ return false;
+ }
+ var level = itemArray[index].level;
+ while (++after < itemArray.length) {
+ switch (level - itemArray[after].level) {
+ case 1:
+ return false;
+ case 0:
+ return true;
+ }
+ }
+ return false;
+ },
+ getLevel(index) {
+ return itemArray[index].level;
+ },
+ getImageSrc(index, column) {},
+ getProgressMode(index, column) {},
+ getCellValue(index, column) {},
+ getCellText(index, column) {
+ return itemArray[index].getCellText(column);
+ },
+ setTree(tree) {
+ this.tree = tree;
+ },
+ toggleOpenState(index) {},
+ cycleHeader(col) {},
+ selectionChanged() {
+ // Save current values and update buttons and deck
+ if (currentItem) {
+ currentItem.onBlur();
+ }
+ var currentIndex = treeSelection.currentIndex;
+ currentItem = itemArray[currentIndex];
+ gDialog.removeButton.disabled = !currentItem.canDestroy();
+ gDialog.previousButton.disabled = currentIndex < 2;
+ gDialog.nextButton.disabled = !currentItem.canMoveDown();
+ // For Advanced Edit
+ globalElement = currentItem.element;
+ currentItem.onFocus();
+ },
+ cycleCell(index, column) {
+ itemArray[index].cycleCell(index);
+ },
+ isEditable(index, column) {
+ return false;
+ },
+ };
+ treeSelection.select(0);
+ currentItem = gDialog;
+ // onNameInput();
+
+ SetTextboxFocus(gDialog.selectName);
+
+ SetWindowLocation();
+}
+
+// Called from Advanced Edit
+function InitDialog() {
+ currentItem.onFocus();
+}
+
+// Called from Advanced Edit
+function ValidateData() {
+ currentItem.onBlur();
+ return true;
+}
+
+function onAccept() {
+ // All values are valid - copy to actual element in doc or
+ // element created to insert
+ ValidateData();
+
+ var editor = GetCurrentEditor();
+
+ // Coalesce into one undo transaction
+ editor.beginTransaction();
+
+ try {
+ editor.cloneAttributes(selectElement, gDialog.element);
+
+ if (insertNew) {
+ // 'true' means delete the selection before inserting
+ editor.insertElementAtSelection(selectElement, true);
+ }
+
+ editor.setShouldTxnSetSelection(false);
+
+ while (selectElement.lastChild) {
+ editor.deleteNode(selectElement.lastChild);
+ }
+
+ var offset = 0;
+ for (var i = 1; i < itemArray.length; i++) {
+ if (itemArray[i].level > 1) {
+ selectElement.lastChild.appendChild(itemArray[i].element);
+ } else {
+ editor.insertNode(itemArray[i].element, selectElement, offset++);
+ }
+ }
+
+ editor.setShouldTxnSetSelection(true);
+ } finally {
+ editor.endTransaction();
+ }
+
+ SaveWindowLocation();
+}
+
+// Button actions
+function AddOption() {
+ currentItem.appendOption(
+ GetCurrentEditor().createElementWithDefaults("option"),
+ treeSelection.currentIndex
+ );
+ SetTextboxFocus(gDialog.optionText);
+}
+
+function AddOptGroup() {
+ var optgroupElement = GetCurrentEditor().createElementWithDefaults(
+ "optgroup"
+ );
+ var index = itemArray.length;
+ // XXX need to repaint the lines, tree won't do this
+ theTree.invalidateRange(gDialog.lastChild(), index);
+ // append the wrapped object
+ itemArray.push(new optgroupObject(optgroupElement));
+ theTree.rowCountChanged(index, 1);
+ selectTreeIndex(index, false);
+ SetTextboxFocus(gDialog.optgroupLabel);
+}
+
+function RemoveElement() {
+ if (currentItem.canDestroy(true)) {
+ // Only removing empty option groups for now
+ var index = treeSelection.currentIndex;
+ var level = itemArray[index].level;
+ // Perform necessary cleanup and remove the wrapper
+ itemArray[index].destroy();
+ itemArray.splice(index, 1);
+ --index;
+ // XXX need to repaint the lines, tree won't do this
+ if (level == 1) {
+ var last = gDialog.lastChild();
+ if (index > last) {
+ theTree.invalidateRange(last, index);
+ }
+ }
+ selectTreeIndex(index, true);
+ theTree.rowCountChanged(++index, -1);
+ }
+}
+
+// Event handler
+function onTreeKeyUp(event) {
+ if (event.keyCode == event.DOM_VK_SPACE) {
+ currentItem.cycleCell();
+ }
+}
+
+function onNameInput() {
+ var disabled = !gDialog.selectName.value;
+ if (gDialog.accept.disabled != disabled) {
+ gDialog.accept.disabled = disabled;
+ }
+ gDialog.element.setAttribute("name", gDialog.selectName.value);
+ // repaint the tree
+ var primaryCol = theTree.columns.getPrimaryColumn();
+ theTree.invalidateCell(treeSelection.currentIndex, primaryCol);
+}
+
+function onLabelInput() {
+ currentItem.element.setAttribute("label", gDialog.optgroupLabel.value);
+ // repaint the tree
+ var primaryCol = theTree.columns.getPrimaryColumn();
+ theTree.invalidateCell(treeSelection.currentIndex, primaryCol);
+}
+
+function onTextInput() {
+ currentItem.element.text = gDialog.optionText.value;
+ // repaint the tree
+ if (hasValue) {
+ var primaryCol = theTree.columns.getPrimaryColumn();
+ theTree.invalidateCell(treeSelection.currentIndex, primaryCol);
+ } else {
+ gDialog.optionValue.value = gDialog.optionText.value;
+ theTree.invalidateRow(treeSelection.currentIndex);
+ }
+}
+
+function onValueInput() {
+ gDialog.optionHasValue.checked = hasValue = true;
+ oldValue = gDialog.optionValue.value;
+ currentItem.element.setAttribute("value", oldValue);
+ // repaint the tree
+ var column = theTree.columns.SelectValCol;
+ theTree.invalidateCell(treeSelection.currentIndex, column);
+}
+
+function onHasValueClick() {
+ hasValue = gDialog.optionHasValue.checked;
+ if (hasValue) {
+ gDialog.optionValue.value = oldValue;
+ currentItem.element.setAttribute("value", oldValue);
+ } else {
+ oldValue = gDialog.optionValue.value;
+ gDialog.optionValue.value = gDialog.optionText.value;
+ currentItem.element.removeAttribute("value");
+ }
+ // repaint the tree
+ var column = theTree.columns.SelectValCol;
+ theTree.invalidateCell(treeSelection.currentIndex, column);
+}
+
+function onSelectMultipleClick() {
+ // Recalculate the unique selected option if we need it and have lost it
+ if (
+ !gDialog.selectMultiple.checked &&
+ selectedOptionCount == 1 &&
+ !selectedOption
+ ) {
+ // eslint-disable-next-line curly
+ for (
+ var i = 1;
+ !(selectedOption = itemArray[i].element).hasAttribute("selected");
+ i++
+ );
+ }
+}
+
+function selectTreeIndex(index, focus) {
+ treeSelection.select(index);
+ theTree.ensureRowIsVisible(index);
+ if (focus) {
+ gDialog.tree.focus();
+ }
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdSelectProps.xhtml b/comm/suite/editor/components/dialogs/content/EdSelectProps.xhtml
new file mode 100644
index 0000000000..ff91b02e28
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdSelectProps.xhtml
@@ -0,0 +1,143 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edSelectProperties SYSTEM "chrome://editor/locale/EditorSelectProperties.dtd">
+%edSelectProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ buttons="accept,cancel">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdSelectProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <!-- Setting rows="7" on tree isn't working, equalsize vbox sets tree height. -->
+ <vbox equalsize="always">
+ <tree id="SelectTree" onselect="treeBoxObject.view.selectionChanged();" onkeyup="onTreeKeyUp(event);">
+ <treecols id="SelectCols">
+ <treecol id="SelectTextCol" flex="3" label="&TextHeader.label;" primary="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="SelectValCol" flex="2" label="&ValueHeader.label;"/>
+ <treecol id="SelectSelCol" label="&SelectedHeader.label;" cycler="true"/>
+ </treecols>
+
+ <treechildren id="SelectTreeChildren"/>
+ </tree>
+
+ <hbox flex="1">
+ <deck flex="1" id="SelectDeck" index="0">
+ <groupbox flex="1">
+ <hbox class="groupbox-title">
+ <label class="header">&Select.label;</label>
+ </hbox>
+ <grid flex="1"><columns><column flex="1"/><column/></columns>
+ <rows>
+ <row align="center">
+ <label control="SelectName" value="&SelectName.label;" accesskey="&SelectName.accesskey;"/>
+ <textbox id="SelectName" flex="1" oninput="onNameInput();"/>
+ </row>
+ <row align="center">
+ <label control="SelectSize" value="&SelectSize.label;" accesskey="&SelectSize.accesskey;"/>
+ <hbox>
+ <textbox id="SelectSize" class="narrow" oninput="forceInteger(this.id);"/>
+ </hbox>
+ </row>
+ <row>
+ <spacer/>
+ <checkbox id="SelectMultiple" flex="1" label="&SelectMultiple.label;" accesskey="&SelectMultiple.accesskey;" oncommand="onSelectMultipleClick();"/>
+ </row>
+ <row>
+ <spacer/>
+ <checkbox id="SelectDisabled" flex="1" label="&SelectDisabled.label;" accesskey="&SelectDisabled.accesskey;"/>
+ </row>
+ <row align="center">
+ <label control="SelectTabIndex" value="&SelectTabIndex.label;" accesskey="&SelectTabIndex.accesskey;"/>
+ <hbox>
+ <textbox id="SelectTabIndex" class="narrow" oninput="forceInteger(this.id);"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <groupbox flex="1">
+ <hbox class="groupbox-title">
+ <label class="header">&OptGroup.label;</label>
+ </hbox>
+ <grid flex="1"><columns><column flex="1"/><column/></columns>
+ <rows>
+ <row align="center">
+ <label control="OptGroupLabel" value="&OptGroupLabel.label;" accesskey="&OptGroupLabel.accesskey;"/>
+ <textbox id="OptGroupLabel" oninput="onLabelInput();"/>
+ </row>
+ <row>
+ <spacer/>
+ <checkbox id="OptGroupDisabled" label="&OptGroupDisabled.label;" accesskey="&OptGroupDisabled.accesskey;"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <groupbox flex="1">
+ <hbox class="groupbox-title">
+ <label class="header">&Option.label;</label>
+ </hbox>
+ <grid flex="1"><columns><column flex="1"/><column/></columns>
+ <rows>
+ <row align="center">
+ <label control="OptionText" value="&OptionText.label;" accesskey="&OptionText.accesskey;"/>
+ <textbox id="OptionText" oninput="onTextInput();"/>
+ </row>
+ <row align="center">
+ <checkbox id="OptionHasValue" label="&OptionValue.label;" accesskey="&OptionValue.accesskey;" oncommand="onHasValueClick();"/>
+ <textbox id="OptionValue" oninput="onValueInput();"/>
+ </row>
+ <row>
+ <spacer/>
+ <checkbox id="OptionSelected" label="&OptionSelected.label;" accesskey="&OptionSelected.accesskey;" oncommand="currentItem.cycleCell();"/>
+ </row>
+ <row>
+ <spacer/>
+ <checkbox id="OptionDisabled" label="&OptionDisabled.label;" accesskey="&OptionDisabled.accesskey;"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+ </deck>
+
+ <vbox>
+ <button label="&AddOption.label;" accesskey="&AddOption.accesskey;" oncommand="AddOption();"/>
+ <button label="&AddOptGroup.label;" accesskey="&AddOptGroup.accesskey;" oncommand="AddOptGroup();"/>
+ <button id="RemoveButton" label="&RemoveElement.label;" accesskey="&RemoveElement.accesskey;"
+ oncommand="RemoveElement();" disabled="true"/>
+ <button id="PreviousButton" label="&MoveElementUp.label;" accesskey="&MoveElementUp.accesskey;"
+ oncommand="currentItem.moveUp();" disabled="true" type="row"/>
+ <button id="NextButton" label="&MoveElementDown.label;" accesskey="&MoveElementDown.accesskey;"
+ oncommand="currentItem.moveDown();" disabled="true" type="row"/>
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </vbox>
+ </hbox>
+ </vbox>
+
+ <separator class="groove"/>
+
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdSnapToGrid.js b/comm/suite/editor/components/dialogs/content/EdSnapToGrid.js
new file mode 100644
index 0000000000..ed546d3540
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdSnapToGrid.js
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gEditor;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup()
+{
+ gEditor = GetCurrentEditor();
+ if (!gEditor)
+ {
+ window.close();
+ return;
+ }
+
+ gEditor instanceof Ci.nsIHTMLAbsPosEditor;
+
+ gDialog.enableSnapToGrid = document.getElementById("enableSnapToGrid");
+ gDialog.sizeInput = document.getElementById("size");
+ gDialog.sizeLabel = document.getElementById("sizeLabel");
+ gDialog.unitLabel = document.getElementById("unitLabel");
+
+ // Initialize control values based on existing attributes
+ InitDialog()
+
+ // SET FOCUS TO FIRST CONTROL
+ SetTextboxFocus(gDialog.sizeInput);
+
+ // Resize window
+ window.sizeToContent();
+
+ SetWindowLocation();
+}
+
+// Set dialog widgets with attribute data
+// We get them from globalElement copy so this can be used
+// by AdvancedEdit(), which is shared by all property dialogs
+function InitDialog()
+{
+ gDialog.enableSnapToGrid.checked = gEditor.snapToGridEnabled;
+ toggleSnapToGrid();
+
+ gDialog.sizeInput.value = gEditor.gridSize;
+}
+
+function onAccept()
+{
+ gEditor.snapToGridEnabled = gDialog.enableSnapToGrid.checked;
+ gEditor.gridSize = gDialog.sizeInput.value;
+}
+
+function toggleSnapToGrid()
+{
+ SetElementEnabledById("size", gDialog.enableSnapToGrid.checked)
+ SetElementEnabledById("sizeLabel", gDialog.enableSnapToGrid.checked)
+ SetElementEnabledById("unitLabel", gDialog.enableSnapToGrid.checked)
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdSnapToGrid.xhtml b/comm/suite/editor/components/dialogs/content/EdSnapToGrid.xhtml
new file mode 100644
index 0000000000..9ae6643975
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdSnapToGrid.xhtml
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorSnapToGrid.dtd">
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <!--- Element-specific methods -->
+ <script src="chrome://editor/content/EdSnapToGrid.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <checkbox id="enableSnapToGrid"
+ label="&enableSnapToGrid.label;"
+ accesskey="&enableSnapToGrid.accessKey;"
+ oncommand="toggleSnapToGrid();"/>
+
+ <spacer class="spacer"/>
+
+ <grid>
+ <columns><column/><column/><column /></columns>
+ <rows>
+ <row align="center">
+ <label value="&sizeEditField.label;"
+ id="sizeLabel"
+ control="size"
+ accesskey="&sizeEditField.accessKey;"/>
+ <textbox class="narrow" id="size" oninput="forceInteger('size')"/>
+ <label id="unitLabel"
+ value="&pixelsLabel.value;" />
+ </row>
+ </rows>
+ </grid>
+
+ <spacer class="spacer"/>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdSpellCheck.js b/comm/suite/editor/components/dialogs/content/EdSpellCheck.js
new file mode 100644
index 0000000000..ddb23726cd
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdSpellCheck.js
@@ -0,0 +1,495 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../../../mail/base/content/utilityOverlay.js */
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var { InlineSpellChecker } = ChromeUtils.import(
+ "resource://gre/modules/InlineSpellChecker.jsm"
+);
+
+var gMisspelledWord;
+var gSpellChecker = null;
+var gAllowSelectWord = true;
+var gPreviousReplaceWord = "";
+var gFirstTime = true;
+var gLastSelectedLang = null;
+var gDictCount = 0;
+
+document.addEventListener("dialogaccept", doDefault);
+document.addEventListener("dialogcancel", CancelSpellCheck);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ window.close();
+ return;
+ }
+
+ // Get the spellChecker shell
+ gSpellChecker = Cu.createSpellChecker();
+ if (!gSpellChecker) {
+ dump("SpellChecker not found!!!\n");
+ window.close();
+ return;
+ }
+
+ // Start the spell checker module.
+ try {
+ var skipBlockQuotes = window.arguments[1];
+ var enableSelectionChecking = window.arguments[2];
+
+ gSpellChecker.setFilterType(
+ skipBlockQuotes
+ ? Ci.nsIEditorSpellCheck.FILTERTYPE_MAIL
+ : Ci.nsIEditorSpellCheck.FILTERTYPE_NORMAL
+ );
+ gSpellChecker.InitSpellChecker(
+ editor,
+ enableSelectionChecking,
+ spellCheckStarted
+ );
+ } catch (ex) {
+ dump("*** Exception error: InitSpellChecker\n");
+ window.close();
+ }
+}
+
+function spellCheckStarted() {
+ gDialog.MisspelledWordLabel = document.getElementById("MisspelledWordLabel");
+ gDialog.MisspelledWord = document.getElementById("MisspelledWord");
+ gDialog.ReplaceButton = document.getElementById("Replace");
+ gDialog.IgnoreButton = document.getElementById("Ignore");
+ gDialog.StopButton = document.getElementById("Stop");
+ gDialog.CloseButton = document.getElementById("Close");
+ gDialog.ReplaceWordInput = document.getElementById("ReplaceWordInput");
+ gDialog.SuggestedList = document.getElementById("SuggestedList");
+ gDialog.LanguageMenulist = document.getElementById("LanguageMenulist");
+
+ // Fill in the language menulist and sync it up
+ // with the spellchecker's current language.
+
+ var curLang;
+
+ try {
+ curLang = gSpellChecker.GetCurrentDictionary();
+ } catch (ex) {
+ curLang = "";
+ }
+
+ InitLanguageMenu(curLang);
+
+ // Get the first misspelled word and setup all UI
+ NextWord();
+
+ // When startup param is true, setup different UI when spell checking
+ // just before sending mail message
+ if (window.arguments[0]) {
+ // If no misspelled words found, simply close dialog and send message
+ if (!gMisspelledWord) {
+ onClose();
+ return;
+ }
+
+ // Hide "Close" button and use "Send" instead
+ gDialog.CloseButton.hidden = true;
+ gDialog.CloseButton = document.getElementById("Send");
+ gDialog.CloseButton.hidden = false;
+ } else {
+ // Normal spell checking - hide the "Stop" button
+ // (Note that this button is the "Cancel" button for
+ // Esc keybinding and related window close actions)
+ gDialog.StopButton.hidden = true;
+ }
+
+ // Clear flag that determines message when
+ // no misspelled word is found
+ // (different message when used for the first time)
+ gFirstTime = false;
+
+ window.sizeToContent();
+}
+
+function InitLanguageMenu(aCurLang) {
+ // Get the list of dictionaries from
+ // the spellchecker.
+
+ var dictList;
+ try {
+ dictList = gSpellChecker.GetDictionaryList();
+ } catch (ex) {
+ dump("Failed to get DictionaryList!\n");
+ return;
+ }
+
+ // If we're not just starting up and dictionary count
+ // hasn't changed then no need to update the menu.
+ if (gDictCount == dictList.length) {
+ return;
+ }
+
+ // Store current dictionary count.
+ gDictCount = dictList.length;
+
+ var inlineSpellChecker = new InlineSpellChecker();
+ var sortedList = inlineSpellChecker.sortDictionaryList(dictList);
+
+ // Remove any languages from the list.
+ var languageMenuPopup = gDialog.LanguageMenulist.menupopup;
+ while (languageMenuPopup.firstChild.localName != "menuseparator") {
+ languageMenuPopup.firstChild.remove();
+ }
+
+ var defaultItem = null;
+
+ for (var i = 0; i < gDictCount; i++) {
+ let item = document.createXULElement("menuitem");
+ item.setAttribute("label", sortedList[i].displayName);
+ item.setAttribute("value", sortedList[i].localeCode);
+ let beforeItem = gDialog.LanguageMenulist.getItemAtIndex(i);
+ languageMenuPopup.insertBefore(item, beforeItem);
+
+ if (aCurLang && sortedList[i].localeCode == aCurLang) {
+ defaultItem = item;
+ }
+ }
+
+ // Now make sure the correct item in the menu list is selected.
+ if (defaultItem) {
+ gDialog.LanguageMenulist.selectedItem = defaultItem;
+ gLastSelectedLang = defaultItem;
+ }
+}
+
+function DoEnabling() {
+ if (!gMisspelledWord) {
+ // No more misspelled words
+ gDialog.MisspelledWord.setAttribute(
+ "value",
+ GetString(gFirstTime ? "NoMisspelledWord" : "CheckSpellingDone")
+ );
+
+ gDialog.ReplaceButton.removeAttribute("default");
+ gDialog.IgnoreButton.removeAttribute("default");
+
+ gDialog.CloseButton.setAttribute("default", "true");
+ // Shouldn't have to do this if "default" is true?
+ gDialog.CloseButton.focus();
+
+ SetElementEnabledById("MisspelledWordLabel", false);
+ SetElementEnabledById("ReplaceWordLabel", false);
+ SetElementEnabledById("ReplaceWordInput", false);
+ SetElementEnabledById("CheckWord", false);
+ SetElementEnabledById("SuggestedListLabel", false);
+ SetElementEnabledById("SuggestedList", false);
+ SetElementEnabledById("Ignore", false);
+ SetElementEnabledById("IgnoreAll", false);
+ SetElementEnabledById("Replace", false);
+ SetElementEnabledById("ReplaceAll", false);
+ SetElementEnabledById("AddToDictionary", false);
+ } else {
+ SetElementEnabledById("MisspelledWordLabel", true);
+ SetElementEnabledById("ReplaceWordLabel", true);
+ SetElementEnabledById("ReplaceWordInput", true);
+ SetElementEnabledById("CheckWord", true);
+ SetElementEnabledById("SuggestedListLabel", true);
+ SetElementEnabledById("SuggestedList", true);
+ SetElementEnabledById("Ignore", true);
+ SetElementEnabledById("IgnoreAll", true);
+ SetElementEnabledById("AddToDictionary", true);
+
+ gDialog.CloseButton.removeAttribute("default");
+ SetReplaceEnable();
+ }
+}
+
+function NextWord() {
+ gMisspelledWord = gSpellChecker.GetNextMisspelledWord();
+ SetWidgetsForMisspelledWord();
+}
+
+function SetWidgetsForMisspelledWord() {
+ gDialog.MisspelledWord.setAttribute("value", gMisspelledWord);
+
+ // Initial replace word is misspelled word
+ gDialog.ReplaceWordInput.value = gMisspelledWord;
+ gPreviousReplaceWord = gMisspelledWord;
+
+ // This sets gDialog.ReplaceWordInput to first suggested word in list
+ FillSuggestedList(gMisspelledWord);
+
+ DoEnabling();
+
+ if (gMisspelledWord) {
+ SetTextboxFocus(gDialog.ReplaceWordInput);
+ }
+}
+
+function CheckWord() {
+ var word = gDialog.ReplaceWordInput.value;
+ if (word) {
+ if (gSpellChecker.CheckCurrentWord(word)) {
+ FillSuggestedList(word);
+ SetReplaceEnable();
+ } else {
+ ClearListbox(gDialog.SuggestedList);
+ var item = gDialog.SuggestedList.appendItem(
+ GetString("CorrectSpelling"),
+ ""
+ );
+ if (item) {
+ item.setAttribute("disabled", "true");
+ }
+ // Suppress being able to select the message text
+ gAllowSelectWord = false;
+ }
+ }
+}
+
+function SelectSuggestedWord() {
+ if (gAllowSelectWord) {
+ if (gDialog.SuggestedList.selectedItem) {
+ var selValue = gDialog.SuggestedList.selectedItem.label;
+ gDialog.ReplaceWordInput.value = selValue;
+ gPreviousReplaceWord = selValue;
+ } else {
+ gDialog.ReplaceWordInput.value = gPreviousReplaceWord;
+ }
+ SetReplaceEnable();
+ }
+}
+
+function ChangeReplaceWord() {
+ // Calling this triggers SelectSuggestedWord(),
+ // so temporarily suppress the effect of that
+ var saveAllow = gAllowSelectWord;
+ gAllowSelectWord = false;
+
+ // Select matching word in list
+ var newSelectedItem;
+ var replaceWord = TrimString(gDialog.ReplaceWordInput.value);
+ if (replaceWord) {
+ for (var i = 0; i < gDialog.SuggestedList.getRowCount(); i++) {
+ var item = gDialog.SuggestedList.getItemAtIndex(i);
+ if (item.label == replaceWord) {
+ newSelectedItem = item;
+ break;
+ }
+ }
+ }
+ gDialog.SuggestedList.selectedItem = newSelectedItem;
+
+ gAllowSelectWord = saveAllow;
+
+ // Remember the new word
+ gPreviousReplaceWord = gDialog.ReplaceWordInput.value;
+
+ SetReplaceEnable();
+}
+
+function Ignore() {
+ NextWord();
+}
+
+function IgnoreAll() {
+ if (gMisspelledWord) {
+ gSpellChecker.IgnoreWordAllOccurrences(gMisspelledWord);
+ }
+ NextWord();
+}
+
+function Replace(newWord) {
+ if (!newWord) {
+ return;
+ }
+
+ if (gMisspelledWord && gMisspelledWord != newWord) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+ try {
+ gSpellChecker.ReplaceWord(gMisspelledWord, newWord, false);
+ } catch (e) {}
+ editor.endTransaction();
+ }
+ NextWord();
+}
+
+function ReplaceAll() {
+ var newWord = gDialog.ReplaceWordInput.value;
+ if (gMisspelledWord && gMisspelledWord != newWord) {
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+ try {
+ gSpellChecker.ReplaceWord(gMisspelledWord, newWord, true);
+ } catch (e) {}
+ editor.endTransaction();
+ }
+ NextWord();
+}
+
+function AddToDictionary() {
+ if (gMisspelledWord) {
+ gSpellChecker.AddWordToDictionary(gMisspelledWord);
+ }
+ NextWord();
+}
+
+function EditDictionary() {
+ window.openDialog(
+ "chrome://editor/content/EdDictionary.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ "",
+ gMisspelledWord
+ );
+}
+
+function SelectLanguage() {
+ var item = gDialog.LanguageMenulist.selectedItem;
+ if (item.value != "more-cmd") {
+ gSpellChecker.SetCurrentDictionary(item.value);
+ // For compose windows we need to set the "lang" attribute so the
+ // core editor uses the correct dictionary for the inline spell check.
+ if (window.arguments[1]) {
+ if ("ComposeChangeLanguage" in window.opener) {
+ // We came here from a compose window.
+ window.opener.ComposeChangeLanguage(item.value);
+ } else {
+ window.opener.document.documentElement.setAttribute("lang", item.value);
+ }
+ }
+ gLastSelectedLang = item;
+ } else {
+ openDictionaryList();
+
+ if (gLastSelectedLang) {
+ gDialog.LanguageMenulist.selectedItem = gLastSelectedLang;
+ }
+ }
+}
+
+function Recheck() {
+ var recheckLanguage;
+
+ function finishRecheck() {
+ gSpellChecker.SetCurrentDictionary(recheckLanguage);
+ gMisspelledWord = gSpellChecker.GetNextMisspelledWord();
+ SetWidgetsForMisspelledWord();
+ }
+
+ // TODO: Should we bother to add a "Recheck" method to interface?
+ try {
+ recheckLanguage = gSpellChecker.GetCurrentDictionary();
+ gSpellChecker.UninitSpellChecker();
+ // Clear the ignore all list.
+ Cc["@mozilla.org/spellchecker/personaldictionary;1"]
+ .getService(Ci.mozIPersonalDictionary)
+ .endSession();
+ gSpellChecker.InitSpellChecker(GetCurrentEditor(), false, finishRecheck);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+}
+
+function FillSuggestedList(misspelledWord) {
+ var list = gDialog.SuggestedList;
+
+ // Clear the current contents of the list
+ gAllowSelectWord = false;
+ ClearListbox(list);
+ var item;
+
+ if (misspelledWord.length > 0) {
+ // Get suggested words until an empty string is returned
+ var count = 0;
+ do {
+ var word = gSpellChecker.GetSuggestedWord();
+ if (word.length > 0) {
+ list.appendItem(word, "");
+ count++;
+ }
+ } while (word.length > 0);
+
+ if (count == 0) {
+ // No suggestions - show a message but don't let user select it
+ item = list.appendItem(GetString("NoSuggestedWords"));
+ if (item) {
+ item.setAttribute("disabled", "true");
+ }
+ gAllowSelectWord = false;
+ } else {
+ gAllowSelectWord = true;
+ // Initialize with first suggested list by selecting it
+ gDialog.SuggestedList.selectedIndex = 0;
+ }
+ } else {
+ item = list.appendItem("", "");
+ if (item) {
+ item.setAttribute("disabled", "true");
+ }
+ }
+}
+
+function SetReplaceEnable() {
+ // Enable "Change..." buttons only if new word is different than misspelled
+ var newWord = gDialog.ReplaceWordInput.value;
+ var enable = newWord.length > 0 && newWord != gMisspelledWord;
+ SetElementEnabledById("Replace", enable);
+ SetElementEnabledById("ReplaceAll", enable);
+ if (enable) {
+ gDialog.ReplaceButton.setAttribute("default", "true");
+ gDialog.IgnoreButton.removeAttribute("default");
+ } else {
+ gDialog.IgnoreButton.setAttribute("default", "true");
+ gDialog.ReplaceButton.removeAttribute("default");
+ }
+}
+
+function doDefault(event) {
+ if (gDialog.ReplaceButton.getAttribute("default") == "true") {
+ Replace(gDialog.ReplaceWordInput.value);
+ } else if (gDialog.IgnoreButton.getAttribute("default") == "true") {
+ Ignore();
+ } else if (gDialog.CloseButton.getAttribute("default") == "true") {
+ onClose();
+ }
+
+ event.preventDefault();
+}
+
+function ExitSpellChecker() {
+ if (gSpellChecker) {
+ try {
+ gSpellChecker.UninitSpellChecker();
+ // now check the document over again with the new dictionary
+ // if we have an inline spellchecker
+ if (
+ "InlineSpellCheckerUI" in window.opener &&
+ window.opener.InlineSpellCheckerUI.enabled
+ ) {
+ window.opener.InlineSpellCheckerUI.mInlineSpellChecker.spellCheckRange(
+ null
+ );
+ }
+ } finally {
+ gSpellChecker = null;
+ }
+ }
+}
+
+function CancelSpellCheck() {
+ ExitSpellChecker();
+
+ // Signal to calling window that we canceled
+ window.opener.cancelSendMessage = true;
+}
+
+function onClose() {
+ ExitSpellChecker();
+
+ window.opener.cancelSendMessage = false;
+ window.close();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdSpellCheck.xhtml b/comm/suite/editor/components/dialogs/content/EdSpellCheck.xhtml
new file mode 100644
index 0000000000..4cb9f49198
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdSpellCheck.xhtml
@@ -0,0 +1,113 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorSpellCheck.dtd">
+
+<!-- dialog containing a control requiring initial setup -->
+<dialog id="spellCheckDlg" buttons="cancel" title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ persist="screenX screenY"
+ onload="Startup()">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://communicator/content/utilityOverlay.js"/>
+ <script src="chrome://editor/content/EdSpellCheck.js"/>
+ <script src="chrome://global/content/contentAreaUtils.js"/>
+
+ <stringbundle id="languageBundle" src="chrome://global/locale/languageNames.properties"/>
+ <stringbundle id="regionBundle" src="chrome://global/locale/regionNames.properties"/>
+
+ <grid>
+ <columns>
+ <column class="spell-check"/>
+ <column class="spell-check" flex="1"/>
+ <column class="spell-check"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label id="MisspelledWordLabel" value="&misspelledWord.label;"/>
+ <label class="bold" id="MisspelledWord" crop="end"/>
+ <button class="spell-check" label="&recheckButton2.label;" oncommand="Recheck();"
+ accesskey="&recheckButton2.accessKey;"/>
+ </row>
+ <row align="center">
+ <label id="ReplaceWordLabel" value="&wordEditField.label;"
+ control="ReplaceWordInput"
+ accesskey="&wordEditField.accessKey;"/>
+ <textbox id="ReplaceWordInput" oninput="ChangeReplaceWord()" flex="1"/>
+ <button id="CheckWord" oncommand="CheckWord()" label="&checkwordButton.label;"
+ accesskey="&checkwordButton.accessKey;"/>
+ </row>
+ </rows>
+ </grid>
+ <label id="SuggestedListLabel" value="&suggestions.label;"
+ control="SuggestedList"
+ accesskey="&suggestions.accessKey;"/>
+ <grid flex="1">
+ <columns><column flex="1"/><column/></columns>
+ <rows>
+ <row flex="1">
+ <!-- BUG! setting class="MinWidth20em" on tree doesn't work (width=0) -->
+ <richlistbox id="SuggestedList"
+ class="theme-listbox"
+ onselect="SelectSuggestedWord()"
+ ondblclick="if (gAllowSelectWord) { Replace(event.target.value); }"/>
+ <vbox>
+ <grid>
+ <columns><column class="spell-check" flex="1"/><column class="spell-check" flex="1"/></columns>
+ <rows>
+ <row>
+ <button id="Replace" label="&replaceButton.label;"
+ oncommand="Replace(gDialog.ReplaceWordInput.value);"
+ accesskey="&replaceButton.accessKey;"/>
+ <button id="Ignore" oncommand="Ignore();" label="&ignoreButton.label;"
+ accesskey="&ignoreButton.accessKey;"/>
+ </row>
+ <row>
+ <button id="ReplaceAll" oncommand="ReplaceAll();" label="&replaceAllButton.label;"
+ accesskey="&replaceAllButton.accessKey;"/>
+ <button id="IgnoreAll" oncommand="IgnoreAll();" label="&ignoreAllButton.label;"
+ accesskey="&ignoreAllButton.accessKey;"/>
+ </row>
+ </rows>
+ </grid>
+ <separator/>
+ <label value="&userDictionary.label;"/>
+ <hbox align="start">
+ <button class="spell-check" id="AddToDictionary" oncommand="AddToDictionary()" label="&addToUserDictionaryButton.label;"
+ accesskey="&addToUserDictionaryButton.accessKey;"/>
+ <button class="spell-check" id="EditDictionary" oncommand="EditDictionary()" label="&editUserDictionaryButton.label;"
+ accesskey="&editUserDictionaryButton.accessKey;"/>
+ </hbox>
+ </vbox>
+ </row>
+ <label value ="&languagePopup.label;"
+ control="LanguageMenulist"
+ accesskey="&languagePopup.accessKey;"/>
+ <row>
+ <menulist id="LanguageMenulist" oncommand="SelectLanguage()">
+ <menupopup onpopupshowing="InitLanguageMenu(gDialog.LanguageMenulist.selectedItem.value);">
+ <!-- dynamic content populated by JS -->
+ <menuseparator/>
+ <menuitem value="more-cmd" label="&moreDictionaries.label;"/>
+ </menupopup>
+ </menulist>
+ <hbox flex="1">
+ <button class="spell-check" dlgtype="cancel" id="Stop" label="&stopButton.label;" oncommand="CancelSpellCheck();"
+ accesskey="&stopButton.accessKey;"/>
+ <spacer flex="1"/>
+ <button class="spell-check" id="Close" label="&closeButton.label;" oncommand="onClose();"
+ accesskey="&closeButton.accessKey;"/>
+ <button class="spell-check" id="Send" label="&sendButton.label;" oncommand="onClose();"
+ accesskey="&sendButton.accessKey;" hidden="true"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdTableProps.js b/comm/suite/editor/components/dialogs/content/EdTableProps.js
new file mode 100644
index 0000000000..e78a89bc41
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdTableProps.js
@@ -0,0 +1,1439 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+// Cancel() is in EdDialogCommon.js
+
+var gTableElement;
+var gCellElement;
+var gTableCaptionElement;
+var globalCellElement;
+var globalTableElement;
+var gValidateTab;
+const defHAlign = "left";
+const centerStr = "center"; // Index=1
+const rightStr = "right"; // 2
+const justifyStr = "justify"; // 3
+const charStr = "char"; // 4
+const defVAlign = "middle";
+const topStr = "top";
+const bottomStr = "bottom";
+const bgcolor = "bgcolor";
+var gTableColor;
+var gCellColor;
+
+const cssBackgroundColorStr = "background-color";
+
+var gRowCount = 1;
+var gColCount = 1;
+var gLastRowIndex;
+var gLastColIndex;
+var gNewRowCount;
+var gNewColCount;
+var gCurRowIndex;
+var gCurColIndex;
+var gCurColSpan;
+var gSelectedCellsType = 1;
+const SELECT_CELL = 1;
+const SELECT_ROW = 2;
+const SELECT_COLUMN = 3;
+const RESET_SELECTION = 0;
+var gCellData = {
+ value: null,
+ startRowIndex: 0,
+ startColIndex: 0,
+ rowSpan: 0,
+ colSpan: 0,
+ actualRowSpan: 0,
+ actualColSpan: 0,
+ isSelected: false,
+};
+var gAdvancedEditUsed;
+var gAlignWasChar = false;
+
+/*
+From C++:
+ 0 TABLESELECTION_TABLE
+ 1 TABLESELECTION_CELL There are 1 or more cells selected
+ but complete rows or columns are not selected
+ 2 TABLESELECTION_ROW All cells are in 1 or more rows
+ and in each row, all cells selected
+ Note: This is the value if all rows (thus all cells) are selected
+ 3 TABLESELECTION_COLUMN All cells are in 1 or more columns
+*/
+
+var gSelectedCellCount = 0;
+var gApplyUsed = false;
+var gSelection;
+var gCellDataChanged = false;
+var gCanDelete = false;
+var gUseCSS = true;
+var gActiveEditor;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogextra1", Apply);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ gActiveEditor = GetCurrentTableEditor();
+ if (!gActiveEditor) {
+ window.close();
+ return;
+ }
+
+ try {
+ gSelection = gActiveEditor.selection;
+ } catch (e) {}
+ if (!gSelection) {
+ return;
+ }
+
+ // Get dialog widgets - Table Panel
+ gDialog.TableRowsInput = document.getElementById("TableRowsInput");
+ gDialog.TableColumnsInput = document.getElementById("TableColumnsInput");
+ gDialog.TableWidthInput = document.getElementById("TableWidthInput");
+ gDialog.TableWidthUnits = document.getElementById("TableWidthUnits");
+ gDialog.TableHeightInput = document.getElementById("TableHeightInput");
+ gDialog.TableHeightUnits = document.getElementById("TableHeightUnits");
+ try {
+ if (
+ !Services.prefs.getBoolPref("editor.use_css") ||
+ gActiveEditor.flags & 1
+ ) {
+ gUseCSS = false;
+ var tableHeightLabel = document.getElementById("TableHeightLabel");
+ tableHeightLabel.remove();
+ gDialog.TableHeightInput.remove();
+ gDialog.TableHeightUnits.remove();
+ }
+ } catch (e) {}
+ gDialog.BorderWidthInput = document.getElementById("BorderWidthInput");
+ gDialog.SpacingInput = document.getElementById("SpacingInput");
+ gDialog.PaddingInput = document.getElementById("PaddingInput");
+ gDialog.TableAlignList = document.getElementById("TableAlignList");
+ gDialog.TableCaptionList = document.getElementById("TableCaptionList");
+ gDialog.TableInheritColor = document.getElementById("TableInheritColor");
+ gDialog.TabBox = document.getElementById("TabBox");
+
+ // Cell Panel
+ gDialog.SelectionList = document.getElementById("SelectionList");
+ gDialog.PreviousButton = document.getElementById("PreviousButton");
+ gDialog.NextButton = document.getElementById("NextButton");
+ // Currently, we always apply changes and load new attributes when changing selection
+ // (Let's keep this for possible future use)
+ // gDialog.ApplyBeforeMove = document.getElementById("ApplyBeforeMove");
+ // gDialog.KeepCurrentData = document.getElementById("KeepCurrentData");
+
+ gDialog.CellHeightInput = document.getElementById("CellHeightInput");
+ gDialog.CellHeightUnits = document.getElementById("CellHeightUnits");
+ gDialog.CellWidthInput = document.getElementById("CellWidthInput");
+ gDialog.CellWidthUnits = document.getElementById("CellWidthUnits");
+ gDialog.CellHAlignList = document.getElementById("CellHAlignList");
+ gDialog.CellVAlignList = document.getElementById("CellVAlignList");
+ gDialog.CellInheritColor = document.getElementById("CellInheritColor");
+ gDialog.CellStyleList = document.getElementById("CellStyleList");
+ gDialog.TextWrapList = document.getElementById("TextWrapList");
+
+ // In cell panel, user must tell us which attributes to apply via checkboxes,
+ // else we would apply values from one cell to ALL in selection
+ // and that's probably not what they expect!
+ gDialog.CellHeightCheckbox = document.getElementById("CellHeightCheckbox");
+ gDialog.CellWidthCheckbox = document.getElementById("CellWidthCheckbox");
+ gDialog.CellHAlignCheckbox = document.getElementById("CellHAlignCheckbox");
+ gDialog.CellVAlignCheckbox = document.getElementById("CellVAlignCheckbox");
+ gDialog.CellStyleCheckbox = document.getElementById("CellStyleCheckbox");
+ gDialog.TextWrapCheckbox = document.getElementById("TextWrapCheckbox");
+ gDialog.CellColorCheckbox = document.getElementById("CellColorCheckbox");
+ gDialog.TableTab = document.getElementById("TableTab");
+ gDialog.CellTab = document.getElementById("CellTab");
+ gDialog.AdvancedEditCell = document.getElementById("AdvancedEditButton2");
+ // Save "normal" tooltip message for Advanced Edit button
+ gDialog.AdvancedEditCellToolTipText = gDialog.AdvancedEditCell.getAttribute(
+ "tooltiptext"
+ );
+
+ try {
+ gTableElement = gActiveEditor.getElementOrParentByTagName("table", null);
+ } catch (e) {}
+ if (!gTableElement) {
+ dump("Failed to get table element!\n");
+ window.close();
+ return;
+ }
+ globalTableElement = gTableElement.cloneNode(false);
+
+ var tagNameObj = { value: "" };
+ var countObj = { value: 0 };
+ var tableOrCellElement;
+ try {
+ tableOrCellElement = gActiveEditor.getSelectedOrParentTableElement(
+ tagNameObj,
+ countObj
+ );
+ } catch (e) {}
+
+ if (tagNameObj.value == "td") {
+ // We are in a cell
+ gSelectedCellCount = countObj.value;
+ gCellElement = tableOrCellElement;
+ globalCellElement = gCellElement.cloneNode(false);
+
+ // Tells us whether cell, row, or column is selected
+ try {
+ gSelectedCellsType = gActiveEditor.getSelectedCellsType(gTableElement);
+ } catch (e) {}
+
+ // Ignore types except Cell, Row, and Column
+ if (
+ gSelectedCellsType < SELECT_CELL ||
+ gSelectedCellsType > SELECT_COLUMN
+ ) {
+ gSelectedCellsType = SELECT_CELL;
+ }
+
+ // Be sure at least 1 cell is selected.
+ // (If the count is 0, then we were inside the cell.)
+ if (gSelectedCellCount == 0) {
+ DoCellSelection();
+ }
+
+ // Get location in the cell map
+ var rowIndexObj = { value: 0 };
+ var colIndexObj = { value: 0 };
+ try {
+ gActiveEditor.getCellIndexes(gCellElement, rowIndexObj, colIndexObj);
+ } catch (e) {}
+ gCurRowIndex = rowIndexObj.value;
+ gCurColIndex = colIndexObj.value;
+
+ // We save the current colspan to quickly
+ // move selection from from cell to cell
+ if (GetCellData(gCurRowIndex, gCurColIndex)) {
+ gCurColSpan = gCellData.colSpan;
+ }
+
+ // Starting TabPanel name is passed in
+ if (window.arguments[1] == "CellPanel") {
+ gDialog.TabBox.selectedTab = gDialog.CellTab;
+ }
+ }
+
+ if (gDialog.TabBox.selectedTab == gDialog.TableTab) {
+ // We may call this with table selected, but no cell,
+ // so disable the Cell Properties tab
+ if (!gCellElement) {
+ // XXX: Disabling of tabs is currently broken, so for
+ // now we'll just remove the tab completely.
+ // gDialog.CellTab.disabled = true;
+ gDialog.CellTab.remove();
+ }
+ }
+
+ // Note: we must use gTableElement, not globalTableElement for these,
+ // thus we should not put this in InitDialog.
+ // Instead, monitor desired counts with separate globals
+ var rowCountObj = { value: 0 };
+ var colCountObj = { value: 0 };
+ try {
+ gActiveEditor.getTableSize(gTableElement, rowCountObj, colCountObj);
+ } catch (e) {}
+
+ gRowCount = rowCountObj.value;
+ gLastRowIndex = gRowCount - 1;
+ gColCount = colCountObj.value;
+ gLastColIndex = gColCount - 1;
+
+ // Set appropriate icons and enable state for the Previous/Next buttons
+ SetSelectionButtons();
+
+ // If only one cell in table, disable change-selection widgets
+ if (gRowCount == 1 && gColCount == 1) {
+ gDialog.SelectionList.setAttribute("disabled", "true");
+ }
+
+ // User can change these via textboxes
+ gNewRowCount = gRowCount;
+ gNewColCount = gColCount;
+
+ // This flag is used to control whether set check state
+ // on "set attribute" checkboxes
+ // (Advanced Edit dialog use calls InitDialog when done)
+ gAdvancedEditUsed = false;
+ InitDialog();
+ gAdvancedEditUsed = true;
+
+ // If first initializing, we really aren't changing anything
+ gCellDataChanged = false;
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ // Get Table attributes
+ gDialog.TableRowsInput.value = gRowCount;
+ gDialog.TableColumnsInput.value = gColCount;
+ gDialog.TableWidthInput.value = InitPixelOrPercentMenulist(
+ globalTableElement,
+ gTableElement,
+ "width",
+ "TableWidthUnits",
+ gPercent
+ );
+ if (gUseCSS) {
+ gDialog.TableHeightInput.value = InitPixelOrPercentMenulist(
+ globalTableElement,
+ gTableElement,
+ "height",
+ "TableHeightUnits",
+ gPercent
+ );
+ }
+ gDialog.BorderWidthInput.value = globalTableElement.border;
+ gDialog.SpacingInput.value = globalTableElement.cellSpacing;
+ gDialog.PaddingInput.value = globalTableElement.cellPadding;
+
+ var marginLeft = GetHTMLOrCSSStyleValue(
+ globalTableElement,
+ "align",
+ "margin-left"
+ );
+ var marginRight = GetHTMLOrCSSStyleValue(
+ globalTableElement,
+ "align",
+ "margin-right"
+ );
+ var halign = marginLeft.toLowerCase() + " " + marginRight.toLowerCase();
+ if (halign == "center center" || halign == "auto auto") {
+ gDialog.TableAlignList.value = "center";
+ } else if (halign == "right right" || halign == "auto 0px") {
+ gDialog.TableAlignList.value = "right";
+ } else {
+ // Default is left.
+ gDialog.TableAlignList.value = "left";
+ }
+
+ // Be sure to get caption from table in doc, not the copied "globalTableElement"
+ gTableCaptionElement = gTableElement.caption;
+ if (gTableCaptionElement) {
+ var align = GetHTMLOrCSSStyleValue(
+ gTableCaptionElement,
+ "align",
+ "caption-side"
+ );
+ if (align != "bottom" && align != "left" && align != "right") {
+ align = "top";
+ }
+ gDialog.TableCaptionList.value = align;
+ }
+
+ gTableColor = GetHTMLOrCSSStyleValue(
+ globalTableElement,
+ bgcolor,
+ cssBackgroundColorStr
+ );
+ gTableColor = ConvertRGBColorIntoHEXColor(gTableColor);
+ SetColor("tableBackgroundCW", gTableColor);
+
+ InitCellPanel();
+}
+
+function InitCellPanel() {
+ // Get cell attributes
+ if (globalCellElement) {
+ // This assumes order of items is Cell, Row, Column
+ gDialog.SelectionList.value = gSelectedCellsType;
+
+ var previousValue = gDialog.CellHeightInput.value;
+ gDialog.CellHeightInput.value = InitPixelOrPercentMenulist(
+ globalCellElement,
+ gCellElement,
+ "height",
+ "CellHeightUnits",
+ gPixel
+ );
+ gDialog.CellHeightCheckbox.checked =
+ gAdvancedEditUsed && previousValue != gDialog.CellHeightInput.value;
+
+ previousValue = gDialog.CellWidthInput.value;
+ gDialog.CellWidthInput.value = InitPixelOrPercentMenulist(
+ globalCellElement,
+ gCellElement,
+ "width",
+ "CellWidthUnits",
+ gPixel
+ );
+ gDialog.CellWidthCheckbox.checked =
+ gAdvancedEditUsed && previousValue != gDialog.CellWidthInput.value;
+
+ var previousIndex = gDialog.CellVAlignList.selectedIndex;
+ var valign = GetHTMLOrCSSStyleValue(
+ globalCellElement,
+ "valign",
+ "vertical-align"
+ ).toLowerCase();
+ if (valign == topStr || valign == bottomStr) {
+ gDialog.CellVAlignList.value = valign;
+ } else {
+ // Default is middle.
+ gDialog.CellVAlignList.value = defVAlign;
+ }
+
+ gDialog.CellVAlignCheckbox.checked =
+ gAdvancedEditUsed &&
+ previousIndex != gDialog.CellVAlignList.selectedIndex;
+
+ previousIndex = gDialog.CellHAlignList.selectedIndex;
+
+ gAlignWasChar = false;
+
+ var halign = GetHTMLOrCSSStyleValue(
+ globalCellElement,
+ "align",
+ "text-align"
+ ).toLowerCase();
+ switch (halign) {
+ case centerStr:
+ case rightStr:
+ case justifyStr:
+ gDialog.CellHAlignList.value = halign;
+ break;
+ case charStr:
+ // We don't support UI for this because layout doesn't work: bug 2212.
+ // Remember that's what they had so we don't change it
+ // unless they change the alignment by using the menulist
+ gAlignWasChar = true;
+ // Fall through to use show default alignment in menu
+ default:
+ // Default depends on cell type (TH is "center", TD is "left")
+ gDialog.CellHAlignList.value =
+ globalCellElement.nodeName.toLowerCase() == "th" ? "center" : "left";
+ break;
+ }
+
+ gDialog.CellHAlignCheckbox.checked =
+ gAdvancedEditUsed &&
+ previousIndex != gDialog.CellHAlignList.selectedIndex;
+
+ previousIndex = gDialog.CellStyleList.selectedIndex;
+ gDialog.CellStyleList.value = globalCellElement.nodeName.toLowerCase();
+ gDialog.CellStyleCheckbox.checked =
+ gAdvancedEditUsed && previousIndex != gDialog.CellStyleList.selectedIndex;
+
+ previousIndex = gDialog.TextWrapList.selectedIndex;
+ if (
+ GetHTMLOrCSSStyleValue(globalCellElement, "nowrap", "white-space") ==
+ "nowrap"
+ ) {
+ gDialog.TextWrapList.value = "nowrap";
+ } else {
+ gDialog.TextWrapList.value = "wrap";
+ }
+ gDialog.TextWrapCheckbox.checked =
+ gAdvancedEditUsed && previousIndex != gDialog.TextWrapList.selectedIndex;
+
+ previousValue = gCellColor;
+ gCellColor = GetHTMLOrCSSStyleValue(
+ globalCellElement,
+ bgcolor,
+ cssBackgroundColorStr
+ );
+ gCellColor = ConvertRGBColorIntoHEXColor(gCellColor);
+ SetColor("cellBackgroundCW", gCellColor);
+ gDialog.CellColorCheckbox.checked =
+ gAdvancedEditUsed && previousValue != gCellColor;
+
+ // We want to set this true in case changes came
+ // from Advanced Edit dialog session (must assume something changed)
+ gCellDataChanged = true;
+ }
+}
+
+function GetCellData(rowIndex, colIndex) {
+ // Get actual rowspan and colspan
+ var startRowIndexObj = { value: 0 };
+ var startColIndexObj = { value: 0 };
+ var rowSpanObj = { value: 0 };
+ var colSpanObj = { value: 0 };
+ var actualRowSpanObj = { value: 0 };
+ var actualColSpanObj = { value: 0 };
+ var isSelectedObj = { value: false };
+
+ try {
+ gActiveEditor.getCellDataAt(
+ gTableElement,
+ rowIndex,
+ colIndex,
+ gCellData,
+ startRowIndexObj,
+ startColIndexObj,
+ rowSpanObj,
+ colSpanObj,
+ actualRowSpanObj,
+ actualColSpanObj,
+ isSelectedObj
+ );
+ // We didn't find a cell
+ if (!gCellData.value) {
+ return false;
+ }
+ } catch (ex) {
+ return false;
+ }
+
+ gCellData.startRowIndex = startRowIndexObj.value;
+ gCellData.startColIndex = startColIndexObj.value;
+ gCellData.rowSpan = rowSpanObj.value;
+ gCellData.colSpan = colSpanObj.value;
+ gCellData.actualRowSpan = actualRowSpanObj.value;
+ gCellData.actualColSpan = actualColSpanObj.value;
+ gCellData.isSelected = isSelectedObj.value;
+ return true;
+}
+
+function SelectCellHAlign() {
+ SetCheckbox("CellHAlignCheckbox");
+ // Once user changes the alignment,
+ // we lose their original "CharAt" alignment"
+ gAlignWasChar = false;
+}
+
+function GetColorAndUpdate(ColorWellID) {
+ var colorWell = document.getElementById(ColorWellID);
+ if (!colorWell) {
+ return;
+ }
+
+ var colorObj = {
+ Type: "",
+ TableColor: 0,
+ CellColor: 0,
+ NoDefault: false,
+ Cancel: false,
+ BackgroundColor: 0,
+ };
+
+ switch (ColorWellID) {
+ case "tableBackgroundCW":
+ colorObj.Type = "Table";
+ colorObj.TableColor = gTableColor;
+ break;
+ case "cellBackgroundCW":
+ colorObj.Type = "Cell";
+ colorObj.CellColor = gCellColor;
+ break;
+ }
+ window.openDialog(
+ "chrome://editor/content/EdColorPicker.xhtml",
+ "_blank",
+ "chrome,close,titlebar,modal",
+ "",
+ colorObj
+ );
+
+ // User canceled the dialog
+ if (colorObj.Cancel) {
+ return;
+ }
+
+ switch (ColorWellID) {
+ case "tableBackgroundCW":
+ gTableColor = colorObj.BackgroundColor;
+ SetColor(ColorWellID, gTableColor);
+ break;
+ case "cellBackgroundCW":
+ gCellColor = colorObj.BackgroundColor;
+ SetColor(ColorWellID, gCellColor);
+ SetCheckbox("CellColorCheckbox");
+ break;
+ }
+}
+
+function SetColor(ColorWellID, color) {
+ // Save the color
+ if (ColorWellID == "cellBackgroundCW") {
+ if (color) {
+ try {
+ gActiveEditor.setAttributeOrEquivalent(
+ globalCellElement,
+ bgcolor,
+ color,
+ true
+ );
+ } catch (e) {}
+ gDialog.CellInheritColor.collapsed = true;
+ } else {
+ try {
+ gActiveEditor.removeAttributeOrEquivalent(
+ globalCellElement,
+ bgcolor,
+ true
+ );
+ } catch (e) {}
+ // Reveal addition message explaining "default" color
+ gDialog.CellInheritColor.collapsed = false;
+ }
+ } else {
+ if (color) {
+ try {
+ gActiveEditor.setAttributeOrEquivalent(
+ globalTableElement,
+ bgcolor,
+ color,
+ true
+ );
+ } catch (e) {}
+ gDialog.TableInheritColor.collapsed = true;
+ } else {
+ try {
+ gActiveEditor.removeAttributeOrEquivalent(
+ globalTableElement,
+ bgcolor,
+ true
+ );
+ } catch (e) {}
+ gDialog.TableInheritColor.collapsed = false;
+ }
+ SetCheckbox("CellColorCheckbox");
+ }
+
+ setColorWell(ColorWellID, color);
+}
+
+function ChangeSelectionToFirstCell() {
+ if (!GetCellData(0, 0)) {
+ dump("Can't find first cell in table!\n");
+ return;
+ }
+ gCellElement = gCellData.value;
+ globalCellElement = gCellElement;
+
+ gCurRowIndex = 0;
+ gCurColIndex = 0;
+ ChangeSelection(RESET_SELECTION);
+}
+
+function ChangeSelection(newType) {
+ newType = Number(newType);
+
+ if (gSelectedCellsType == newType) {
+ return;
+ }
+
+ if (newType == RESET_SELECTION) {
+ // Restore selection to existing focus cell
+ gSelection.collapse(gCellElement, 0);
+ } else {
+ gSelectedCellsType = newType;
+ }
+
+ // Keep the same focus gCellElement, just change the type
+ DoCellSelection();
+ SetSelectionButtons();
+
+ // Note: globalCellElement should still be a clone of gCellElement
+}
+
+function MoveSelection(forward) {
+ var newRowIndex = gCurRowIndex;
+ var newColIndex = gCurColIndex;
+ var inRow = false;
+
+ if (gSelectedCellsType == SELECT_ROW) {
+ newRowIndex += forward ? 1 : -1;
+
+ // Wrap around if before first or after last row
+ if (newRowIndex < 0) {
+ newRowIndex = gLastRowIndex;
+ } else if (newRowIndex > gLastRowIndex) {
+ newRowIndex = 0;
+ }
+ inRow = true;
+
+ // Use first cell in row for focus cell
+ newColIndex = 0;
+ } else {
+ // Cell or column:
+ if (!forward) {
+ newColIndex--;
+ }
+
+ if (gSelectedCellsType == SELECT_CELL) {
+ // Skip to next cell
+ if (forward) {
+ newColIndex += gCurColSpan;
+ }
+ } else {
+ // SELECT_COLUMN
+ // Use first cell in column for focus cell
+ newRowIndex = 0;
+
+ // Don't skip by colspan,
+ // but find first cell in next cellmap column
+ if (forward) {
+ newColIndex++;
+ }
+ }
+
+ if (newColIndex < 0) {
+ // Request is before the first cell in column
+
+ // Wrap to last cell in column
+ newColIndex = gLastColIndex;
+
+ if (gSelectedCellsType == SELECT_CELL) {
+ // If moving by cell, also wrap to previous...
+ if (newRowIndex > 0) {
+ newRowIndex -= 1;
+ } else {
+ // ...or the last row.
+ newRowIndex = gLastRowIndex;
+ }
+
+ inRow = true;
+ }
+ } else if (newColIndex > gLastColIndex) {
+ // Request is after the last cell in column
+
+ // Wrap to first cell in column
+ newColIndex = 0;
+
+ if (gSelectedCellsType == SELECT_CELL) {
+ // If moving by cell, also wrap to next...
+ if (newRowIndex < gLastRowIndex) {
+ newRowIndex++;
+ } else {
+ // ...or the first row.
+ newRowIndex = 0;
+ }
+
+ inRow = true;
+ }
+ }
+ }
+
+ // Get the cell at the new location
+ do {
+ if (!GetCellData(newRowIndex, newColIndex)) {
+ dump("MoveSelection: CELL NOT FOUND\n");
+ return;
+ }
+ if (inRow) {
+ if (gCellData.startRowIndex == newRowIndex) {
+ break;
+ } else {
+ // Cell spans from a row above, look for the next cell in row.
+ newRowIndex += gCellData.actualRowSpan;
+ }
+ } else if (gCellData.startColIndex == newColIndex) {
+ break;
+ } else {
+ // Cell spans from a Col above, look for the next cell in column
+ newColIndex += gCellData.actualColSpan;
+ }
+ } while (true);
+
+ // Save data for current selection before changing
+ if (gCellDataChanged) {
+ // && gDialog.ApplyBeforeMove.checked)
+ if (!ValidateCellData()) {
+ return;
+ }
+
+ gActiveEditor.beginTransaction();
+ // Apply changes to all selected cells
+ ApplyCellAttributes();
+ gActiveEditor.endTransaction();
+
+ SetCloseButton();
+ }
+
+ // Set cell and other data for new selection
+ gCellElement = gCellData.value;
+
+ // Save globals for new current cell
+ gCurRowIndex = gCellData.startRowIndex;
+ gCurColIndex = gCellData.startColIndex;
+ gCurColSpan = gCellData.actualColSpan;
+
+ // Copy for new global cell
+ globalCellElement = gCellElement.cloneNode(false);
+
+ // Change the selection
+ DoCellSelection();
+
+ // Scroll page so new selection is visible
+ // Using SELECTION_ANCHOR_REGION makes the upper-left corner of first selected cell
+ // the point to bring into view.
+ try {
+ var selectionController = gActiveEditor.selectionController;
+ selectionController.scrollSelectionIntoView(
+ selectionController.SELECTION_NORMAL,
+ selectionController.SELECTION_ANCHOR_REGION,
+ true
+ );
+ } catch (e) {}
+
+ // Reinitialize dialog using new cell
+ // if (!gDialog.KeepCurrentData.checked)
+ // Setting this false unchecks all "set attributes" checkboxes
+ gAdvancedEditUsed = false;
+ InitCellPanel();
+ gAdvancedEditUsed = true;
+}
+
+function DoCellSelection() {
+ // Collapse selection into to the focus cell
+ // so editor uses that as start cell
+ gSelection.collapse(gCellElement, 0);
+
+ var tagNameObj = { value: "" };
+ var countObj = { value: 0 };
+ try {
+ switch (gSelectedCellsType) {
+ case SELECT_CELL:
+ gActiveEditor.selectTableCell();
+ break;
+ case SELECT_ROW:
+ gActiveEditor.selectTableRow();
+ break;
+ default:
+ gActiveEditor.selectTableColumn();
+ break;
+ }
+ // Get number of cells selected
+ gActiveEditor.getSelectedOrParentTableElement(tagNameObj, countObj);
+ } catch (e) {}
+
+ if (tagNameObj.value == "td") {
+ gSelectedCellCount = countObj.value;
+ } else {
+ gSelectedCellCount = 0;
+ }
+
+ // Currently, we can only allow advanced editing on ONE cell element at a time
+ // else we ignore CSS, JS, and HTML attributes not already in dialog
+ SetElementEnabled(gDialog.AdvancedEditCell, gSelectedCellCount == 1);
+
+ gDialog.AdvancedEditCell.setAttribute(
+ "tooltiptext",
+ gSelectedCellCount > 1
+ ? GetString("AdvancedEditForCellMsg")
+ : gDialog.AdvancedEditCellToolTipText
+ );
+}
+
+function SetSelectionButtons() {
+ if (gSelectedCellsType == SELECT_ROW) {
+ // Trigger CSS to set images of up and down arrows
+ gDialog.PreviousButton.setAttribute("type", "row");
+ gDialog.NextButton.setAttribute("type", "row");
+ } else {
+ // or images of left and right arrows
+ gDialog.PreviousButton.setAttribute("type", "col");
+ gDialog.NextButton.setAttribute("type", "col");
+ }
+ DisableSelectionButtons(
+ (gSelectedCellsType == SELECT_ROW && gRowCount == 1) ||
+ (gSelectedCellsType == SELECT_COLUMN && gColCount == 1) ||
+ (gRowCount == 1 && gColCount == 1)
+ );
+}
+
+function DisableSelectionButtons(disable) {
+ gDialog.PreviousButton.setAttribute("disabled", disable ? "true" : "false");
+ gDialog.NextButton.setAttribute("disabled", disable ? "true" : "false");
+}
+
+function SwitchToValidatePanel() {
+ if (gDialog.TabBox.selectedTab != gValidateTab) {
+ gDialog.TabBox.selectedTab = gValidateTab;
+ }
+}
+
+function SetAlign(listID, defaultValue, element, attName) {
+ var value = document.getElementById(listID).value;
+ if (value == defaultValue) {
+ try {
+ gActiveEditor.removeAttributeOrEquivalent(element, attName, true);
+ } catch (e) {}
+ } else {
+ try {
+ gActiveEditor.setAttributeOrEquivalent(element, attName, value, true);
+ } catch (e) {}
+ }
+}
+
+function ValidateTableData() {
+ gValidateTab = gDialog.TableTab;
+ gNewRowCount = Number(
+ ValidateNumber(gDialog.TableRowsInput, null, 1, gMaxRows, null, true, true)
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ gNewColCount = Number(
+ ValidateNumber(
+ gDialog.TableColumnsInput,
+ null,
+ 1,
+ gMaxColumns,
+ null,
+ true,
+ true
+ )
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ // If user is deleting any cells, get confirmation
+ // (This is a global to the dialog and we ask only once per dialog session)
+ if (!gCanDelete && (gNewRowCount < gRowCount || gNewColCount < gColCount)) {
+ if (
+ ConfirmWithTitle(
+ GetString("DeleteTableTitle"),
+ GetString("DeleteTableMsg"),
+ GetString("DeleteCells")
+ )
+ ) {
+ gCanDelete = true;
+ } else {
+ SetTextboxFocus(
+ gNewRowCount < gRowCount
+ ? gDialog.TableRowsInput
+ : gDialog.TableColumnsInput
+ );
+ return false;
+ }
+ }
+
+ ValidateNumber(
+ gDialog.TableWidthInput,
+ gDialog.TableWidthUnits,
+ 1,
+ gMaxTableSize,
+ globalTableElement,
+ "width"
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ if (gUseCSS) {
+ ValidateNumber(
+ gDialog.TableHeightInput,
+ gDialog.TableHeightUnits,
+ 1,
+ gMaxTableSize,
+ globalTableElement,
+ "height"
+ );
+ if (gValidationError) {
+ return false;
+ }
+ }
+
+ ValidateNumber(
+ gDialog.BorderWidthInput,
+ null,
+ 0,
+ gMaxPixels,
+ globalTableElement,
+ "border"
+ );
+ // TODO: Deal with "BORDER" without value issue
+ if (gValidationError) {
+ return false;
+ }
+
+ ValidateNumber(
+ gDialog.SpacingInput,
+ null,
+ 0,
+ gMaxPixels,
+ globalTableElement,
+ "cellspacing"
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ ValidateNumber(
+ gDialog.PaddingInput,
+ null,
+ 0,
+ gMaxPixels,
+ globalTableElement,
+ "cellpadding"
+ );
+ if (gValidationError) {
+ return false;
+ }
+
+ SetAlign("TableAlignList", defHAlign, globalTableElement, "align");
+
+ // Color is set on globalCellElement immediately
+ return true;
+}
+
+function ValidateCellData() {
+ gValidateTab = gDialog.CellTab;
+
+ if (gDialog.CellHeightCheckbox.checked) {
+ ValidateNumber(
+ gDialog.CellHeightInput,
+ gDialog.CellHeightUnits,
+ 1,
+ gMaxTableSize,
+ globalCellElement,
+ "height"
+ );
+ if (gValidationError) {
+ return false;
+ }
+ }
+
+ if (gDialog.CellWidthCheckbox.checked) {
+ ValidateNumber(
+ gDialog.CellWidthInput,
+ gDialog.CellWidthUnits,
+ 1,
+ gMaxTableSize,
+ globalCellElement,
+ "width"
+ );
+ if (gValidationError) {
+ return false;
+ }
+ }
+
+ if (gDialog.CellHAlignCheckbox.checked) {
+ var hAlign = gDialog.CellHAlignList.value;
+
+ // Horizontal alignment is complicated by "char" type
+ // We don't change current values if user didn't edit alignment
+ if (!gAlignWasChar) {
+ globalCellElement.removeAttribute(charStr);
+
+ // Always set "align" attribute,
+ // so the default "left" is effective in a cell
+ // when parent row has align set.
+ globalCellElement.setAttribute("align", hAlign);
+ }
+ }
+
+ if (gDialog.CellVAlignCheckbox.checked) {
+ // Always set valign (no default in 2nd param) so
+ // the default "middle" is effective in a cell
+ // when parent row has valign set.
+ SetAlign("CellVAlignList", "", globalCellElement, "valign");
+ }
+
+ if (gDialog.TextWrapCheckbox.checked) {
+ if (gDialog.TextWrapList.value == "nowrap") {
+ try {
+ gActiveEditor.setAttributeOrEquivalent(
+ globalCellElement,
+ "nowrap",
+ "nowrap",
+ true
+ );
+ } catch (e) {}
+ } else {
+ try {
+ gActiveEditor.removeAttributeOrEquivalent(
+ globalCellElement,
+ "nowrap",
+ true
+ );
+ } catch (e) {}
+ }
+ }
+
+ return true;
+}
+
+function ValidateData() {
+ var result;
+
+ // Validate current panel first
+ if (gDialog.TabBox.selectedTab == gDialog.TableTab) {
+ result = ValidateTableData();
+ if (result) {
+ result = ValidateCellData();
+ }
+ } else {
+ result = ValidateCellData();
+ if (result) {
+ result = ValidateTableData();
+ }
+ }
+ if (!result) {
+ return false;
+ }
+
+ // Set global element for AdvancedEdit
+ if (gDialog.TabBox.selectedTab == gDialog.TableTab) {
+ globalElement = globalTableElement;
+ } else {
+ globalElement = globalCellElement;
+ }
+
+ return true;
+}
+
+function ChangeCellTextbox(textboxID) {
+ // Filter input for just integers
+ forceInteger(textboxID);
+
+ if (gDialog.TabBox.selectedTab == gDialog.CellTab) {
+ gCellDataChanged = true;
+ }
+}
+
+// Call this when a textbox or menulist is changed
+// so the checkbox is automatically set
+function SetCheckbox(checkboxID) {
+ if (checkboxID && checkboxID.length > 0) {
+ // Set associated checkbox
+ document.getElementById(checkboxID).checked = true;
+ }
+ gCellDataChanged = true;
+}
+
+function ChangeIntTextbox(textboxID, checkboxID) {
+ // Filter input for just integers
+ forceInteger(textboxID);
+
+ // Set associated checkbox
+ SetCheckbox(checkboxID);
+}
+
+function CloneAttribute(destElement, srcElement, attr) {
+ var value = srcElement.getAttribute(attr);
+ // Use editor methods since we are always
+ // modifying a table in the document and
+ // we need transaction system for undo
+ try {
+ if (!value || value.length == 0) {
+ gActiveEditor.removeAttributeOrEquivalent(destElement, attr, false);
+ } else {
+ gActiveEditor.setAttributeOrEquivalent(destElement, attr, value, false);
+ }
+ } catch (e) {}
+}
+
+/* eslint-disable complexity */
+function ApplyTableAttributes() {
+ var newAlign = gDialog.TableCaptionList.value;
+ if (!newAlign) {
+ newAlign = "";
+ }
+
+ if (gTableCaptionElement) {
+ // Get current alignment
+ var align = GetHTMLOrCSSStyleValue(
+ gTableCaptionElement,
+ "align",
+ "caption-side"
+ ).toLowerCase();
+ // This is the default
+ if (!align) {
+ align = "top";
+ }
+
+ if (newAlign == "") {
+ // Remove existing caption
+ try {
+ gActiveEditor.deleteNode(gTableCaptionElement);
+ } catch (e) {}
+ gTableCaptionElement = null;
+ } else if (newAlign != align) {
+ try {
+ if (newAlign == "top") {
+ // This is default, so don't explicitly set it
+ gActiveEditor.removeAttributeOrEquivalent(
+ gTableCaptionElement,
+ "align",
+ false
+ );
+ } else {
+ gActiveEditor.setAttributeOrEquivalent(
+ gTableCaptionElement,
+ "align",
+ newAlign,
+ false
+ );
+ }
+ } catch (e) {}
+ }
+ } else if (newAlign != "") {
+ // Create and insert a caption:
+ try {
+ gTableCaptionElement = gActiveEditor.createElementWithDefaults("caption");
+ } catch (e) {}
+ if (gTableCaptionElement) {
+ if (newAlign != "top") {
+ gTableCaptionElement.setAttribute("align", newAlign);
+ }
+
+ // Insert it into the table - caption is always inserted as first child
+ try {
+ gActiveEditor.insertNode(gTableCaptionElement, gTableElement, 0);
+ } catch (e) {}
+
+ // Put selection back where it was
+ ChangeSelection(RESET_SELECTION);
+ }
+ }
+
+ var countDelta;
+ var foundCell;
+ var i;
+
+ if (gNewRowCount != gRowCount) {
+ countDelta = gNewRowCount - gRowCount;
+ if (gNewRowCount > gRowCount) {
+ // Append new rows
+ // Find first cell in last row
+ if (GetCellData(gLastRowIndex, 0)) {
+ try {
+ // Move selection to the last cell
+ gSelection.collapse(gCellData.value, 0);
+ // Insert new rows after it
+ gActiveEditor.insertTableRow(countDelta, true);
+ gRowCount = gNewRowCount;
+ gLastRowIndex = gRowCount - 1;
+ // Put selection back where it was
+ ChangeSelection(RESET_SELECTION);
+ } catch (ex) {
+ dump("FAILED TO FIND FIRST CELL IN LAST ROW\n");
+ }
+ }
+ } else if (gCanDelete) {
+ // Delete rows
+ // Find first cell starting in first row we delete
+ var firstDeleteRow = gRowCount + countDelta;
+ foundCell = false;
+ for (i = 0; i <= gLastColIndex; i++) {
+ if (!GetCellData(firstDeleteRow, i)) {
+ // We failed to find a cell.
+ break;
+ }
+
+ if (gCellData.startRowIndex == firstDeleteRow) {
+ foundCell = true;
+ break;
+ }
+ }
+ if (foundCell) {
+ try {
+ // Move selection to the cell we found
+ gSelection.collapse(gCellData.value, 0);
+ gActiveEditor.deleteTableRow(-countDelta);
+ gRowCount = gNewRowCount;
+ gLastRowIndex = gRowCount - 1;
+ if (gCurRowIndex > gLastRowIndex) {
+ // We are deleting our selection
+ // move it to start of table
+ ChangeSelectionToFirstCell();
+ } else {
+ // Put selection back where it was.
+ ChangeSelection(RESET_SELECTION);
+ }
+ } catch (ex) {
+ dump("FAILED TO FIND FIRST CELL IN LAST ROW\n");
+ }
+ }
+ }
+ }
+
+ if (gNewColCount != gColCount) {
+ countDelta = gNewColCount - gColCount;
+
+ if (gNewColCount > gColCount) {
+ // Append new columns
+ // Find last cell in first column
+ if (GetCellData(0, gLastColIndex)) {
+ try {
+ // Move selection to the last cell
+ gSelection.collapse(gCellData.value, 0);
+ gActiveEditor.insertTableColumn(countDelta, true);
+ gColCount = gNewColCount;
+ gLastColIndex = gColCount - 1;
+ // Restore selection
+ ChangeSelection(RESET_SELECTION);
+ } catch (ex) {
+ dump("FAILED TO FIND FIRST CELL IN LAST COLUMN\n");
+ }
+ }
+ } else if (gCanDelete) {
+ // Delete columns
+ var firstDeleteCol = gColCount + countDelta;
+ foundCell = false;
+ for (i = 0; i <= gLastRowIndex; i++) {
+ // Find first cell starting in first column we delete
+ if (!GetCellData(i, firstDeleteCol)) {
+ // We failed to find a cell.
+ break;
+ }
+
+ if (gCellData.startColIndex == firstDeleteCol) {
+ foundCell = true;
+ break;
+ }
+ }
+ if (foundCell) {
+ try {
+ // Move selection to the cell we found
+ gSelection.collapse(gCellData.value, 0);
+ gActiveEditor.deleteTableColumn(-countDelta);
+ gColCount = gNewColCount;
+ gLastColIndex = gColCount - 1;
+ if (gCurColIndex > gLastColIndex) {
+ ChangeSelectionToFirstCell();
+ } else {
+ ChangeSelection(RESET_SELECTION);
+ }
+ } catch (ex) {
+ dump("FAILED TO FIND FIRST CELL IN LAST ROW\n");
+ }
+ }
+ }
+ }
+
+ // Clone all remaining attributes to pick up
+ // anything changed by Advanced Edit Dialog
+ try {
+ gActiveEditor.cloneAttributes(gTableElement, globalTableElement);
+ } catch (e) {}
+}
+/* eslint-enable complexity */
+
+function ApplyCellAttributes() {
+ var rangeObj = { value: null };
+ var selectedCell;
+ try {
+ selectedCell = gActiveEditor.getFirstSelectedCell(rangeObj);
+ } catch (e) {}
+
+ if (!selectedCell) {
+ return;
+ }
+
+ if (gSelectedCellCount == 1) {
+ // When only one cell is selected, simply clone entire element,
+ // thus CSS and JS from Advanced edit is copied
+ try {
+ gActiveEditor.cloneAttributes(selectedCell, globalCellElement);
+ } catch (e) {}
+
+ if (gDialog.CellStyleCheckbox.checked) {
+ var currentStyleIndex =
+ selectedCell.nodeName.toLowerCase() == "th" ? 1 : 0;
+ if (gDialog.CellStyleList.selectedIndex != currentStyleIndex) {
+ // Switch cell types
+ // (replaces with new cell and copies attributes and contents)
+ try {
+ selectedCell = gActiveEditor.switchTableCellHeaderType(selectedCell);
+ } catch (e) {}
+ }
+ }
+ } else {
+ // Apply changes to all selected cells
+ // XXX THIS DOESN'T COPY ADVANCED EDIT CHANGES!
+ try {
+ while (selectedCell) {
+ ApplyAttributesToOneCell(selectedCell);
+ selectedCell = gActiveEditor.getNextSelectedCell(rangeObj);
+ }
+ } catch (e) {}
+ }
+ gCellDataChanged = false;
+}
+
+function ApplyAttributesToOneCell(destElement) {
+ if (gDialog.CellHeightCheckbox.checked) {
+ CloneAttribute(destElement, globalCellElement, "height");
+ }
+
+ if (gDialog.CellWidthCheckbox.checked) {
+ CloneAttribute(destElement, globalCellElement, "width");
+ }
+
+ if (gDialog.CellHAlignCheckbox.checked) {
+ CloneAttribute(destElement, globalCellElement, "align");
+ CloneAttribute(destElement, globalCellElement, charStr);
+ }
+
+ if (gDialog.CellVAlignCheckbox.checked) {
+ CloneAttribute(destElement, globalCellElement, "valign");
+ }
+
+ if (gDialog.TextWrapCheckbox.checked) {
+ CloneAttribute(destElement, globalCellElement, "nowrap");
+ }
+
+ if (gDialog.CellStyleCheckbox.checked) {
+ var newStyleIndex = gDialog.CellStyleList.selectedIndex;
+ var currentStyleIndex = destElement.nodeName.toLowerCase() == "th" ? 1 : 0;
+
+ if (newStyleIndex != currentStyleIndex) {
+ // Switch cell types
+ // (replaces with new cell and copies attributes and contents)
+ try {
+ destElement = gActiveEditor.switchTableCellHeaderType(destElement);
+ } catch (e) {}
+ }
+ }
+
+ if (gDialog.CellColorCheckbox.checked) {
+ CloneAttribute(destElement, globalCellElement, "bgcolor");
+ }
+}
+
+function SetCloseButton() {
+ // Change text on "Cancel" button after Apply is used
+ if (!gApplyUsed) {
+ document.documentElement.setAttribute(
+ "buttonlabelcancel",
+ document.documentElement.getAttribute("buttonlabelclose")
+ );
+ gApplyUsed = true;
+ }
+}
+
+function Apply() {
+ if (ValidateData()) {
+ gActiveEditor.beginTransaction();
+
+ ApplyTableAttributes();
+
+ // We may have just a table, so check for cell element
+ if (globalCellElement) {
+ ApplyCellAttributes();
+ }
+
+ gActiveEditor.endTransaction();
+
+ SetCloseButton();
+ return true;
+ }
+ return false;
+}
+
+function onAccept(event) {
+ // Do same as Apply and close window if ValidateData succeeded
+ var retVal = Apply();
+ if (retVal) {
+ SaveWindowLocation();
+ } else {
+ event.preventDefault();
+ }
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdTableProps.xhtml b/comm/suite/editor/components/dialogs/content/EdTableProps.xhtml
new file mode 100644
index 0000000000..30979acb4d
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdTableProps.xhtml
@@ -0,0 +1,287 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edTableProperties SYSTEM "chrome://editor/locale/EditorTableProperties.dtd">
+%edTableProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&tableWindow.title;"
+ id="tableDlg"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()"
+ buttons="accept,extra1,cancel"
+ buttonlabelclose="&closeButton.label;"
+ buttonlabelextra1="&applyButton.label;"
+ buttonaccesskeyextra1="&applyButton.accesskey;">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdTableProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <tabbox id="TabBox">
+ <tabs flex="1">
+ <tab id="TableTab" label="&tableTab.label;"/>
+ <tab id="CellTab" label="&cellTab.label;"/>
+ </tabs>
+ <tabpanels>
+
+ <!-- TABLE PANEL -->
+ <vbox>
+ <groupbox orient="horizontal">
+ <hbox class="groupbox-title">
+ <label class="header">&size.label;</label>
+ </hbox>
+ <grid>
+ <columns><column/><column/><column/><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label value="&tableRows.label;" accesskey="&tableRows.accessKey;" control="TableRowsInput"/>
+ <textbox class="narrow" id="TableRowsInput" oninput="forceInteger(this.id);"/>
+ <spring class="bigspacer"/>
+ <label value="&tableHeight.label;" accesskey="&tableHeight.accessKey;"
+ id="TableHeightLabel" control="TableHeightInput"/>
+ <textbox class="narrow" id="TableHeightInput" oninput="forceInteger(this.id);"/>
+ <menulist id="TableHeightUnits"/>
+ </row>
+ <row align="center">
+ <label value="&tableColumns.label;" accesskey="&tableColumns.accessKey;" control="TableColumnsInput"/>
+ <textbox class="narrow" id="TableColumnsInput" oninput="forceInteger(this.id);"/>
+ <spring class="bigspacer"/>
+ <label value="&tableWidth.label;" accesskey="&tableWidth.accessKey;" control="TableWidthInput"/>
+ <textbox class="narrow" id="TableWidthInput" oninput="forceInteger(this.id);"/>
+ <menulist id="TableWidthUnits"/>
+ </row>
+ </rows>
+ <!-- KEEP GRID LAYOUT here since we will be adding back support for table HEIGHT via CSS -->
+ </grid>
+ </groupbox>
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&tableBorderSpacing.label;</label>
+ </hbox>
+ <grid>
+ <columns><column/><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label control="BorderWidthInput"
+ value="&tableBorderWidth.label;"
+ accesskey="&tableBorderWidth.accessKey;"/>
+ <textbox class="narrow" id="BorderWidthInput" oninput="forceInteger(this.id);"/>
+ <label align="left" value="&pixels.label;"/>
+ </row>
+ <row align="center">
+ <label control="SpacingInput"
+ value="&tableSpacing.label;"
+ accesskey="&tableSpacing.accessKey;"/>
+ <textbox class="narrow" id="SpacingInput" oninput="forceInteger(this.id);"/>
+ <label value="&tablePxBetwCells.label;"/>
+ </row>
+ <row align="center">
+ <label control="PaddingInput"
+ value="&tablePadding.label;"
+ accesskey="&tablePadding.accessKey;"/>
+ <textbox class="narrow" id="PaddingInput" oninput="forceInteger(this.id);"/>
+ <label value="&tablePxBetwBrdrCellContent.label;"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+ <!-- Table Alignment and Caption -->
+ <hbox flex="1" align="center">
+ <label control="TableAlignList"
+ value="&tableAlignment.label;"
+ accesskey="&tableAlignment.accessKey;"/>
+ <menulist id="TableAlignList">
+ <menupopup>
+ <menuitem label="&AlignLeft.label;" value="left"/>
+ <menuitem label="&AlignCenter.label;" value="center"/>
+ <menuitem label="&AlignRight.label;" value="right"/>
+ </menupopup>
+ </menulist>
+ <spacer class="spacer"/>
+ <label control="TableCaptionList"
+ value="&tableCaption.label;"
+ accesskey="&tableCaption.accessKey;"/>
+ <menulist id="TableCaptionList">
+ <menupopup>
+ <menuitem label="&tableCaptionNone.label;" value=""/>
+ <menuitem label="&tableCaptionAbove.label;" value="top"/>
+ <menuitem label="&tableCaptionBelow.label;" value="bottom"/>
+ <menuitem label="&tableCaptionLeft.label;" value="left"/>
+ <menuitem label="&tableCaptionRight.label;" value="right"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ <separator class="groove"/>
+ <hbox align="center">
+ <label value="&backgroundColor.label;"/>
+ <button id="tableBackground" class="color-button" oncommand="GetColorAndUpdate('tableBackgroundCW');">
+ <spacer id="tableBackgroundCW" class="color-well"/>
+ </button>
+ <spacer class="spacer"/>
+ <label id="TableInheritColor" value="&tableInheritColor.label;" collapsed="true"/>
+ </hbox>
+ <separator class="groove"/>
+ <hbox flex="1" align="center">
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton"
+ oncommand="onAdvancedEdit();"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <spacer flex="1"/>
+ </vbox><!-- Table Panel -->
+
+ <!-- CELL PANEL -->
+ <vbox>
+ <groupbox orient="horizontal" align="center">
+ <hbox class="groupbox-title">
+ <label class="header">&cellSelection.label;</label>
+ </hbox>
+ <vbox>
+ <menulist id="SelectionList" oncommand="ChangeSelection(event.target.value)" flex="1">
+ <menupopup>
+ <!-- JS code assumes order is Cell, Row, Column -->
+ <menuitem label="&cellSelectCell.label;" value="1"/>
+ <menuitem label="&cellSelectRow.label;" value="2"/>
+ <menuitem label="&cellSelectColumn.label;" value="3"/>
+ </menupopup>
+ </menulist>
+ <hbox flex="1">
+ <button id="PreviousButton"
+ oncommand="MoveSelection(0)"
+ flex="1"
+ align="center">
+ <image/>
+ <label value="&cellSelectPrevious.label;"
+ accesskey="&cellSelectPrevious.accessKey;"
+ control="PreviousButton"/>
+ </button>
+ <button id="NextButton"
+ oncommand="MoveSelection(1)"
+ class="align-right"
+ flex="1"
+ align="center">
+ <image/>
+ <label value="&cellSelectNext.label;"
+ accesskey="&cellSelectNext.accessKey;"
+ control="NextButton"/>
+ </button>
+ </hbox>
+ </vbox>
+ <spacer class="bigspacer"/>
+ <description class="wrap" flex="1">&applyBeforeChange.label;</description>
+ </groupbox>
+ <hbox align="center">
+ <!-- cell size groupbox -->
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&size.label;</label>
+ </hbox>
+ <grid>
+ <columns><column/><column/><column flex="1"/></columns>
+ <rows>
+ <row align="center">
+ <checkbox id="CellHeightCheckbox" label="&tableHeight.label;" accesskey="&tableHeight.accessKey;"/>
+ <textbox class="narrow" id="CellHeightInput"
+ oninput="ChangeIntTextbox(this.id, 'CellHeightCheckbox');"/>
+ <menulist id="CellHeightUnits" oncommand="SetCheckbox('CellHeightCheckbox');"/>
+ </row>
+ <row align="center">
+ <checkbox id="CellWidthCheckbox" label="&tableWidth.label;" accesskey="&tableWidth.accessKey;"/>
+ <textbox class="narrow" id="CellWidthInput"
+ oninput="ChangeIntTextbox(this.id, 'CellWidthCheckbox');"/>
+ <menulist id="CellWidthUnits" oncommand="SetCheckbox('CellWidthCheckbox');"/>
+ </row>
+ </rows>
+ </grid>
+ <spacer class="bigspacer"/>
+ </groupbox>
+ <!-- Alignment -->
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&cellContentAlignment.label;</label>
+ </hbox>
+ <grid>
+ <columns><column/><column flex="1"/><column/></columns>
+ <rows>
+ <row align="center">
+ <checkbox id="CellVAlignCheckbox" label="&cellVertical.label;" accesskey="&cellVertical.accessKey;"/>
+ <menulist id="CellVAlignList" oncommand="SetCheckbox('CellVAlignCheckbox');">
+ <menupopup>
+ <menuitem label="&cellAlignTop.label;" value="top"/>
+ <menuitem label="&cellAlignMiddle.label;" value="middle"/>
+ <menuitem label="&cellAlignBottom.label;" value="bottom"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row align="center">
+ <checkbox id="CellHAlignCheckbox" label="&cellHorizontal.label;" accesskey="&cellHorizontal.accessKey;"/>
+ <menulist id="CellHAlignList" oncommand="SelectCellHAlign()">
+ <menupopup>
+ <menuitem label="&AlignLeft.label;" value="left"/>
+ <menuitem label="&AlignCenter.label;" value="center"/>
+ <menuitem label="&AlignRight.label;" value="right"/>
+ <menuitem label="&cellAlignJustify.label;" value="justify"/>
+ </menupopup>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+ </hbox>
+ <spacer class="spacer"/>
+ <hbox align="center">
+ <checkbox id="CellStyleCheckbox" label="&cellStyle.label;" accesskey="&cellStyle.accessKey;"/>
+ <menulist id="CellStyleList" oncommand="SetCheckbox('CellStyleCheckbox');">
+ <menupopup>
+ <menuitem label="&cellNormal.label;" value="td"/>
+ <menuitem label="&cellHeader.label;" value="th"/>
+ </menupopup>
+ </menulist>
+ <spacer class="bigspacer"/>
+ <checkbox id="TextWrapCheckbox" label="&cellTextWrap.label;" accesskey="&cellTextWrap.accessKey;"/>
+ <menulist id="TextWrapList" oncommand="SetCheckbox('TextWrapCheckbox');">
+ <menupopup>
+ <menuitem label="&cellWrap.label;" value="wrap"/>
+ <menuitem label="&cellNoWrap.label;" value="nowrap"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ <separator class="groove"/>
+ <hbox align="center">
+ <checkbox id="CellColorCheckbox" label="&backgroundColor.label;" accesskey="&backgroundColor.accessKey;"/>
+ <button class="color-button" oncommand="GetColorAndUpdate('cellBackgroundCW');">
+ <spacer id="cellBackgroundCW" class="color-well"/>
+ </button>
+ <spacer class="spacer"/>
+ <label id="CellInheritColor" value="&cellInheritColor.label;" collapsed="true"/>
+ </hbox>
+ <separator class="groove"/>
+ <hbox align="center">
+ <description class="wrap" flex="1" style="width: 1em">&cellUseCheckboxHelp.label;</description>
+ <button id="AdvancedEditButton2"
+ oncommand="onAdvancedEdit()"
+ label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;"
+ tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <spacer flex="1"/>
+ </vbox><!-- Cell Panel -->
+ </tabpanels>
+ </tabbox>
+ <spacer class="spacer"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EdTextAreaProps.js b/comm/suite/editor/components/dialogs/content/EdTextAreaProps.js
new file mode 100644
index 0000000000..da33ab60c9
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdTextAreaProps.js
@@ -0,0 +1,171 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../composer/content/editorUtilities.js */
+/* import-globals-from EdDialogCommon.js */
+
+var insertNew;
+var textareaElement;
+
+// dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup() {
+ var editor = GetCurrentEditor();
+ if (!editor) {
+ dump("Failed to get active editor!\n");
+ window.close();
+ return;
+ }
+
+ gDialog = {
+ accept: document.documentElement.getButton("accept"),
+ textareaName: document.getElementById("TextAreaName"),
+ textareaRows: document.getElementById("TextAreaRows"),
+ textareaCols: document.getElementById("TextAreaCols"),
+ textareaWrap: document.getElementById("TextAreaWrap"),
+ textareaReadOnly: document.getElementById("TextAreaReadOnly"),
+ textareaDisabled: document.getElementById("TextAreaDisabled"),
+ textareaTabIndex: document.getElementById("TextAreaTabIndex"),
+ textareaAccessKey: document.getElementById("TextAreaAccessKey"),
+ textareaValue: document.getElementById("TextAreaValue"),
+ MoreSection: document.getElementById("MoreSection"),
+ MoreFewerButton: document.getElementById("MoreFewerButton"),
+ };
+
+ // Get a single selected text area element
+ const kTagName = "textarea";
+ try {
+ textareaElement = editor.getSelectedElement(kTagName);
+ } catch (e) {}
+
+ if (textareaElement) {
+ // We found an element and don't need to insert one
+ insertNew = false;
+
+ gDialog.textareaValue.value = textareaElement.value;
+ } else {
+ insertNew = true;
+
+ // We don't have an element selected,
+ // so create one with default attributes
+ try {
+ textareaElement = editor.createElementWithDefaults(kTagName);
+ } catch (e) {}
+
+ if (!textareaElement) {
+ dump("Failed to get selected element or create a new one!\n");
+ window.close();
+ return;
+ }
+ gDialog.textareaValue.value = GetSelectionAsText();
+ }
+
+ // Make a copy to use for AdvancedEdit
+ globalElement = textareaElement.cloneNode(false);
+
+ InitDialog();
+
+ InitMoreFewer();
+
+ SetTextboxFocus(gDialog.textareaName);
+
+ SetWindowLocation();
+}
+
+function InitDialog() {
+ gDialog.textareaName.value = globalElement.getAttribute("name");
+ gDialog.textareaRows.value = globalElement.getAttribute("rows");
+ gDialog.textareaCols.value = globalElement.getAttribute("cols");
+ gDialog.textareaWrap.value = GetHTMLOrCSSStyleValue(
+ globalElement,
+ "wrap",
+ "white-space"
+ );
+ gDialog.textareaReadOnly.checked = globalElement.hasAttribute("readonly");
+ gDialog.textareaDisabled.checked = globalElement.hasAttribute("disabled");
+ gDialog.textareaTabIndex.value = globalElement.getAttribute("tabindex");
+ gDialog.textareaAccessKey.value = globalElement.getAttribute("accesskey");
+ onInput();
+}
+
+function onInput() {
+ var disabled =
+ !gDialog.textareaName.value ||
+ !gDialog.textareaRows.value ||
+ !gDialog.textareaCols.value;
+ if (gDialog.accept.disabled != disabled) {
+ gDialog.accept.disabled = disabled;
+ }
+}
+
+function ValidateData() {
+ var attributes = {
+ name: gDialog.textareaName.value,
+ rows: gDialog.textareaRows.value,
+ cols: gDialog.textareaCols.value,
+ wrap: gDialog.textareaWrap.value,
+ tabindex: gDialog.textareaTabIndex.value,
+ accesskey: gDialog.textareaAccessKey.value,
+ };
+ var flags = {
+ readonly: gDialog.textareaReadOnly.checked,
+ disabled: gDialog.textareaDisabled.checked,
+ };
+ for (var a in attributes) {
+ if (attributes[a]) {
+ globalElement.setAttribute(a, attributes[a]);
+ } else {
+ globalElement.removeAttribute(a);
+ }
+ }
+ for (var f in flags) {
+ if (flags[f]) {
+ globalElement.setAttribute(f, "");
+ } else {
+ globalElement.removeAttribute(f);
+ }
+ }
+ return true;
+}
+
+function onAccept() {
+ // All values are valid - copy to actual element in doc or
+ // element created to insert
+ ValidateData();
+
+ var editor = GetCurrentEditor();
+
+ editor.beginTransaction();
+
+ try {
+ editor.cloneAttributes(textareaElement, globalElement);
+
+ if (insertNew) {
+ editor.insertElementAtSelection(textareaElement, true);
+ }
+
+ // undoably set value
+ var initialText = gDialog.textareaValue.value;
+ if (initialText != textareaElement.value) {
+ editor.setShouldTxnSetSelection(false);
+
+ while (textareaElement.hasChildNodes()) {
+ editor.deleteNode(textareaElement.lastChild);
+ }
+ if (initialText) {
+ var textNode = editor.document.createTextNode(initialText);
+ editor.insertNode(textNode, textareaElement, 0);
+ }
+
+ editor.setShouldTxnSetSelection(true);
+ }
+ } finally {
+ editor.endTransaction();
+ }
+
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EdTextAreaProps.xhtml b/comm/suite/editor/components/dialogs/content/EdTextAreaProps.xhtml
new file mode 100644
index 0000000000..9ab91664e8
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EdTextAreaProps.xhtml
@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % edTextAreaProperties SYSTEM "chrome://editor/locale/EditorTextAreaProperties.dtd">
+%edTextAreaProperties;
+<!ENTITY % edDialogOverlay SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd">
+%edDialogOverlay;
+]>
+
+<dialog title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup();"
+ buttons="accept,cancel">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EdTextAreaProps.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&Settings.label;</label>
+ </hbox>
+ <grid><columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label control="TextAreaName" value="&TextAreaName.label;" accesskey="&TextAreaName.accessKey;"/>
+ <textbox id="TextAreaName" oninput="onInput();"/>
+ </row>
+ <row align="center">
+ <label control="TextAreaRows" value="&TextAreaRows.label;" accesskey="&TextAreaRows.accessKey;"/>
+ <hbox>
+ <textbox id="TextAreaRows" class="narrow" oninput="forceInteger(this.id); onInput();"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="TextAreaCols" value="&TextAreaCols.label;" accesskey="&TextAreaCols.accessKey;"/>
+ <hbox>
+ <textbox id="TextAreaCols" class="narrow" oninput="forceInteger(this.id); onInput();"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ <hbox>
+ <button id="MoreFewerButton" oncommand="onMoreFewer();" persist="more"/>
+ </hbox>
+ <grid id="MoreSection"><columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label control="TextAreaWrap" value="&TextAreaWrap.label;" accesskey="&TextAreaWrap.accessKey;"/>
+ <menulist id="TextAreaWrap">
+ <menupopup>
+ <menuitem label="&WrapDefault.value;"/>
+ <menuitem label="&WrapOff.value;" value="off"/>
+ <menuseparator/>
+ <menuitem label="&WrapSoft.value;" value="soft"/>
+ <menuitem label="&WrapHard.value;" value="hard"/>
+ <menuseparator/>
+ <menuitem label="&WrapPhysical.value;" value="physical"/>
+ <menuitem label="&WrapVirtual.value;" value="virtual"/>
+ <menuseparator/>
+ <menuitem label="normal" value="normal"/>
+ <menuitem label="nowrap" value="nowrap"/>
+ <menuitem label="pre" value="pre"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row>
+ <spacer/>
+ <checkbox id="TextAreaReadOnly" label="&TextAreaReadOnly.label;" accesskey="&TextAreaReadOnly.accessKey;"/>
+ </row>
+ <row>
+ <spacer/>
+ <checkbox id="TextAreaDisabled" label="&TextAreaDisabled.label;" accesskey="&TextAreaDisabled.accessKey;"/>
+ </row>
+ <row align="center">
+ <label control="TextAreaTabIndex" value="&TextAreaTabIndex.label;" accesskey="&TextAreaTabIndex.accessKey;"/>
+ <hbox>
+ <textbox id="TextAreaTabIndex" class="narrow" oninput="forceInteger(this.id);"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="TextAreaAccessKey" value="&TextAreaAccessKey.label;" accesskey="&TextAreaAccessKey.accessKey;"/>
+ <hbox>
+ <textbox id="TextAreaAccessKey" class="narrow" maxlength="1"/>
+ </hbox>
+ </row>
+ <row>
+ <label control="TextAreaValue" value="&InitialText.label;" accesskey="&InitialText.accessKey;"/>
+ </row>
+ <html:textarea id="TextAreaValue" flex="1" rows="5"/>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <vbox id="AdvancedEdit">
+ <hbox flex="1" style="margin-top: 0.2em" align="center">
+ <!-- This will right-align the button -->
+ <spacer flex="1"/>
+ <button id="AdvancedEditButton1" oncommand="onAdvancedEdit()" label="&AdvancedEditButton.label;"
+ accesskey="&AdvancedEditButton.accessKey;" tooltiptext="&AdvancedEditButton.tooltip;"/>
+ </hbox>
+ <separator id="advancedSeparator" class="groove"/>
+ </vbox>
+
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EditConflict.js b/comm/suite/editor/components/dialogs/content/EditConflict.js
new file mode 100644
index 0000000000..28611796cd
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditConflict.js
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// dialog initialization code
+
+document.addEventListener("dialogcancel", onClose);
+
+function Startup()
+{
+ if (!GetCurrentEditor())
+ {
+ window.close();
+ return;
+ }
+
+ SetWindowLocation();
+}
+
+function KeepCurrentPage()
+{
+ // Simply close dialog and don't change current page
+ //TODO: Should we force saving of the current page?
+ SaveWindowLocation();
+ return true;
+}
+
+function UseOtherPage()
+{
+ // Reload the URL -- that will get other editor's contents
+ window.opener.setTimeout(window.opener.EditorLoadUrl, 0, GetDocumentUrl());
+ SaveWindowLocation();
+ return true;
+}
+
+function PreventCancel()
+{
+ SaveWindowLocation();
+
+ // Don't let Esc key close the dialog!
+ return false;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EditConflict.xhtml b/comm/suite/editor/components/dialogs/content/EditConflict.xhtml
new file mode 100644
index 0000000000..fce32792d2
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditConflict.xhtml
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditConflict.dtd">
+
+<dialog buttons="cancel" title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EditConflict.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <label value ="&conflictWarning.label;"/>
+ <spacer class="bigspacer"/>
+ <label value ="&conflictResolve.label;"/>
+ <spacer class="bigspacer"/>
+ <hbox flex="1">
+ <spacer class="bigspacer"/>
+ <button label="&keepCurrentPageButton.label;"
+ oncommand="KeepCurrentPage()"/>
+ <spacer class="bigspacer"/>
+ </hbox>
+ <hbox flex="1">
+ <spacer class="bigspacer"/>
+ <button dlgtype="cancel"
+ label="&useOtherPageButton.label;"
+ oncommand="UseOtherPage()"/>
+ <spacer class="bigspacer"/>
+ </hbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EditorPublish.js b/comm/suite/editor/components/dialogs/content/EditorPublish.js
new file mode 100644
index 0000000000..6947a439ee
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorPublish.js
@@ -0,0 +1,558 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gPublishSiteData;
+var gReturnData;
+var gDefaultSiteIndex = -1;
+var gDefaultSiteName;
+var gPreviousDefaultDir;
+var gPreviousTitle;
+var gSettingsChanged = false;
+var gInitialSiteName;
+var gInitialSiteIndex = -1;
+var gPasswordManagerOn = true;
+
+// Dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup()
+{
+ window.opener.ok = false;
+
+ // Element to edit is passed in
+ gInitialSiteName = window.arguments[1];
+ gReturnData = window.arguments[2];
+ if (!gReturnData || !GetCurrentEditor())
+ {
+ dump("Publish: No editor or return data object not supplied\n");
+ window.close();
+ return;
+ }
+
+ gDialog.TabBox = document.getElementById("TabBox");
+ gDialog.PublishTab = document.getElementById("PublishTab");
+ gDialog.SettingsTab = document.getElementById("SettingsTab");
+
+ // Publish panel
+ gDialog.PageTitleInput = document.getElementById("PageTitleInput");
+ gDialog.FilenameInput = document.getElementById("FilenameInput");
+ gDialog.SiteList = document.getElementById("SiteList");
+ gDialog.DocDirList = document.getElementById("DocDirList");
+ gDialog.OtherDirCheckbox = document.getElementById("OtherDirCheckbox");
+ gDialog.OtherDirRadiogroup = document.getElementById("OtherDirRadiogroup");
+ gDialog.SameLocationRadio = document.getElementById("SameLocationRadio");
+ gDialog.UseSubdirRadio = document.getElementById("UseSubdirRadio");
+ gDialog.OtherDirList = document.getElementById("OtherDirList");
+
+ // Settings Panel
+ gDialog.SiteNameInput = document.getElementById("SiteNameInput");
+ gDialog.PublishUrlInput = document.getElementById("PublishUrlInput");
+ gDialog.BrowseUrlInput = document.getElementById("BrowseUrlInput");
+ gDialog.UsernameInput = document.getElementById("UsernameInput");
+ gDialog.PasswordInput = document.getElementById("PasswordInput");
+ gDialog.SavePassword = document.getElementById("SavePassword");
+
+ gPasswordManagerOn = Services.prefs.getBoolPref("signon.rememberSignons");
+ gDialog.SavePassword.disabled = !gPasswordManagerOn;
+
+ gPublishSiteData = GetPublishSiteData();
+ gDefaultSiteName = GetDefaultPublishSiteName();
+
+ var addNewSite = false;
+ if (gPublishSiteData)
+ {
+ FillSiteList();
+ }
+ else
+ {
+ // No current site data, start a new item in the Settings panel
+ AddNewSite();
+ addNewSite = true;
+ }
+
+ var docUrl = GetDocumentUrl();
+ var scheme = GetScheme(docUrl);
+ var filename = "";
+
+ if (scheme)
+ {
+ filename = GetFilename(docUrl);
+
+ if (scheme != "file")
+ {
+ var siteFound = false;
+
+ // Editing a remote URL.
+ // Attempt to find doc URL in Site Data
+ if (gPublishSiteData)
+ {
+ var dirObj = {};
+ var siteIndex = FindSiteIndexAndDocDir(gPublishSiteData, docUrl, dirObj);
+
+ // Select this site only if the same as user's intended site, or there wasn't one
+ if (siteIndex != -1 && (gInitialSiteIndex == -1 || siteIndex == gInitialSiteIndex))
+ {
+ siteFound = true;
+
+ // Select the site we found
+ gDialog.SiteList.selectedIndex = siteIndex;
+ var docDir = dirObj.value;
+
+ // Use the directory within site in the editable menulist
+ gPublishSiteData[siteIndex].docDir = docDir;
+
+ //XXX HOW DO WE DECIDE WHAT "OTHER" DIR TO USE?
+ //gPublishSiteData[siteIndex].otherDir = docDir;
+ }
+ }
+ if (!siteFound)
+ {
+ // Not found in site database
+ // Setup for a new site and use data from a remote URL
+ if (!addNewSite)
+ AddNewSite();
+
+ addNewSite = true;
+
+ var publishData = CreatePublishDataFromUrl(docUrl);
+ if (publishData)
+ {
+ filename = publishData.filename;
+ gDialog.SiteNameInput.value = publishData.siteName;
+ gDialog.PublishUrlInput.value = publishData.publishUrl;
+ gDialog.BrowseUrlInput.value = publishData.browseUrl;
+ gDialog.UsernameInput.value = publishData.username;
+ gDialog.PasswordInput.value = publishData.password;
+ gDialog.SavePassword.checked = false;
+ }
+ }
+ }
+ }
+ try {
+ gPreviousTitle = GetDocumentTitle();
+ } catch (e) {}
+
+ gDialog.PageTitleInput.value = gPreviousTitle;
+ gDialog.FilenameInput.value = decodeURIComponent(filename);
+
+ if (!addNewSite)
+ {
+ // If not adding a site and we haven't selected a site -- use initial or default site
+ if (gDialog.SiteList.selectedIndex == -1)
+ gDialog.SiteList.selectedIndex = (gInitialSiteIndex != -1) ? gInitialSiteIndex : gDefaultSiteIndex;
+
+ // Fill in all the site data for currently-selected site
+ SelectSiteList();
+ SetTextboxFocus(gDialog.PageTitleInput);
+ }
+
+ if (gDialog.SiteList.selectedIndex == -1)
+ {
+ // No selected site -- assume same directory
+ gDialog.OtherDirRadiogroup.selectedItem = gDialog.SameLocationRadio;
+ }
+ else if (gPublishSiteData[gDialog.SiteList.selectedIndex].docDir ==
+ gPublishSiteData[gDialog.SiteList.selectedIndex].otherDir)
+ {
+ // For now, check "same location" if dirs are already set to same directory
+ gDialog.OtherDirRadiogroup.selectedItem = gDialog.SameLocationRadio;
+ }
+ else
+ {
+ gDialog.OtherDirRadiogroup.selectedItem = gDialog.UseSubdirRadio;
+ }
+
+ doEnabling();
+
+ SetWindowLocation();
+}
+
+function FillSiteList()
+{
+ gDialog.SiteList.removeAllItems();
+ gDefaultSiteIndex = -1;
+
+ // Fill the site lists
+ var count = gPublishSiteData.length;
+ var i;
+
+ for (i = 0; i < count; i++)
+ {
+ var name = gPublishSiteData[i].siteName;
+ var menuitem = gDialog.SiteList.appendItem(name);
+ // Highlight the default site
+ if (name == gDefaultSiteName)
+ {
+ gDefaultSiteIndex = i;
+ if (menuitem)
+ {
+ menuitem.setAttribute("class", "menuitem-highlight-1");
+ menuitem.setAttribute("default", "true");
+ }
+ }
+ // Find initial site location
+ if (name == gInitialSiteName)
+ gInitialSiteIndex = i;
+ }
+}
+
+function doEnabling()
+{
+ var disableOther = !gDialog.OtherDirCheckbox.checked;
+ gDialog.SameLocationRadio.disabled = disableOther;
+ gDialog.UseSubdirRadio.disabled = disableOther;
+ gDialog.OtherDirList.disabled = (disableOther || gDialog.SameLocationRadio.selected);
+}
+
+function SelectSiteList()
+{
+ var selectedSiteIndex = gDialog.SiteList.selectedIndex;
+
+ var siteName = "";
+ var publishUrl = "";
+ var browseUrl = "";
+ var username = "";
+ var password = "";
+ var savePassword = false;
+ var publishOtherFiles = true;
+
+ gDialog.DocDirList.removeAllItems();
+ gDialog.OtherDirList.removeAllItems();
+
+ if (gPublishSiteData && selectedSiteIndex != -1)
+ {
+ siteName = gPublishSiteData[selectedSiteIndex].siteName;
+ publishUrl = gPublishSiteData[selectedSiteIndex].publishUrl;
+ browseUrl = gPublishSiteData[selectedSiteIndex].browseUrl;
+ username = gPublishSiteData[selectedSiteIndex].username;
+ savePassword = gPasswordManagerOn ? gPublishSiteData[selectedSiteIndex].savePassword : false;
+ if (savePassword)
+ password = gPublishSiteData[selectedSiteIndex].password;
+
+ // Fill the directory menulists
+ if (gPublishSiteData[selectedSiteIndex].dirList.length)
+ {
+ for (var i = 0; i < gPublishSiteData[selectedSiteIndex].dirList.length; i++)
+ {
+ gDialog.DocDirList.appendItem(gPublishSiteData[selectedSiteIndex].dirList[i]);
+ gDialog.OtherDirList.appendItem(gPublishSiteData[selectedSiteIndex].dirList[i]);
+ }
+ }
+ gDialog.DocDirList.value = FormatDirForPublishing(gPublishSiteData[selectedSiteIndex].docDir);
+ gDialog.OtherDirList.value = FormatDirForPublishing(gPublishSiteData[selectedSiteIndex].otherDir);
+ publishOtherFiles = gPublishSiteData[selectedSiteIndex].publishOtherFiles;
+
+ }
+ else
+ {
+ gDialog.DocDirList.value = "";
+ gDialog.OtherDirList.value = "";
+ }
+
+ gDialog.SiteNameInput.value = siteName;
+ gDialog.PublishUrlInput.value = publishUrl;
+ gDialog.BrowseUrlInput.value = browseUrl;
+ gDialog.UsernameInput.value = username;
+ gDialog.PasswordInput.value = password;
+ gDialog.SavePassword.checked = savePassword;
+ gDialog.OtherDirCheckbox.checked = publishOtherFiles;
+
+ doEnabling();
+}
+
+function AddNewSite()
+{
+ // Button in Publish panel allows user
+ // to automatically switch to "Settings" panel
+ // to enter data for new site
+ SwitchPanel(gDialog.SettingsTab);
+
+ gDialog.SiteList.selectedIndex = -1;
+
+ SelectSiteList();
+
+ gSettingsChanged = true;
+
+ SetTextboxFocus(gDialog.SiteNameInput);
+}
+
+function SelectPublishTab()
+{
+ if (gSettingsChanged && !ValidateSettings())
+ return;
+
+ SwitchPanel(gDialog.PublishTab);
+ SetTextboxFocus(gDialog.PageTitleInput);
+}
+
+function SelectSettingsTab()
+{
+ SwitchPanel(gDialog.SettingsTab);
+ SetTextboxFocus(gDialog.SiteNameInput);
+}
+
+function SwitchPanel(tab)
+{
+ if (gDialog.TabBox.selectedTab != tab)
+ gDialog.TabBox.selectedTab = tab;
+}
+
+function onInputSettings()
+{
+ // TODO: Save current data during SelectSite and compare here
+ // to detect if real change has occurred?
+ gSettingsChanged = true;
+}
+
+function GetPublishUrlInput()
+{
+ gDialog.PublishUrlInput.value = FormatUrlForPublishing(gDialog.PublishUrlInput.value);
+ return gDialog.PublishUrlInput.value;
+}
+
+function GetBrowseUrlInput()
+{
+ gDialog.BrowseUrlInput.value = FormatUrlForPublishing(gDialog.BrowseUrlInput.value);
+ return gDialog.BrowseUrlInput.value;
+}
+
+function GetDocDirInput()
+{
+ gDialog.DocDirList.value = FormatDirForPublishing(gDialog.DocDirList.value);
+ return gDialog.DocDirList.value;
+}
+
+function GetOtherDirInput()
+{
+ gDialog.OtherDirList.value = FormatDirForPublishing(gDialog.OtherDirList.value);
+ return gDialog.OtherDirList.value;
+}
+
+function ChooseDir(menulist)
+{
+ //TODO: For FTP publish destinations, get file listing of just dirs
+ // and build a tree to let user select dir
+}
+
+function ValidateSettings()
+{
+ var siteName = TrimString(gDialog.SiteNameInput.value);
+ if (!siteName)
+ {
+ ShowErrorInPanel(gDialog.SettingsTab, "MissingSiteNameError", gDialog.SiteNameInput);
+ return false;
+ }
+ if (PublishSiteNameExists(siteName, gPublishSiteData, gDialog.SiteList.selectedIndex))
+ {
+ SwitchPanel(gDialog.SettingsTab);
+ ShowInputErrorMessage(GetString("DuplicateSiteNameError").replace(/%name%/, siteName));
+ SetTextboxFocus(gDialog.SiteNameInput);
+ return false;
+ }
+
+ // Extract username and password while removing them from publishingUrl
+ var urlUserObj = {};
+ var urlPassObj = {};
+ var publishUrl = StripUsernamePassword(gDialog.PublishUrlInput.value, urlUserObj, urlPassObj);
+ if (publishUrl)
+ {
+ publishUrl = FormatUrlForPublishing(publishUrl);
+
+ // Assume scheme = "ftp://" if missing
+ // This compensates when user enters hostname w/o scheme (as most ISPs provide)
+ if (!GetScheme(publishUrl))
+ publishUrl = "ftp://" + publishUrl;
+
+ gDialog.PublishUrlInput.value = publishUrl;
+ }
+ else
+ {
+ ShowErrorInPanel(gDialog.SettingsTab, "MissingPublishUrlError", gDialog.PublishUrlInput);
+ return false;
+ }
+ var browseUrl = GetBrowseUrlInput();
+
+ var username = TrimString(gDialog.UsernameInput.value);
+ var savePassword = gDialog.SavePassword.checked;
+ var password = gDialog.PasswordInput.value;
+ var publishOtherFiles = gDialog.OtherDirCheckbox.checked;
+
+ //XXX If there was a username and/or password in the publishUrl
+ // AND in the input field, which do we use?
+ // Let's use those in url only if input is empty
+ if (!username)
+ {
+ username = urlUserObj.value;
+ gDialog.UsernameInput.value = username;
+ gSettingsChanged = true;
+ }
+ if (!password)
+ {
+ password = urlPassObj.value;
+ gDialog.PasswordInput.value = password;
+ gSettingsChanged = true;
+ }
+
+ // Update or add data for a site
+ var siteIndex = gDialog.SiteList.selectedIndex;
+ var newSite = false;
+
+ if (siteIndex == -1)
+ {
+ // No site is selected, add a new site at the end
+ if (gPublishSiteData)
+ {
+ siteIndex = gPublishSiteData.length;
+ }
+ else
+ {
+ // First time: start entire site array
+ gPublishSiteData = new Array(1);
+ siteIndex = 0;
+ gDefaultSiteIndex = 0;
+ gDefaultSiteName = siteName;
+ }
+ gPublishSiteData[siteIndex] = {};
+ gPublishSiteData[siteIndex].docDir = "";
+ gPublishSiteData[siteIndex].otherDir = "";
+ gPublishSiteData[siteIndex].dirList = [""];
+ gPublishSiteData[siteIndex].publishOtherFiles = true;
+ gPublishSiteData[siteIndex].previousSiteName = siteName;
+ newSite = true;
+ }
+ gPublishSiteData[siteIndex].siteName = siteName;
+ gPublishSiteData[siteIndex].publishUrl = publishUrl;
+ gPublishSiteData[siteIndex].browseUrl = browseUrl;
+ gPublishSiteData[siteIndex].username = username;
+ // Don't save password in data that will be saved in prefs
+ gPublishSiteData[siteIndex].password = savePassword ? password : "";
+ gPublishSiteData[siteIndex].savePassword = savePassword;
+
+ if (publishOtherFiles != gPublishSiteData[siteIndex].publishOtherFiles)
+ gSettingsChanged = true;
+
+ gPublishSiteData[siteIndex].publishOtherFiles = publishOtherFiles;
+
+ gDialog.SiteList.selectedIndex = siteIndex;
+ if (siteIndex == gDefaultSiteIndex)
+ gDefaultSiteName = siteName;
+
+ // Should never be empty, but be sure we have a default site
+ if (!gDefaultSiteName)
+ {
+ gDefaultSiteName = gPublishSiteData[0].siteName;
+ gDefaultSiteIndex = 0;
+ }
+
+ // Rebuild the site menulist if we added a new site
+ if (newSite)
+ {
+ FillSiteList();
+ gDialog.SiteList.selectedIndex = siteIndex;
+ }
+ else
+ {
+ // Update selected item if sitename changed
+ var selectedItem = gDialog.SiteList.selectedItem;
+ if (selectedItem)
+ {
+ var oldName = selectedItem.getAttribute("label");
+ if (oldName != siteName)
+ {
+ selectedItem.setAttribute("label", siteName);
+ gDialog.SiteList.setAttribute("label", siteName);
+ gSettingsChanged = true;
+ if (oldName == gDefaultSiteName)
+ gDefaultSiteName = siteName;
+ }
+ }
+ }
+
+ // Get the directory name in site to publish to
+ var docDir = GetDocDirInput();
+
+ gPublishSiteData[siteIndex].docDir = docDir;
+
+ // And directory for images and other files
+ var otherDir = GetOtherDirInput();
+ if (gDialog.SameLocationRadio.selected)
+ otherDir = docDir;
+ else
+ otherDir = GetOtherDirInput();
+
+ gPublishSiteData[siteIndex].otherDir = otherDir;
+
+ // Fill return data object
+ gReturnData.siteName = siteName;
+ gReturnData.previousSiteName = gPublishSiteData[siteIndex].previousSiteName;
+ gReturnData.publishUrl = publishUrl;
+ gReturnData.browseUrl = browseUrl;
+ gReturnData.username = username;
+ // Note that we use the password for the next publish action
+ // even if savePassword is false; but we won't save it in PasswordManager database
+ gReturnData.password = password;
+ gReturnData.savePassword = savePassword;
+ gReturnData.docDir = gPublishSiteData[siteIndex].docDir;
+ gReturnData.otherDir = gPublishSiteData[siteIndex].otherDir;
+ gReturnData.publishOtherFiles = publishOtherFiles;
+ gReturnData.dirList = gPublishSiteData[siteIndex].dirList;
+ return true;
+}
+
+function ValidateData()
+{
+ if (!ValidateSettings())
+ return false;
+
+ var siteIndex = gDialog.SiteList.selectedIndex;
+ if (siteIndex == -1)
+ return false;
+
+ var filename = TrimString(gDialog.FilenameInput.value);
+ if (!filename)
+ {
+ ShowErrorInPanel(gDialog.PublishTab, "MissingPublishFilename", gDialog.FilenameInput);
+ return false;
+ }
+ gReturnData.filename = filename;
+
+ return true;
+}
+
+function ShowErrorInPanel(tab, errorMsgId, widgetWithError)
+{
+ SwitchPanel(tab);
+ ShowInputErrorMessage(GetString(errorMsgId));
+ if (widgetWithError)
+ SetTextboxFocus(widgetWithError);
+}
+
+function onAccept(event)
+{
+ if (ValidateData())
+ {
+ // DON'T save the docDir and otherDir before trying to publish
+ gReturnData.saveDirs = false;
+
+ // We save new site data to prefs only if we are attempting to publish
+ if (gSettingsChanged)
+ SavePublishDataToPrefs(gReturnData);
+
+ // Set flag to resave data after publishing
+ // so we save docDir and otherDir if we published successfully
+ gReturnData.savePublishData = true;
+
+ var title = TrimString(gDialog.PageTitleInput.value);
+ if (title != gPreviousTitle)
+ SetDocumentTitle(title);
+
+ SaveWindowLocation();
+ window.opener.ok = true;
+ return;
+ }
+
+ event.preventDefault();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EditorPublish.xhtml b/comm/suite/editor/components/dialogs/content/EditorPublish.xhtml
new file mode 100644
index 0000000000..ed15c9f7d2
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorPublish.xhtml
@@ -0,0 +1,132 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/menulist.css" type="text/css"?>
+
+<?xul-overlay href="chrome://editor/content/EditorPublishOverlay.xhtml"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorPublish.dtd">
+
+<dialog title="&windowTitle.label;"
+ id="publishDlg"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()"
+ buttons="accept,cancel"
+ buttonlabelaccept="&publishButton.label;">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EditorPublish.js"/>
+ <script src="chrome://editor/content/publishprefs.js"/>
+ <script src="chrome://messenger/content/customElements.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <tabbox id="TabBox">
+ <tabs flex="1">
+ <tab id="PublishTab" oncommand="SelectPublishTab()" label="&publishTab.label;"/>
+ <tab id="SettingsTab" oncommand="SelectSettingsTab()" label="&settingsTab.label;"/>
+ </tabs>
+ <tabpanels>
+ <!-- PUBLISH PANEL -->
+ <vbox>
+ <spacer class="spacer"/>
+ <grid pack="start">
+ <columns><column/><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label value="&siteList.label;"
+ accesskey="&siteList.accesskey;"
+ control="SiteList"/>
+ <!-- Contents filled in at runtime -->
+ <menulist id="SiteList"
+ style="min-width:18em; max-width:18em;" crop="right"
+ tooltiptext="&siteList.tooltip;"
+ oncommand="SelectSiteList();"/>
+ <hbox>
+ <button label="&newSiteButton.label;"
+ accesskey="&newSiteButton.accesskey;"
+ oncommand="AddNewSite();"/>
+ <spacer flex="1"/>
+ </hbox>
+ </row>
+ <spacer class="spacer"/>
+ <row align="center">
+ <label value="&pageTitle.label;" accesskey="&pageTitle.accesskey;"
+ control="PageTitleInput"/>
+ <textbox id="PageTitleInput"
+ tooltiptext="&pageTitle.tooltip;" class="minWidth15"/>
+ <label value="&pageTitleExample.label;"/>
+ </row>
+ <row align="center">
+ <label value="&filename.label;" accesskey="&filename.accesskey;"
+ control="FilenameInput"/>
+ <textbox id="FilenameInput"
+ tooltiptext="&filename.tooltip;" class="minWidth15 uri-element"/>
+ <label value="&filenameExample.label;"/>
+ </row>
+ </rows>
+ </grid>
+ <spacer class="spacer"/>
+ <label value="&docDirList.label;"
+ accesskey="&docDirList.accesskey;"
+ control="DocDirList"/>
+ <hbox align="center">
+ <!-- Contents filled in at runtime -->
+ <menulist is="menulist-editable" id="DocDirList"
+ class="minWidth20 uri-element" editable="true" flex="1"
+ tooltiptext="&docDirList.tooltip;" oninput="onInputSettings();"/>
+ </hbox>
+ <spacer class="spacer"/>
+ <groupbox>
+ <caption>
+ <checkbox id="OtherDirCheckbox" label="&publishImgCheckbox.label;"
+ accesskey="&publishImgCheckbox.accesskey;"
+ tooltiptext="&publishImgCheckbox.tooltip;"
+ oncommand="doEnabling();"/>
+ </caption>
+ <vbox>
+ <radiogroup id="OtherDirRadiogroup">
+ <hbox>
+ <spacer class="checkbox-spacer"/>
+ <radio id="SameLocationRadio" label="&sameLocationRadio.label;"
+ accesskey="&sameLocationRadio.accesskey;"
+ tooltiptext="&sameLocationRadio.tooltip;"
+ oncommand="doEnabling();"/>
+ </hbox>
+ <hbox>
+ <spacer class="checkbox-spacer"/>
+ <radio id="UseSubdirRadio" label="&useSubdirRadio.label;"
+ accesskey="&useSubdirRadio.accesskey;"
+ tooltiptext="&useSubdirRadio.tooltip;"
+ oncommand="doEnabling();"/>
+ </hbox>
+ </radiogroup>
+ </vbox>
+ <hbox>
+ <spacer class="checkbox-spacer"/>
+ <spacer class="radio-spacer"/>
+ <!-- Contents filled in at runtime -->
+ <menulist is="menulist-editable" id="OtherDirList"
+ class="minWidth20 uri-element"
+ editable="true" flex="1" tooltiptext="&otherDirList.tooltip;"
+ oninput="onInputSettings();"/>
+ </hbox>
+ </groupbox>
+ <spacer flex="1"/>
+ </vbox><!-- Publish Panel -->
+
+ <!-- SETTINGS PANEL -->
+ <hbox id="SettingsPanel">
+ <!-- from EditorPublishOverlay.xhtml -->
+ <vbox id="PublishSettingsInputs" flex="1"/>
+ </hbox><!-- Settings Panel -->
+ </tabpanels>
+ </tabbox>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EditorPublishOverlay.xhtml b/comm/suite/editor/components/dialogs/content/EditorPublishOverlay.xhtml
new file mode 100644
index 0000000000..136a75623c
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorPublishOverlay.xhtml
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://editor/locale/EditorPublish.dtd">
+
+<overlay id="EditorPublishOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+<vbox id="PublishSettingsInputs">
+ <groupbox id="ServerSettingsBox">
+ <label class="header">&serverInfo.label;</label>
+ <hbox align="center">
+ <label value="&siteName.label;" accesskey="&siteName.accesskey;"
+ control="SiteNameInput"/>
+ <textbox id="SiteNameInput" class="MinWidth20em" flex="1"
+ tooltiptext="&siteName.tooltip;" oninput="onInputSettings();"/>
+ </hbox>
+ <spacer class="spacer"/>
+ <label value="&siteUrl.label;" accesskey="&siteUrl.accesskey;"
+ control="PublishUrlInput"/>
+ <textbox id="PublishUrlInput" class="MinWidth20em uri-element"
+ tooltiptext="&siteUrl.tooltip;" oninput="onInputSettings();"/>
+ <spacer class="spacer"/>
+ <label value="&browseUrl.label;" accesskey="&browseUrl.accesskey;"
+ control="BrowseUrlInput"/>
+ <textbox id="BrowseUrlInput" class="MinWidth20em uri-element"
+ tooltiptext="&browseUrl.tooltip;" oninput="onInputSettings();"/>
+ <spacer class="spacer"/>
+ </groupbox>
+ <groupbox id="LoginSettingsBox">
+ <label class="header">&loginInfo.label;</label>
+ <grid>
+ <columns><column flex="1"/><column flex="3"/></columns>
+ <rows>
+ <row align="center">
+ <label value="&username.label;" accesskey="&username.accesskey;"
+ control="UsernameInput"/>
+ <textbox id="UsernameInput" class="MinWidth10em" flex="1"
+ tooltiptext="&username.tooltip;" oninput="onInputSettings();"/>
+ </row>
+ <row align="center">
+ <label value="&password.label;" accesskey="&password.accesskey;"
+ control="PasswordInput"/>
+ <hbox>
+ <textbox id="PasswordInput" type="password" class="MinWidth5em"
+ oninput="onInputSettings();"
+ tooltiptext="&password.tooltip;"/>
+ <checkbox id="SavePassword" label="&savePassword.label;"
+ accesskey="&savePassword.accesskey;"
+ tooltiptext="&savePassword.tooltip;"
+ oncommand="onInputSettings();"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ <spacer class="spacer"/>
+ </groupbox>
+</vbox>
+
+</overlay>
diff --git a/comm/suite/editor/components/dialogs/content/EditorPublishProgress.js b/comm/suite/editor/components/dialogs/content/EditorPublishProgress.js
new file mode 100644
index 0000000000..dafa053661
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorPublishProgress.js
@@ -0,0 +1,391 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gInProgress = true;
+var gPublishData;
+var gPersistObj;
+var gTotalFileCount = 0;
+var gSucceededCount = 0;
+var gFinished = false;
+var gPublishingFailed = false;
+var gFileNotFound = false;
+var gStatusMessage="";
+
+var gTimerID;
+var gAllowEnterKey = false;
+
+// Publishing error codes
+// These are translated from C++ error code strings like this:
+// kFileNotFound = "FILE_NOT_FOUND",
+const kNetReset = 2152398868; // nsISocketTransportService.idl
+const kFileNotFound = 2152857618;
+const kNotConnected = 2152398860; // in netCore.h
+const kConnectionRefused = 2152398861; // nsISocketTransportService.idl
+const kNetTimeout = 2152398862; // nsISocketTransportService.idl
+const kNoConnectionOrTimeout = 2152398878;
+const kPortAccessNotAllowed = 2152398867; // netCore.h
+const kOffline = 2152398865; // netCore.h
+const kDiskFull = 2152857610;
+const kNoDeviceSpace = 2152857616;
+const kNameTooLong = 2152857617;
+const kAccessDenied = 2152857621;
+
+// These are more errors that I don't think we encounter during publishing,
+// so we don't have error strings yet. Let's keep them here for future reference
+//const kUnrecognizedPath = 2152857601;
+//const kUnresolvableSymlink = 2152857602;
+//const kUnknownType = 2152857604;
+//const kDestinationNotDir = 2152857605;
+//const kTargetDoesNotExist = 2152857606;
+//const kAlreadyExists = 2152857608;
+//const kInvalidPath = 2152857609;
+//const kNotDirectory = 2152857612;
+//const kIsDirectory = 2152857613;
+//const kIsLocked = 2152857614;
+//const kTooBig = 2152857615;
+//const kReadOnly = 2152857619;
+//const kDirectoryNotEmpty = 2152857620;
+//const kErrorBindingRedirected = 2152398851;
+//const kAlreadyConnected = 2152398859; // in netCore.h
+//const kInProgress = 2152398863; // netCore.h
+//const kNoContent = 2152398865; // netCore.h
+//const kUnknownProtocol = 2152398866 // netCore.h
+//const kFtpLogin = 2152398869; // ftpCore.h
+//const kFtpCWD = 2152398870; // ftpCore.h
+//const kFtpPasv = 2152398871; // ftpCore.h
+//const kFtpPwd = 2152398872; // ftpCore.h
+
+document.addEventListener("dialogaccept", onEnterKey);
+document.addEventListener("dialogcancel", onClose);
+
+function Startup()
+{
+ gPublishData = window.arguments[0];
+ if (!gPublishData)
+ {
+ dump("No publish data!\n");
+ window.close();
+ return;
+ }
+
+ gDialog.FileList = document.getElementById("FileList");
+ gDialog.FinalStatusMessage = document.getElementById("FinalStatusMessage");
+ gDialog.StatusMessage = document.getElementById("StatusMessage");
+ gDialog.KeepOpen = document.getElementById("KeepOpen");
+ gDialog.Close = document.documentElement.getButton("cancel");
+
+ SetWindowLocation();
+ var title = GetDocumentTitle();
+ if (!title)
+ title = "(" + opener.gUntitledString + ")";
+ document.title = GetString("PublishProgressCaption").replace(/%title%/, title);
+
+ document.getElementById("PublishToSite").value =
+ GetString("PublishToSite").replace(/%title%/, TruncateStringAtWordEnd(gPublishData.siteName, 25));
+
+ // Show publishing destination URL
+ document.getElementById("PublishUrl").value = gPublishData.publishUrl;
+
+ // Show subdirectories only if not empty
+ if (gPublishData.docDir || gPublishData.otherDir)
+ {
+ if (gPublishData.docDir)
+ document.getElementById("docDir").value = gPublishData.docDir;
+ else
+ document.getElementById("DocSubdir").hidden = true;
+
+ if (gPublishData.publishOtherFiles && gPublishData.otherDir)
+ document.getElementById("otherDir").value = gPublishData.otherDir;
+ else
+ document.getElementById("OtherSubdir").hidden = true;
+ }
+ else
+ document.getElementById("Subdirectories").hidden = true;
+
+ // Add the document to the "publish to" list as quick as possible!
+ SetProgressStatus(gPublishData.filename, "busy");
+
+ if (gPublishData.publishOtherFiles)
+ {
+ // When publishing images as well, expand list to show more items
+ gDialog.FileList.setAttribute("rows", 5);
+ window.sizeToContent();
+ }
+
+ // Now that dialog is initialized, we can start publishing
+ gPersistObj = window.opener.StartPublishing();
+}
+
+// this function is to be used when we cancel persist's saving
+// since not all messages will be returned to us if we cancel
+// this function changes status for all non-done/non-failure to failure
+function SetProgressStatusCancel()
+{
+ let listitems = document.querySelectorAll('listitem:not([progress="done"]):not([progress="failed"])');
+ if (!listitems)
+ return;
+
+ for (var i=0; i < listitems.length; i++)
+ {
+ listitems[i].setAttribute("progress", "failed");
+ }
+}
+
+// Add filename to list of files to publish
+// or set status for file already in the list
+// Returns true if file was in the list
+function SetProgressStatus(filename, status)
+{
+ if (!filename)
+ return false;
+
+ if (!status)
+ status = "busy";
+
+ // Just set attribute for status icon if we already have this filename.
+ let listitem = document.querySelector('listitem[label="' + filename + '"]');
+ if (listitem)
+ {
+ listitem.setAttribute("progress", status);
+ return true;
+ }
+ // We're adding a new file item to list
+ gTotalFileCount++;
+
+ listitem = document.createXULElement("listitem");
+ if (listitem)
+ {
+ listitem.setAttribute("class", "listitem-iconic progressitem");
+ // This triggers CSS to show icon for each status state
+ listitem.setAttribute("progress", status);
+ listitem.setAttribute("label", filename);
+ gDialog.FileList.appendChild(listitem);
+ }
+ return false;
+}
+
+function SetProgressFinished(filename, networkStatus)
+{
+ var abortPublishing = false;
+ if (filename)
+ {
+ var status = networkStatus ? "failed" : "done";
+ if (networkStatus == 0)
+ gSucceededCount++;
+
+ SetProgressStatus(filename, status);
+ }
+
+ if (networkStatus != 0) // Error condition
+ {
+ // We abort on all errors except if image file was not found
+ abortPublishing = networkStatus != kFileNotFound;
+
+ // Mark all remaining files as "failed"
+ if (abortPublishing)
+ {
+ gPublishingFailed = true;
+ SetProgressStatusCancel();
+ gDialog.FinalStatusMessage.value = GetString("PublishFailed");
+ }
+
+ switch (networkStatus)
+ {
+ case kFileNotFound:
+ gFileNotFound = true;
+ if (filename)
+ gStatusMessage = GetString("FileNotFound").replace(/%file%/, filename);
+ break;
+ case kNetReset:
+ // We get this when subdir doesn't exist AND
+ // if filename used is same as an existing subdir
+ var dir = (gPublishData.filename == filename) ?
+ gPublishData.docDir : gPublishData.otherDir;
+
+ if (dir)
+ {
+ // This is the ambiguous case when we can't tell if subdir or filename is bad
+ // Remove terminal "/" from dir string and insert into message
+ gStatusMessage = GetString("SubdirDoesNotExist").replace(/%dir%/, dir.slice(0, dir.length-1));
+ gStatusMessage = gStatusMessage.replace(/%file%/, filename);
+
+ // Remove directory from saved prefs
+ // XXX Note that if subdir is good,
+ // but filename = next level subdirectory name,
+ // we really shouldn't remove subdirectory,
+ // but it's impossible to differentiate this case!
+ RemovePublishSubdirectoryFromPrefs(gPublishData, dir);
+ }
+ else if (filename)
+ gStatusMessage = GetString("FilenameIsSubdir").replace(/%file%/, filename);
+
+ break;
+ case kNotConnected:
+ case kConnectionRefused:
+ case kNetTimeout:
+ case kNoConnectionOrTimeout:
+ case kPortAccessNotAllowed:
+ gStatusMessage = GetString("ServerNotAvailable");
+ break;
+ case kOffline:
+ gStatusMessage = GetString("Offline");
+ break;
+ case kDiskFull:
+ case kNoDeviceSpace:
+ if (filename)
+ gStatusMessage = GetString("DiskFull").replace(/%file%/, filename);
+ break;
+ case kNameTooLong:
+ if (filename)
+ gStatusMessage = GetString("NameTooLong").replace(/%file%/, filename);
+ break;
+ case kAccessDenied:
+ if (filename)
+ gStatusMessage = GetString("AccessDenied").replace(/%file%/, filename);
+ break;
+ case kUnknownType:
+ default:
+ gStatusMessage = GetString("UnknownPublishError")
+ break;
+ }
+ }
+ else if (!filename)
+ {
+ gFinished = true;
+
+ document.documentElement.setAttribute("buttonlabelcancel",
+ document.documentElement.getAttribute("buttonlabelclose"));
+
+ if (!gStatusMessage)
+ gStatusMessage = GetString(gPublishingFailed ? "UnknownPublishError" : "AllFilesPublished");
+
+ // Now allow "Enter/Return" key to close the dialog
+ AllowDefaultButton();
+
+ if (gPublishingFailed || gFileNotFound)
+ {
+ // Show "Troubleshooting" button to help solving problems
+ // and key for successful / failed files
+ document.getElementById("failureBox").hidden = false;
+ }
+ }
+
+ if (gStatusMessage)
+ SetStatusMessage(gStatusMessage);
+}
+
+function CheckKeepOpen()
+{
+ if (gTimerID)
+ {
+ clearTimeout(gTimerID);
+ gTimerID = null;
+ }
+}
+
+function onClose()
+{
+ if (!gFinished)
+ {
+ const buttonFlags = (Services.prompt.BUTTON_TITLE_IS_STRING *
+ Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_CANCEL *
+ Services.prompt.BUTTON_POS_1);
+ let button = Services.prompt.confirmEx(window,
+ GetString("CancelPublishTitle"),
+ GetString("CancelPublishMessage"),
+ buttonFlags,
+ GetString("CancelPublishContinue"),
+ null, null, null, {});
+ if (button == 0)
+ return false;
+ }
+
+ if (gTimerID)
+ {
+ clearTimeout(gTimerID);
+ gTimerID = null;
+ }
+
+ if (!gFinished && gPersistObj)
+ {
+ try {
+ gPersistObj.cancelSave();
+ } catch (e) {}
+ }
+ SaveWindowLocation();
+
+ // Tell caller so they can cleanup and restore editability
+ window.opener.FinishPublishing();
+ return true;
+}
+
+function AllowDefaultButton()
+{
+ gDialog.Close.setAttribute("default","true");
+ gAllowEnterKey = true;
+}
+
+function onEnterKey(event)
+{
+ if (gAllowEnterKey)
+ return CloseDialog();
+
+ event.preventDefault();
+}
+
+function RequestCloseDialog()
+{
+ // Finish progress messages, settings buttons etc.
+ SetProgressFinished(null, 0);
+
+ if (!gDialog.KeepOpen.checked)
+ {
+ // Leave window open a minimum amount of time
+ gTimerID = setTimeout(CloseDialog, 3000);
+ }
+
+ // Set "completed" message if we succeeded
+ // (Some image files may have failed,
+ // but we don't abort publishing for that)
+ if (!gPublishingFailed)
+ {
+ gDialog.FinalStatusMessage.value = GetString("PublishCompleted");
+ if (gFileNotFound && gTotalFileCount-gSucceededCount)
+ {
+ // Show number of files that failed to upload
+ gStatusMessage =
+ (GetString("FailedFileMsg").replace(/%x%/,(gTotalFileCount-gSucceededCount)))
+ .replace(/%total%/,gTotalFileCount);
+
+ SetStatusMessage(gStatusMessage);
+ }
+ }
+}
+
+function SetStatusMessage(message)
+{
+ // Status message is a child of <description> element
+ // so text can wrap to multiple lines if necessary
+ if (gDialog.StatusMessage.firstChild)
+ {
+ gDialog.StatusMessage.firstChild.data = message;
+ }
+ else
+ {
+ var textNode = document.createTextNode(message);
+ if (textNode)
+ gDialog.StatusMessage.appendChild(textNode);
+ }
+ window.sizeToContent();
+}
+
+function CloseDialog()
+{
+ SaveWindowLocation();
+ window.opener.FinishPublishing();
+ try {
+ window.close();
+ } catch (e) {}
+}
diff --git a/comm/suite/editor/components/dialogs/content/EditorPublishProgress.xhtml b/comm/suite/editor/components/dialogs/content/EditorPublishProgress.xhtml
new file mode 100644
index 0000000000..2cf422c8b5
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorPublishProgress.xhtml
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorPublishProgress.dtd">
+
+<dialog title=""
+ id="publishProgressDlg"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ buttons="cancel"
+ buttonlabelclose="&closeButton.label;"
+ onload="Startup()">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/publishprefs.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EditorPublishProgress.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <caption><label id="PublishToSite"/></caption>
+ <label value="&siteUrl.label;"/>
+ <hbox>
+ <label class="indent bold" id="PublishUrl"/>
+ </hbox>
+ <spacer class="spacer"/>
+ <grid id="Subdirectories">
+ <columns><column/><column/></columns>
+ <rows>
+ <row id="DocSubdir">
+ <label value="&docSubdir.label;"/>
+ <label id="docDir"/>
+ </row>
+ <row id="OtherSubdir">
+ <label value="&otherSubdir.label;"/>
+ <label id="otherDir"/>
+ </row>
+ </rows>
+ </grid>
+ <label id="OtherUrl" class="bold" style="margin-left:3em"/>
+ </groupbox>
+ <groupbox>
+ <caption><label value="&fileList.label;"/></caption>
+ <vbox align="center" style="max-width:30em">
+ <label id="FinalStatusMessage" class="bold" value="&status.label;"/>
+ </vbox>
+ <description id="StatusMessage" class="wrap" style="max-width:30em; min-height: 1em"/>
+ <vbox flex="1">
+ <listbox id="FileList" rows="1"/>
+ </vbox>
+ <hbox align="center" id="failureBox" hidden="true">
+ <image class="progressitem" progress="done"/>
+ <label value="&succeeded.label;"/>
+ <spacer class="bigspacer"/>
+ <image class="progressitem" progress="failed"/>
+ <label value="&failed.label;"/>
+ </hbox>
+ </groupbox>
+ <checkbox id="KeepOpen" label="&keepOpen;" oncommand="CheckKeepOpen();" persist="checked"/>
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EditorPublishSettings.js b/comm/suite/editor/components/dialogs/content/EditorPublishSettings.js
new file mode 100644
index 0000000000..01677ae8c0
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorPublishSettings.js
@@ -0,0 +1,343 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gPublishSiteData;
+var gPublishDataChanged = false;
+var gDefaultSiteIndex = -1;
+var gDefaultSiteName;
+var gPreviousDefaultSite;
+var gPreviousTitle;
+var gSettingsChanged = false;
+var gSiteDataChanged = false;
+var gAddNewSite = false;
+var gCurrentSiteIndex = -1;
+var gPasswordManagerOn = true;
+
+// Dialog initialization code
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+function Startup()
+{
+ if (!GetCurrentEditor())
+ {
+ window.close();
+ return;
+ }
+
+ gDialog.SiteList = document.getElementById("SiteList");
+ gDialog.SiteNameInput = document.getElementById("SiteNameInput");
+ gDialog.PublishUrlInput = document.getElementById("PublishUrlInput");
+ gDialog.BrowseUrlInput = document.getElementById("BrowseUrlInput");
+ gDialog.UsernameInput = document.getElementById("UsernameInput");
+ gDialog.PasswordInput = document.getElementById("PasswordInput");
+ gDialog.SavePassword = document.getElementById("SavePassword");
+ gDialog.SetDefaultButton = document.getElementById("SetDefaultButton");
+ gDialog.RemoveSiteButton = document.getElementById("RemoveSiteButton");
+ gDialog.OkButton = document.documentElement.getButton("accept");
+
+ gPublishSiteData = GetPublishSiteData();
+ gDefaultSiteName = GetDefaultPublishSiteName();
+ gPreviousDefaultSite = gDefaultSiteName;
+
+ gPasswordManagerOn = Services.prefs.getBoolPref("signon.rememberSignons");
+ gDialog.SavePassword.disabled = !gPasswordManagerOn;
+
+ InitDialog();
+
+ SetWindowLocation();
+}
+
+function InitDialog()
+{
+ // If there's no current site data, start a new item in the Settings panel
+ if (!gPublishSiteData)
+ {
+ AddNewSite();
+ }
+ else
+ {
+ FillSiteList();
+
+ // uncomment next code line if you want preselection of the default
+ // publishing site
+ //InitSiteSettings(gDefaultSiteIndex);
+
+ SetTextboxFocus(gDialog.SiteNameInput);
+ }
+}
+
+function FillSiteList()
+{
+ // Prevent triggering SelectSiteList() actions
+ gIsSelecting = true;
+ ClearListbox(gDialog.SiteList);
+ gIsSelecting = false;
+ gDefaultSiteIndex = -1;
+
+ // Fill the site list
+ var count = gPublishSiteData.length;
+ for (var i = 0; i < count; i++)
+ {
+ var name = gPublishSiteData[i].siteName;
+ var item = gDialog.SiteList.appendItem(name);
+ SetPublishItemStyle(item);
+ if (name == gDefaultSiteName)
+ gDefaultSiteIndex = i;
+ }
+}
+
+function SetPublishItemStyle(item)
+{
+ // Display default site with bold style
+ if (item)
+ {
+ if (item.getAttribute("label") == gDefaultSiteName)
+ item.setAttribute("class", "bold");
+ else
+ item.removeAttribute("class");
+ }
+}
+
+function AddNewSite()
+{
+ // Save any pending changes locally first
+ if (!ApplyChanges())
+ return;
+
+ // Initialize Setting widgets to none of the selected sites
+ InitSiteSettings(-1);
+ gAddNewSite = true;
+
+ SetTextboxFocus(gDialog.SiteNameInput);
+}
+
+function RemoveSite()
+{
+ if (!gPublishSiteData)
+ return;
+
+ var index = gDialog.SiteList.selectedIndex;
+ if (index != -1)
+ {
+ let item = gDialog.SiteList.selectedItem;
+ var nameToRemove = item.getAttribute("label");
+
+ // Remove one item from site data array
+ gPublishSiteData.splice(index, 1);
+ // Remove item from site list
+ gDialog.SiteList.clearSelection();
+ item.remove();
+
+ // Adjust if we removed last item and reselect a site
+ if (index >= gPublishSiteData.length)
+ index--;
+ InitSiteSettings(index);
+
+ if (nameToRemove == gDefaultSiteName)
+ {
+ // Deleting current default -- set to new selected item
+ // Arbitrary, but what else to do?
+ SetDefault();
+ }
+ gSiteDataChanged = true;
+ }
+}
+
+function SetDefault()
+{
+ if (!gPublishSiteData)
+ return;
+
+ var index = gDialog.SiteList.selectedIndex;
+ if (index != -1)
+ {
+ gDefaultSiteIndex = index;
+ gDefaultSiteName = gPublishSiteData[index].siteName;
+
+ // Set bold style on new default
+ var item = gDialog.SiteList.firstChild;
+ while (item)
+ {
+ SetPublishItemStyle(item);
+ item = item.nextSibling;
+ }
+ }
+}
+
+// Recursion prevention:
+// Use when you don't want to trigger ApplyChanges and InitSiteSettings
+var gIsSelecting = false;
+
+function SelectSiteList()
+{
+ if (gIsSelecting)
+ return;
+
+ gIsSelecting = true;
+ var newIndex = gDialog.SiteList.selectedIndex;
+
+ // Save any pending changes locally first
+ if (!ApplyChanges())
+ return;
+
+ InitSiteSettings(newIndex);
+
+ gIsSelecting = false;
+}
+
+// Use this to prevent recursion in SelectSiteList
+function SetSelectedSiteIndex(index)
+{
+ gIsSelecting = true;
+ gDialog.SiteList.selectedIndex = index;
+ gIsSelecting = false;
+}
+
+function InitSiteSettings(selectedSiteIndex)
+{
+ // Index to the site we will need to update if settings changed
+ gCurrentSiteIndex = selectedSiteIndex;
+
+ SetSelectedSiteIndex(selectedSiteIndex);
+ var haveData = (gPublishSiteData && selectedSiteIndex != -1);
+
+ gDialog.SiteNameInput.value = haveData ? gPublishSiteData[selectedSiteIndex].siteName : "";
+ gDialog.PublishUrlInput.value = haveData ? gPublishSiteData[selectedSiteIndex].publishUrl : "";
+ gDialog.BrowseUrlInput.value = haveData ? gPublishSiteData[selectedSiteIndex].browseUrl : "";
+ gDialog.UsernameInput.value = haveData ? gPublishSiteData[selectedSiteIndex].username : "";
+
+ var savePassord = haveData && gPasswordManagerOn;
+ gDialog.PasswordInput.value = savePassord ? gPublishSiteData[selectedSiteIndex].password : "";
+ gDialog.SavePassword.checked = savePassord ? gPublishSiteData[selectedSiteIndex].savePassword : false;
+
+ gDialog.SetDefaultButton.disabled = !haveData;
+ gDialog.RemoveSiteButton.disabled = !haveData;
+ gSettingsChanged = false;
+}
+
+function onInputSettings()
+{
+ // TODO: Save current data during SelectSite1 and compare here
+ // to detect if real change has occurred?
+ gSettingsChanged = true;
+}
+
+function ApplyChanges()
+{
+ if (gSettingsChanged && !UpdateSettings())
+ {
+ // Restore selection to previously current site
+ SetSelectedSiteIndex(gCurrentSiteIndex);
+ return false;
+ }
+ return true;
+}
+
+function UpdateSettings()
+{
+ // Validate and add new site
+ var newName = TrimString(gDialog.SiteNameInput.value);
+ if (!newName)
+ {
+ ShowInputErrorMessage(GetString("MissingSiteNameError"), gDialog.SiteNameInput);
+ return false;
+ }
+ if (PublishSiteNameExists(newName, gPublishSiteData, gCurrentSiteIndex))
+ {
+ ShowInputErrorMessage(GetString("DuplicateSiteNameError").replace(/%name%/, newName));
+ SetTextboxFocus(gDialog.SiteNameInput);
+ return false;
+ }
+
+ var newUrl = FormatUrlForPublishing(gDialog.PublishUrlInput.value);
+ if (!newUrl)
+ {
+ ShowInputErrorMessage(GetString("MissingPublishUrlError"), gDialog.PublishUrlInput);
+ return false;
+ }
+
+ // Start assuming we're updating existing site at gCurrentSiteIndex
+ var newSiteData = false;
+
+ if (!gPublishSiteData)
+ {
+ // First time used - Create the first site profile
+ gPublishSiteData = new Array(1);
+ gCurrentSiteIndex = 0;
+ newSiteData = true;
+ }
+ else if (gCurrentSiteIndex == -1)
+ {
+ // No currently-selected site,
+ // must be adding a new site
+ // Add new data at the end of list
+ gCurrentSiteIndex = gPublishSiteData.length;
+ newSiteData = true;
+ }
+
+ if (newSiteData)
+ {
+ // Init new site profile
+ gPublishSiteData[gCurrentSiteIndex] = {};
+ gPublishSiteData[gCurrentSiteIndex].docDir = "";
+ gPublishSiteData[gCurrentSiteIndex].otherDir = "";
+ gPublishSiteData[gCurrentSiteIndex].dirList = [""];
+ gPublishSiteData[gCurrentSiteIndex].previousSiteName = newName;
+ }
+
+ gPublishSiteData[gCurrentSiteIndex].siteName = newName;
+ gPublishSiteData[gCurrentSiteIndex].publishUrl = newUrl;
+ gPublishSiteData[gCurrentSiteIndex].browseUrl = FormatUrlForPublishing(gDialog.BrowseUrlInput.value);
+ gPublishSiteData[gCurrentSiteIndex].username = TrimString(gDialog.UsernameInput.value);
+ gPublishSiteData[gCurrentSiteIndex].password= gDialog.PasswordInput.value;
+ gPublishSiteData[gCurrentSiteIndex].savePassword = gDialog.SavePassword.checked;
+
+ if (gCurrentSiteIndex == gDefaultSiteIndex)
+ gDefaultSiteName = newName;
+
+ // When adding the very first site, assume that's the default
+ if (gPublishSiteData.length == 1 && !gDefaultSiteName)
+ {
+ gDefaultSiteName = gPublishSiteData[0].siteName;
+ gDefaultSiteIndex = 0;
+ }
+
+ FillSiteList();
+
+ // Select current site in list
+ SetSelectedSiteIndex(gCurrentSiteIndex);
+
+ // Signal saving data to prefs
+ gSiteDataChanged = true;
+
+ // Clear current site flags
+ gSettingsChanged = false;
+ gAddNewSite = false;
+
+ return true;
+}
+
+function onAccept(event)
+{
+ // Save any pending changes locally first
+ if (!ApplyChanges()) {
+ event.preventDefault();
+ return;
+ }
+
+ if (gSiteDataChanged)
+ {
+ // Save all local data to prefs
+ SavePublishSiteDataToPrefs(gPublishSiteData, gDefaultSiteName);
+ }
+ else if (gPreviousDefaultSite != gDefaultSiteName)
+ {
+ // only the default site was changed
+ SetDefaultSiteName(gDefaultSiteName);
+ }
+
+ SaveWindowLocation();
+}
diff --git a/comm/suite/editor/components/dialogs/content/EditorPublishSettings.xhtml b/comm/suite/editor/components/dialogs/content/EditorPublishSettings.xhtml
new file mode 100644
index 0000000000..ace6c2fc5d
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorPublishSettings.xhtml
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<?xul-overlay href="chrome://editor/content/EditorPublishOverlay.xhtml"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorPublish.dtd">
+
+<dialog title="&windowTitleSettings.label;"
+ id="publishSettingsDlg"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="Startup()"
+ buttons="accept,cancel">
+
+ <!-- Methods common to all editor dialogs -->
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EditorPublishSettings.js"/>
+ <script src="chrome://editor/content/publishprefs.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <hbox id="SettingsPanel">
+ <groupbox align="center">
+ <label class="header">&publishSites.label;</label>
+ <!-- XXX: If tree isn't wrapped in vbox, it appears BELOW next vbox -->
+ <vbox flex="1">
+ <listbox rows="4" id="SiteList" flex="1" onselect="SelectSiteList();"/>
+ </vbox>
+ <hbox pack="center">
+ <vbox>
+ <button id="NewSiteButton" label="&newSiteButton.label;"
+ accesskey="&newSiteButton.accesskey;" oncommand="AddNewSite();"/>
+ <button id="SetDefaultButton" label="&setDefaultButton.label;"
+ accesskey="&setDefaultButton.accesskey;" oncommand="SetDefault();"/>
+ <button id="RemoveSiteButton" label="&removeButton.label;"
+ accesskey="&removeButton.accesskey;" oncommand="RemoveSite();"/>
+ </vbox>
+ </hbox>
+ </groupbox>
+ <!-- from EditorPublishOverlay.xhtml -->
+ <vbox id="PublishSettingsInputs"/>
+ </hbox>
+ <spacer class="spacer"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.js b/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.js
new file mode 100644
index 0000000000..745a8bfd30
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.js
@@ -0,0 +1,155 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+var {CharsetMenu} = ChromeUtils.import("resource://gre/modules/CharsetMenu.jsm");
+
+var gCharset="";
+var gTitleWasEdited = false;
+var gCharsetWasChanged = false;
+var gInsertNewContentType = false;
+var gContenttypeElement;
+var gInitDone = false;
+var gCharsetInfo;
+
+//Cancel() is in EdDialogCommon.js
+
+var gCharsetView = {
+ get rowCount() { return gCharsetInfo.length; },
+ selection: null,
+ getRowProperties: function(index) { return ""; },
+ getCellProperties: function(index, column) { return ""; },
+ getColumnProperties: function(columm) { return ""; },
+ isContainer: function() { return false; },
+ isContainerOpen: function() { return false; },
+ isContainerEmpty: function() { return true; },
+ isSeparator: function() { return false; },
+ isSorted: function() { return false; },
+ canDrop: function(index, orientation) { return false; },
+ drop: function(index, orientation) {},
+ getParentIndex: function(index) { return -1; },
+ hasNextSibling: function(index, after) { return false; },
+ getLevel: function(index) { return 1; },
+ getImageSrc: function(index) { return null; },
+ getProgressMode: function(index) { return 0; },
+ getCellValue: function(index) { return ""; },
+ getCellText: function(index) { return gCharsetInfo[index].label; },
+ toggleOpenState: function(index) {},
+ cycleHeader: function(column) {},
+ selectionChanged: function() {},
+ cycleCell: function(index, column) {},
+ isEditable: function isEditable(index, column) { return false; },
+};
+
+function Startup()
+{
+ var editor = GetCurrentEditor();
+ if (!editor)
+ {
+ window.close();
+ return;
+ }
+
+ gDialog.TitleInput = document.getElementById("TitleInput");
+ gDialog.charsetTree = document.getElementById('CharsetTree');
+ gDialog.exportToText = document.getElementById('ExportToText');
+
+ gContenttypeElement = GetMetaElementByAttribute("http-equiv", "content-type");
+ if (!gContenttypeElement && (editor.contentsMIMEType != 'text/plain'))
+ {
+ gContenttypeElement = CreateMetaElementWithAttribute("http-equiv", "content-type");
+ if (!gContenttypeElement )
+ {
+ window.close();
+ return;
+ }
+ gInsertNewContentType = true;
+ }
+
+ try {
+ gCharset = editor.documentCharacterSet;
+ } catch (e) {}
+
+ var data = CharsetMenu.getData();
+ var charsets = data.pinnedCharsets.concat(data.otherCharsets);
+ gCharsetInfo = CharsetMenu.getCharsetInfo(charsets.map(info => info.value));
+ gDialog.charsetTree.view = gCharsetView;
+
+ InitDialog();
+
+ // Use the same text as the messagebox for getting title by regular "Save"
+ document.getElementById("EnterTitleLabel").setAttribute("value",GetString("NeedDocTitle"));
+ // This is an <HTML> element so it wraps -- append a child textnode
+ var helpTextParent = document.getElementById("TitleHelp");
+ var helpText = document.createTextNode(GetString("DocTitleHelp"));
+ if (helpTextParent)
+ helpTextParent.appendChild(helpText);
+
+ // SET FOCUS TO FIRST CONTROL
+ SetTextboxFocus(gDialog.TitleInput);
+
+ gInitDone = true;
+
+ SetWindowLocation();
+}
+
+
+function InitDialog()
+{
+ gDialog.TitleInput.value = GetDocumentTitle();
+
+ var tree = gDialog.charsetTree;
+ var index = gCharsetInfo.map(info => info.value).indexOf(gCharset);
+ if (index >= 0) {
+ tree.view.selection.select(index);
+ tree.ensureRowIsVisible(index);
+ }
+}
+
+
+function onAccept()
+{
+ var editor = GetCurrentEditor();
+ editor.beginTransaction();
+
+ if(gCharsetWasChanged)
+ {
+ try {
+ SetMetaElementContent(gContenttypeElement, "text/html; charset=" + gCharset, gInsertNewContentType, true);
+ editor.documentCharacterSet = gCharset;
+ } catch (e) {}
+ }
+
+ editor.endTransaction();
+
+ if(gTitleWasEdited)
+ SetDocumentTitle(TrimString(gDialog.TitleInput.value));
+
+ window.opener.ok = true;
+ window.opener.exportToText = gDialog.exportToText.checked;
+ SaveWindowLocation();
+}
+
+
+function SelectCharset()
+{
+ if(gInitDone)
+ {
+ try
+ {
+ gCharset = gCharsetInfo[gDialog.charsetTree.currentIndex].value;
+ if (gCharset)
+ gCharsetWasChanged = true;
+ }
+ catch(e) {}
+ }
+}
+
+
+function TitleChanged()
+{
+ gTitleWasEdited = true;
+}
diff --git a/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.xhtml b/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.xhtml
new file mode 100644
index 0000000000..815a56dfe3
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/EditorSaveAsCharset.xhtml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://editor/skin/editor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://editor/locale/EditorSaveAsCharset.dtd">
+
+<dialog title="&windowTitle2.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ onload = "Startup()"
+ style="width: 32em;">
+
+ <script src="chrome://editor/content/editorUtilities.js"/>
+ <script src="chrome://editor/content/EdDialogCommon.js"/>
+ <script src="chrome://editor/content/EditorSaveAsCharset.js"/>
+
+ <spacer id="location" offsetY="50" persist="offsetX offsetY"/>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label class="header">&documentTitleTitle.label;</label>
+ </hbox>
+ <label id="EnterTitleLabel"/>
+ <textbox id="TitleInput" oninput="TitleChanged();"/>
+ <description id="TitleHelp" class="wrap" style="width:1em" />
+ </groupbox>
+
+ <groupbox flex="1">
+ <hbox class="groupbox-title">
+ <label class="header">&documentCharsetTitle2.label;</label>
+ </hbox>
+ <label value="&documentCharsetDesc2.label;"/>
+ <tree id="CharsetTree" rows="8" hidecolumnpicker="true" onselect="SelectCharset();">
+ <treecols>
+ <treecol id="CharsetCol" flex="1" hideheader="true"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ </groupbox>
+
+ <checkbox id="ExportToText" label="&documentExportToText.label;" />
+ <separator class="groove"/>
+</dialog>
diff --git a/comm/suite/editor/components/dialogs/content/edImage.inc.xhtml b/comm/suite/editor/components/dialogs/content/edImage.inc.xhtml
new file mode 100644
index 0000000000..e80fb0457c
--- /dev/null
+++ b/comm/suite/editor/components/dialogs/content/edImage.inc.xhtml
@@ -0,0 +1,248 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+ <vbox id="imageLocation">
+ <spacer class="spacer"/>
+ <label control = "srcInput"
+ value = "&locationEditField.label;"
+ accesskey="&locationEditField.accessKey;"
+ tooltiptext="&locationEditField.tooltip;"
+ />
+ <tooltip id="shortenedDataURI">
+ <label value="&locationEditField.shortenedDataURI;"/>
+ </tooltip>
+ <textbox id="srcInput" oninput="ChangeImageSrc();" tabindex="1" class="uri-element"
+ tooltiptext="&locationEditField.tooltip;"/>
+ <hbox id="MakeRelativeHbox">
+ <checkbox id="MakeRelativeCheckbox"
+ for="srcInput"
+ tabindex="2"
+ label="&makeUrlRelative.label;"
+ accesskey="&makeUrlRelative.accessKey;"
+ oncommand="MakeInputValueRelativeOrAbsolute(this);"
+ tooltiptext="&makeUrlRelative.tooltip;"/>
+ <checkbox id="AttachSourceToMail"
+ hidden="true"
+ label="&attachImageSource.label;"
+ accesskey="&attachImageSource.accesskey;"
+ oncommand="DoAttachSourceCheckbox()"/>
+ <spacer flex="1"/>
+ <button id="ChooseFile"
+ tabindex="3"
+ oncommand="chooseFile()"
+ label="&chooseFileButton.label;"
+ accesskey="&chooseFileButton.accessKey;"/>
+ </hbox>
+ <spacer class="spacer"/>
+ <radiogroup id="altTextRadioGroup" flex="1">
+ <grid>
+ <columns><column/><column flex="1"/></columns>
+ <rows>
+ <row align="center">
+ <label
+ style = "margin-left: 26px"
+ control = "titleInput"
+ accesskey = "&title.accessKey;"
+ value ="&title.label;"
+ tooltiptext="&title.tooltip;"
+ for = "titleInput"/>
+ <textbox flex="1"
+ id = "titleInput"
+ class = "MinWidth20em"
+ tooltiptext="&title.tooltip;"
+ tabindex="4"/>
+ </row>
+ <row align="center">
+ <radio id="altTextRadio" value="usealt-yes"
+ label="&altText.label;"
+ accesskey="&altText.accessKey;"
+ tooltiptext="&altTextEditField.tooltip;"
+#ifndef MOZ_SUITE
+ persist="selected"
+#endif
+ oncommand = "SetAltTextDisabled(false);"
+ tabindex="5"/>
+ <textbox flex="1"
+ id = "altTextInput"
+ class = "MinWidth20em"
+ tooltiptext="&altTextEditField.tooltip;"
+ oninput = "SetAltTextDisabled(false);"
+ tabindex="6"/>
+ </row>
+ </rows>
+ </grid>
+
+ <radio id="noAltTextRadio" value="usealt-no"
+ label="&noAltText.label;"
+ accesskey = "&noAltText.accessKey;"
+#ifndef MOZ_SUITE
+ persist="selected"
+#endif
+ oncommand = "SetAltTextDisabled(true);"/>
+ </radiogroup>
+ </vbox>
+
+ <vbox id="imageDimensions" align="start">
+ <spacer class="spacer"/>
+ <hbox>
+ <radiogroup id="imgSizeGroup">
+ <radio
+ id = "actualSizeRadio"
+ label = "&actualSizeRadio.label;"
+ accesskey = "&actualSizeRadio.accessKey;"
+ tooltiptext="&actualSizeRadio.tooltip;"
+ oncommand = "SetActualSize()"
+ value="actual"/>
+ <radio
+ id = "customSizeRadio"
+ label = "&customSizeRadio.label;"
+ selected = "true"
+ accesskey = "&customSizeRadio.accessKey;"
+ tooltiptext="&customSizeRadio.tooltip;"
+ oncommand = "doDimensionEnabling();"
+ value="custom"/>
+ </radiogroup>
+ <spacer flex="1"/>
+ <vbox>
+ <spacer flex="1"/>
+ <checkbox id="constrainCheckbox" label="&constrainCheckbox.label;"
+ accesskey="&constrainCheckbox.accessKey;"
+ oncommand="ToggleConstrain()"
+ tooltiptext="&constrainCheckbox.tooltip;"/>
+ </vbox>
+ <spacer flex="1"/>
+ </hbox>
+ <spacer class="spacer"/>
+ <grid class="indent">
+ <columns><column/><column/><column flex="1"/></columns>
+ <rows>
+ <row align="center">
+ <label id = "widthLabel"
+ control = "widthInput"
+ accesskey = "&widthEditField.accessKey;"
+ value = "&widthEditField.label;" />
+ <textbox
+ id = "widthInput"
+ class = "narrow"
+ oninput = "constrainProportions(this.id, 'heightInput')"/>
+ <menulist id = "widthUnitsMenulist"
+ oncommand = "doDimensionEnabling();" />
+ <!-- contents are appended by JS -->
+ </row>
+ <row align="center">
+ <label id = "heightLabel"
+ control = "heightInput"
+ accesskey = "&heightEditField.accessKey;"
+ value = "&heightEditField.label;" />
+ <textbox
+ id = "heightInput"
+ class = "narrow"
+ oninput = "constrainProportions(this.id, 'widthInput')"/>
+ <menulist id = "heightUnitsMenulist"
+ oncommand = "doDimensionEnabling();" />
+ <!-- contents are appended by JS -->
+ </row>
+ </rows>
+ </grid>
+ <spacer flex="1"/>
+ </vbox>
+
+ <hbox id="imageAppearance">
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label id="spacingLabel" class="header">&spacingBox.label;</label>
+ </hbox>
+ <grid>
+ <columns><column/><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label
+ class = "align-right"
+ id = "leftrightLabel"
+ control = "imageleftrightInput"
+ accesskey = "&leftRightEditField.accessKey;"
+ value = "&leftRightEditField.label;"/>
+ <textbox
+ class = "narrow"
+ id = "imageleftrightInput"
+ oninput = "forceInteger(this.id)"/>
+ <label
+ id = "leftrighttypeLabel"
+ value = "&pixelsPopup.value;" />
+ </row>
+ <spacer class="spacer"/>
+ <row align="center">
+ <label
+ class = "align-right"
+ id = "topbottomLabel"
+ control = "imagetopbottomInput"
+ accesskey = "&topBottomEditField.accessKey;"
+ value = "&topBottomEditField.label;"/>
+ <textbox
+ class = "narrow"
+ id = "imagetopbottomInput"
+ oninput = "forceInteger(this.id)"/>
+ <label id="topbottomtypeLabel"
+ value="&pixelsPopup.value;" />
+ </row>
+ <spacer class="spacer"/>
+ <row align="center">
+ <label class="align-right"
+ id="borderLabel"
+ control="border"
+ accesskey="&borderEditField.accessKey;"
+ value="&borderEditField.label;"/>
+ <textbox
+ class = "narrow"
+ id = "border"
+ oninput = "forceInteger(this.id)"/>
+ <label id="bordertypeLabel"
+ value="&pixelsPopup.value;" />
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <vbox>
+ <groupbox align="start">
+ <hbox class="groupbox-title">
+ <label id="alignLabel" class="header">&alignment.label;</label>
+ </hbox>
+ <menulist id="alignTypeSelect" class="align-menu">
+ <menupopup>
+ <menuitem class="align-menu menuitem-iconic"
+ value="top"
+ label="&topPopup.value;"/>
+ <menuitem class="align-menu menuitem-iconic"
+ value="middle"
+ label="&centerPopup.value;"/>
+ <menuitem class="align-menu menuitem-iconic"
+ value="bottom"
+ label="&bottomPopup.value;"/>
+ <!-- HTML attribute value is opposite of the button label on purpose -->
+ <menuitem class="align-menu menuitem-iconic"
+ value="right"
+ label="&wrapLeftPopup.value;"/>
+ <menuitem class="align-menu menuitem-iconic"
+ value="left"
+ label="&wrapRightPopup.value;"/>
+ </menupopup>
+ </menulist>
+ </groupbox>
+
+ <groupbox>
+ <hbox class="groupbox-title">
+ <label id="imagemapLabel" class="header">&imagemapBox.label;</label>
+ </hbox>
+ <hbox equalsize="always">
+ <button id="removeImageMap"
+ oncommand="removeImageMap()"
+ accesskey="&removeImageMapButton.accessKey;"
+ label="&removeImageMapButton.label;"
+ flex="1"/>
+ <spacer flex="1"/><!-- remove when we restore Image Map Editor -->
+ </hbox>
+ </groupbox>
+ </vbox>
+ </hbox>