@bobfrankston/rmfmail 1.2.222 → 1.2.224

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/bin/mailx.js +6 -9
  2. package/bin/mailx.js.map +1 -1
  3. package/bin/mailx.ts +6 -9
  4. package/client/android-bootstrap.bundle.js +38 -4
  5. package/client/android-bootstrap.bundle.js.map +2 -2
  6. package/client/app.bundle.js +149 -32
  7. package/client/app.bundle.js.map +2 -2
  8. package/client/components/address-book.js +93 -29
  9. package/client/components/address-book.js.map +1 -1
  10. package/client/components/address-book.ts +103 -24
  11. package/client/components/context-menu.js +76 -0
  12. package/client/components/context-menu.js.map +1 -1
  13. package/client/components/context-menu.ts +77 -0
  14. package/client/compose/compose.bundle.js +276 -116
  15. package/client/compose/compose.bundle.js.map +4 -4
  16. package/client/compose/compose.js +4 -149
  17. package/client/compose/compose.js.map +1 -1
  18. package/client/compose/compose.ts +4 -104
  19. package/client/compose/edit-commands.js +207 -0
  20. package/client/compose/edit-commands.js.map +1 -0
  21. package/client/compose/edit-commands.ts +181 -0
  22. package/client/compose/editor.js +6 -0
  23. package/client/compose/editor.js.map +1 -1
  24. package/client/compose/editor.ts +6 -0
  25. package/client/compose/spellcheck.js +19 -0
  26. package/client/compose/spellcheck.js.map +1 -1
  27. package/client/compose/spellcheck.ts +11 -0
  28. package/client/lib/api-client.js +2 -2
  29. package/client/lib/api-client.js.map +1 -1
  30. package/client/lib/api-client.ts +2 -2
  31. package/client/lib/mailxapi.js +2 -2
  32. package/client/styles/components.css +40 -0
  33. package/package.json +7 -7
  34. package/packages/mailx-imap/index.d.ts.map +1 -1
  35. package/packages/mailx-imap/index.js +32 -4
  36. package/packages/mailx-imap/index.js.map +1 -1
  37. package/packages/mailx-imap/index.ts +34 -5
  38. package/packages/mailx-imap/package-lock.json +2 -2
  39. package/packages/mailx-imap/package.json +1 -1
  40. package/packages/mailx-service/index.d.ts +1 -1
  41. package/packages/mailx-service/index.d.ts.map +1 -1
  42. package/packages/mailx-service/index.js +33 -4
  43. package/packages/mailx-service/index.js.map +1 -1
  44. package/packages/mailx-service/index.ts +28 -4
  45. package/packages/mailx-service/jsonrpc.js +1 -1
  46. package/packages/mailx-service/jsonrpc.js.map +1 -1
  47. package/packages/mailx-service/jsonrpc.ts +1 -1
  48. package/packages/mailx-service/package.json +1 -1
  49. package/packages/mailx-store/db.d.ts +30 -10
  50. package/packages/mailx-store/db.d.ts.map +1 -1
  51. package/packages/mailx-store/db.js +108 -10
  52. package/packages/mailx-store/db.js.map +1 -1
  53. package/packages/mailx-store/db.ts +126 -13
  54. package/packages/mailx-store/package.json +1 -1
  55. package/packages/mailx-store-web/android-bootstrap.d.ts.map +1 -1
  56. package/packages/mailx-store-web/android-bootstrap.js +17 -2
  57. package/packages/mailx-store-web/android-bootstrap.js.map +1 -1
  58. package/packages/mailx-store-web/android-bootstrap.ts +17 -2
  59. package/packages/mailx-store-web/db.d.ts +1 -1
  60. package/packages/mailx-store-web/db.d.ts.map +1 -1
  61. package/packages/mailx-store-web/db.js +17 -2
  62. package/packages/mailx-store-web/db.js.map +1 -1
  63. package/packages/mailx-store-web/db.ts +18 -3
  64. package/packages/mailx-store-web/package.json +1 -1
  65. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-104584 → node_modules.npmglobalize-stash-142920}/.package-lock.json +0 -0
@@ -1303,8 +1303,8 @@ function hasBccHistoryTo(email) {
1303
1303
  function listContacts(query, page = 1, pageSize = 100) {
1304
1304
  return ipc().listContacts(query, page, pageSize);
1305
1305
  }
1306
- function upsertContact(name, email) {
1307
- return ipc().upsertContact(name, email);
1306
+ function upsertContact(name, email, fields) {
1307
+ return ipc().upsertContact(name, email, fields);
1308
1308
  }
1309
1309
  function deleteContact(email) {
1310
1310
  return ipc().deleteContact(email);
@@ -1983,6 +1983,188 @@ var init_spellcheck_core = __esm({
1983
1983
  }
1984
1984
  });
1985
1985
 
1986
+ // client/compose/edit-commands.js
1987
+ var edit_commands_exports = {};
1988
+ __export(edit_commands_exports, {
1989
+ runClipboardCommand: () => runClipboardCommand,
1990
+ runFormatCommand: () => runFormatCommand,
1991
+ standardEditItems: () => standardEditItems
1992
+ });
1993
+ function isTinyEditor(ne) {
1994
+ return !!ne && typeof ne.execCommand === "function" && !!ne.selection;
1995
+ }
1996
+ function expandToWord(ne) {
1997
+ if (isTinyEditor(ne)) {
1998
+ try {
1999
+ if (ne.selection.isCollapsed() && typeof ne.selection.expand === "function") {
2000
+ ne.selection.expand({ type: "word" });
2001
+ }
2002
+ } catch {
2003
+ }
2004
+ return;
2005
+ }
2006
+ if (ne && typeof ne.getSelection === "function" && typeof ne.setSelection === "function") {
2007
+ const sel = ne.getSelection();
2008
+ if (!sel || sel.length > 0)
2009
+ return;
2010
+ const text = ne.getText();
2011
+ let a = sel.index, b = sel.index;
2012
+ while (a > 0 && /\S/.test(text[a - 1]))
2013
+ a--;
2014
+ while (b < text.length && /\S/.test(text[b]))
2015
+ b++;
2016
+ if (b > a)
2017
+ ne.setSelection(a, b - a);
2018
+ }
2019
+ }
2020
+ function escapeHtml(s) {
2021
+ return s.replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c]);
2022
+ }
2023
+ async function runClipboardCommand(ne, id) {
2024
+ const isTiny = isTinyEditor(ne);
2025
+ if (id === "paste") {
2026
+ let html2 = "";
2027
+ let text2 = "";
2028
+ if (navigator.clipboard?.read) {
2029
+ for (const item of await navigator.clipboard.read()) {
2030
+ if (item.types.includes("text/html"))
2031
+ html2 = await (await item.getType("text/html")).text();
2032
+ if (item.types.includes("text/plain"))
2033
+ text2 = await (await item.getType("text/plain")).text();
2034
+ }
2035
+ } else {
2036
+ text2 = await navigator.clipboard.readText();
2037
+ }
2038
+ const content = html2 || (text2 ? escapeHtml(text2).replace(/\r?\n/g, "<br>") : "");
2039
+ if (!content)
2040
+ return;
2041
+ if (isTiny) {
2042
+ ne.execCommand("mceInsertContent", false, content);
2043
+ return;
2044
+ }
2045
+ if (ne?.clipboard?.dangerouslyPasteHTML) {
2046
+ const sel = ne.getSelection(true);
2047
+ ne.clipboard.dangerouslyPasteHTML(sel?.index ?? 0, content);
2048
+ return;
2049
+ }
2050
+ throw new Error("editor doesn't support paste");
2051
+ }
2052
+ expandToWord(ne);
2053
+ let html = "";
2054
+ let text = "";
2055
+ if (isTiny) {
2056
+ html = ne.selection.getContent({ format: "html" });
2057
+ text = ne.selection.getContent({ format: "text" });
2058
+ } else {
2059
+ text = window.getSelection()?.toString() || "";
2060
+ }
2061
+ if (!text && !html)
2062
+ return;
2063
+ if (html && typeof ClipboardItem === "function" && navigator.clipboard?.write) {
2064
+ await navigator.clipboard.write([new ClipboardItem({
2065
+ "text/html": new Blob([html], { type: "text/html" }),
2066
+ "text/plain": new Blob([text], { type: "text/plain" })
2067
+ })]);
2068
+ } else {
2069
+ await navigator.clipboard.writeText(text);
2070
+ }
2071
+ if (id !== "cut")
2072
+ return;
2073
+ if (isTiny) {
2074
+ ne.execCommand("Delete");
2075
+ return;
2076
+ }
2077
+ if (typeof ne?.deleteText === "function") {
2078
+ const sel = ne.getSelection();
2079
+ if (sel?.length)
2080
+ ne.deleteText(sel.index, sel.length);
2081
+ }
2082
+ }
2083
+ function runFormatCommand(ne, id) {
2084
+ if (!ne)
2085
+ return;
2086
+ expandToWord(ne);
2087
+ if (isTinyEditor(ne)) {
2088
+ const cmd = {
2089
+ bold: "Bold",
2090
+ italic: "Italic",
2091
+ underline: "Underline",
2092
+ strike: "Strikethrough",
2093
+ link: "mceLink",
2094
+ clear: "RemoveFormat"
2095
+ };
2096
+ if (cmd[id])
2097
+ ne.execCommand(cmd[id]);
2098
+ return;
2099
+ }
2100
+ if (typeof ne.format !== "function" || typeof ne.getSelection !== "function")
2101
+ return;
2102
+ const sel = ne.getSelection();
2103
+ if (!sel || sel.length === 0)
2104
+ return;
2105
+ const cur = ne.getFormat(sel);
2106
+ switch (id) {
2107
+ case "bold":
2108
+ ne.format("bold", !cur.bold);
2109
+ break;
2110
+ case "italic":
2111
+ ne.format("italic", !cur.italic);
2112
+ break;
2113
+ case "underline":
2114
+ ne.format("underline", !cur.underline);
2115
+ break;
2116
+ case "strike":
2117
+ ne.format("strike", !cur.strike);
2118
+ break;
2119
+ case "clear":
2120
+ ne.removeFormat(sel.index, sel.length);
2121
+ break;
2122
+ // Quill's link dialog lives in its keyboard binding — Ctrl+K.
2123
+ case "link":
2124
+ try {
2125
+ ne.root.dispatchEvent(new KeyboardEvent("keydown", { key: "K", ctrlKey: true, bubbles: true, cancelable: true }));
2126
+ } catch {
2127
+ }
2128
+ break;
2129
+ }
2130
+ }
2131
+ function standardEditItems(getEditor, opts = {}) {
2132
+ const report = opts.onError || ((m) => console.warn(`[edit-commands] ${m}`));
2133
+ const clip = (id) => () => {
2134
+ void runClipboardCommand(getEditor(), id).catch((e) => {
2135
+ const key = id === "cut" ? "Ctrl+X" : id === "copy" ? "Ctrl+C" : "Ctrl+V";
2136
+ report(`${id[0].toUpperCase()}${id.slice(1)} failed: ${e?.message || e}. ${key} still works.`);
2137
+ });
2138
+ };
2139
+ const fmt = (id) => () => runFormatCommand(getEditor(), id);
2140
+ const items = [
2141
+ { label: "", action: () => {
2142
+ }, separator: true },
2143
+ { label: "Cut", action: clip("cut") },
2144
+ { label: "Copy", action: clip("copy") },
2145
+ { label: "Paste", action: clip("paste") },
2146
+ { label: "", action: () => {
2147
+ }, separator: true },
2148
+ { label: "Bold", action: fmt("bold") },
2149
+ { label: "Italic", action: fmt("italic") },
2150
+ { label: "Underline", action: fmt("underline") },
2151
+ { label: "Strikethrough", action: fmt("strike") },
2152
+ { label: "Link\u2026", action: fmt("link") },
2153
+ { label: "Clear formatting", action: fmt("clear") }
2154
+ ];
2155
+ if (opts.showSource) {
2156
+ items.push({ label: "", action: () => {
2157
+ }, separator: true });
2158
+ items.push({ label: "View HTML source\u2026", action: opts.showSource });
2159
+ }
2160
+ return items;
2161
+ }
2162
+ var init_edit_commands = __esm({
2163
+ "client/compose/edit-commands.js"() {
2164
+ "use strict";
2165
+ }
2166
+ });
2167
+
1986
2168
  // client/lib/rmf-tiny.js
1987
2169
  var rmf_tiny_exports = {};
1988
2170
  __export(rmf_tiny_exports, {
@@ -2937,6 +3119,21 @@ function wireSpellcheck(editor2) {
2937
3119
  scheduleScan();
2938
3120
  }
2939
3121
  });
3122
+ items.push(...standardEditItems(() => editor2, {
3123
+ onError: (msg) => {
3124
+ try {
3125
+ editor2.notificationManager?.open({ text: msg, type: "error", timeout: 6e3 });
3126
+ } catch {
3127
+ console.warn(msg);
3128
+ }
3129
+ },
3130
+ showSource: () => {
3131
+ try {
3132
+ editor2.execCommand("mceCodeEditor");
3133
+ } catch {
3134
+ }
3135
+ }
3136
+ }));
2940
3137
  const iframeEl = editor2.iframeElement;
2941
3138
  const rect = iframeEl ? iframeEl.getBoundingClientRect() : { left: 0, top: 0 };
2942
3139
  showSuggestionsMenu(document, rect.left + e.clientX, rect.top + e.clientY, items, [iframeDoc]);
@@ -2947,6 +3144,7 @@ var init_spellcheck = __esm({
2947
3144
  "client/compose/spellcheck.js"() {
2948
3145
  "use strict";
2949
3146
  init_spellcheck_core();
3147
+ init_edit_commands();
2950
3148
  SCAN_DEBOUNCE_MS = 600;
2951
3149
  WAVE = `url("data:image/svg+xml,${encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="6" height="3"><path d="M0 2 Q1.5 0 3 2 T6 2" stroke="#d33" fill="none" stroke-width="1"/></svg>')}")`;
2952
3150
  OVERLAY_ID = "mailx-spell-overlay";
@@ -3941,6 +4139,8 @@ function createQuillEditor(container2) {
3941
4139
  label: "Ignore (this session)",
3942
4140
  action: () => _spellSp.add(word)
3943
4141
  });
4142
+ const { standardEditItems: standardEditItems2 } = await Promise.resolve().then(() => (init_edit_commands(), edit_commands_exports));
4143
+ items.push(...standardEditItems2(() => q));
3944
4144
  core.showSuggestionsMenu(document, e.clientX, e.clientY, items);
3945
4145
  } catch (err) {
3946
4146
  console.warn("[spellcheck] right-click handler error:", err?.message || err);
@@ -4417,12 +4617,21 @@ async function createTinyMceEditor2(container2, opts = {}) {
4417
4617
 
4418
4618
  // client/compose/compose.ts
4419
4619
  init_api_client();
4620
+ init_edit_commands();
4420
4621
 
4421
4622
  // client/components/context-menu.js
4422
4623
  var activeMenu = null;
4423
4624
  var dismissListener = null;
4424
4625
  var escapeListener = null;
4626
+ var activeSubmenu = null;
4627
+ function closeSubmenu() {
4628
+ if (activeSubmenu) {
4629
+ activeSubmenu.remove();
4630
+ activeSubmenu = null;
4631
+ }
4632
+ }
4425
4633
  function closeContextMenu() {
4634
+ closeSubmenu();
4426
4635
  if (activeMenu) {
4427
4636
  activeMenu.remove();
4428
4637
  activeMenu = null;
@@ -4436,6 +4645,45 @@ function closeContextMenu() {
4436
4645
  escapeListener = null;
4437
4646
  }
4438
4647
  }
4648
+ function openSubmenu(parentRow, items) {
4649
+ closeSubmenu();
4650
+ const sub = document.createElement("div");
4651
+ sub.className = "ctx-menu";
4652
+ for (const item of items) {
4653
+ if (item.separator) {
4654
+ const sep = document.createElement("div");
4655
+ sep.className = "ctx-sep";
4656
+ sub.appendChild(sep);
4657
+ continue;
4658
+ }
4659
+ const el = document.createElement("div");
4660
+ el.className = "ctx-item" + (item.disabled ? " ctx-disabled" : "");
4661
+ el.textContent = item.label;
4662
+ if (item.tooltip)
4663
+ el.title = item.tooltip;
4664
+ if (!item.disabled) {
4665
+ el.addEventListener("click", () => {
4666
+ closeContextMenu();
4667
+ item.action();
4668
+ });
4669
+ }
4670
+ sub.appendChild(el);
4671
+ }
4672
+ document.body.appendChild(sub);
4673
+ const anchor = parentRow.getBoundingClientRect();
4674
+ const vw = window.visualViewport?.width ?? window.innerWidth;
4675
+ const vh = window.visualViewport?.height ?? window.innerHeight;
4676
+ const rect = sub.getBoundingClientRect();
4677
+ let left = anchor.right - 2;
4678
+ if (left + rect.width > vw)
4679
+ left = anchor.left - rect.width + 2;
4680
+ let top = anchor.top;
4681
+ if (top + rect.height > vh)
4682
+ top = vh - rect.height - 4;
4683
+ sub.style.left = `${Math.max(4, left)}px`;
4684
+ sub.style.top = `${Math.max(4, top)}px`;
4685
+ activeSubmenu = sub;
4686
+ }
4439
4687
  function showContextMenu(x, y, items) {
4440
4688
  closeContextMenu();
4441
4689
  const menu = document.createElement("div");
@@ -4454,6 +4702,24 @@ function showContextMenu(x, y, items) {
4454
4702
  el.textContent = item.label;
4455
4703
  if (item.tooltip)
4456
4704
  el.title = item.tooltip;
4705
+ if (item.submenu && item.submenu.length > 0 && !item.disabled) {
4706
+ el.style.display = "flex";
4707
+ el.style.justifyContent = "space-between";
4708
+ el.style.gap = "12px";
4709
+ const caret = document.createElement("span");
4710
+ caret.textContent = "\u25B8";
4711
+ caret.style.opacity = "0.6";
4712
+ el.appendChild(caret);
4713
+ const open = () => openSubmenu(el, item.submenu);
4714
+ el.addEventListener("mouseenter", open);
4715
+ el.addEventListener("click", (e) => {
4716
+ e.stopPropagation();
4717
+ open();
4718
+ });
4719
+ menu.appendChild(el);
4720
+ continue;
4721
+ }
4722
+ el.addEventListener("mouseenter", closeSubmenu);
4457
4723
  if (!item.disabled) {
4458
4724
  el.addEventListener("click", () => {
4459
4725
  closeContextMenu();
@@ -4485,6 +4751,8 @@ function showContextMenu(x, y, items) {
4485
4751
  activeMenu = menu;
4486
4752
  requestAnimationFrame(() => {
4487
4753
  dismissListener = (e) => {
4754
+ if (activeSubmenu?.contains(e.target))
4755
+ return;
4488
4756
  if (activeMenu && !activeMenu.contains(e.target)) {
4489
4757
  closeContextMenu();
4490
4758
  }
@@ -5413,7 +5681,7 @@ function applyInit(init) {
5413
5681
  if (init.mode !== "draft" && !init.draftUid) {
5414
5682
  let sigHtml = "";
5415
5683
  if (acct?.sig?.text) {
5416
- sigHtml = acct.sig.html ? acct.sig.text : escapeHtml(acct.sig.text).replace(/\n/g, "<br>");
5684
+ sigHtml = acct.sig.html ? acct.sig.text : escapeHtml2(acct.sig.text).replace(/\n/g, "<br>");
5417
5685
  } else if (acct?.signature) {
5418
5686
  sigHtml = acct.signature;
5419
5687
  }
@@ -6100,60 +6368,6 @@ async function popoutHandoff() {
6100
6368
  window.addEventListener("compose-popout", () => {
6101
6369
  void popoutHandoff();
6102
6370
  });
6103
- async function runClipboardCommand(id) {
6104
- const ne = editor?.nativeEditor;
6105
- const isTiny = !!ne && typeof ne.execCommand === "function" && !!ne.selection;
6106
- const label = id === "cut" ? "Cut" : id === "copy" ? "Copy" : "Paste";
6107
- const shortcut = id === "cut" ? "Ctrl+X" : id === "copy" ? "Ctrl+C" : "Ctrl+V";
6108
- try {
6109
- if (id === "paste") {
6110
- let html2 = "";
6111
- let text2 = "";
6112
- if (navigator.clipboard?.read) {
6113
- for (const item of await navigator.clipboard.read()) {
6114
- if (item.types.includes("text/html")) html2 = await (await item.getType("text/html")).text();
6115
- if (item.types.includes("text/plain")) text2 = await (await item.getType("text/plain")).text();
6116
- }
6117
- } else {
6118
- text2 = await navigator.clipboard.readText();
6119
- }
6120
- const content = html2 || (text2 ? text2.replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c]).replace(/\r?\n/g, "<br>") : "");
6121
- if (!content) return;
6122
- if (isTiny) ne.execCommand("mceInsertContent", false, content);
6123
- else if (ne && typeof ne.getSelection === "function") {
6124
- const sel = ne.getSelection(true);
6125
- ne.clipboard?.dangerouslyPasteHTML?.(sel?.index ?? 0, content);
6126
- } else throw new Error("no editor");
6127
- return;
6128
- }
6129
- if (isTiny) {
6130
- try {
6131
- if (ne.selection.isCollapsed() && typeof ne.selection.expand === "function") ne.selection.expand({ type: "word" });
6132
- } catch {
6133
- }
6134
- }
6135
- const html = isTiny ? ne.selection.getContent({ format: "html" }) : "";
6136
- const text = isTiny ? ne.selection.getContent({ format: "text" }) : window.getSelection()?.toString() || "";
6137
- if (!text && !html) return;
6138
- if (html && typeof ClipboardItem === "function" && navigator.clipboard?.write) {
6139
- await navigator.clipboard.write([new ClipboardItem({
6140
- "text/html": new Blob([html], { type: "text/html" }),
6141
- "text/plain": new Blob([text], { type: "text/plain" })
6142
- })]);
6143
- } else {
6144
- await navigator.clipboard.writeText(text);
6145
- }
6146
- if (id === "cut") {
6147
- if (isTiny) ne.execCommand("Delete");
6148
- else if (ne && typeof ne.deleteText === "function") {
6149
- const sel = ne.getSelection();
6150
- if (sel?.length) ne.deleteText(sel.index, sel.length);
6151
- }
6152
- }
6153
- } catch (e) {
6154
- showDraftStatus(`${label} failed: ${e?.message || e}. ${shortcut} still works.`, true);
6155
- }
6156
- }
6157
6371
  window.__msgerContextCommand = (id) => {
6158
6372
  if (id === "source") {
6159
6373
  try {
@@ -6162,61 +6376,7 @@ window.__msgerContextCommand = (id) => {
6162
6376
  }
6163
6377
  return;
6164
6378
  }
6165
- if (id === "cut" || id === "copy" || id === "paste") {
6166
- void runClipboardCommand(id);
6167
- return;
6168
- }
6169
- const ne = editor?.nativeEditor;
6170
- if (!ne) return;
6171
- const isTiny = typeof ne.execCommand === "function" && ne.selection;
6172
- if (isTiny) {
6173
- try {
6174
- if (ne.selection.isCollapsed() && typeof ne.selection.expand === "function") ne.selection.expand({ type: "word" });
6175
- } catch {
6176
- }
6177
- const cmd = { bold: "Bold", italic: "Italic", underline: "Underline", strike: "Strikethrough", link: "mceLink", clear: "RemoveFormat" };
6178
- if (cmd[id]) ne.execCommand(cmd[id]);
6179
- return;
6180
- }
6181
- if (typeof ne.format === "function" && typeof ne.getSelection === "function") {
6182
- let sel = ne.getSelection();
6183
- if (sel && sel.length === 0) {
6184
- const text = ne.getText();
6185
- let a = sel.index, b = sel.index;
6186
- while (a > 0 && /S/.test(text[a - 1])) a--;
6187
- while (b < text.length && /S/.test(text[b])) b++;
6188
- if (b > a) {
6189
- ne.setSelection(a, b - a);
6190
- sel = { index: a, length: b - a };
6191
- }
6192
- }
6193
- if (!sel || sel.length === 0) return;
6194
- const cur = ne.getFormat(sel);
6195
- switch (id) {
6196
- case "bold":
6197
- ne.format("bold", !cur.bold);
6198
- break;
6199
- case "italic":
6200
- ne.format("italic", !cur.italic);
6201
- break;
6202
- case "underline":
6203
- ne.format("underline", !cur.underline);
6204
- break;
6205
- case "strike":
6206
- ne.format("strike", !cur.strike);
6207
- break;
6208
- case "clear":
6209
- ne.removeFormat(sel.index, sel.length);
6210
- break;
6211
- // link: Quill's dialog lives in its keyboard binding — Ctrl+K.
6212
- case "link":
6213
- try {
6214
- ne.root.dispatchEvent(new KeyboardEvent("keydown", { key: "K", ctrlKey: true, bubbles: true, cancelable: true }));
6215
- } catch {
6216
- }
6217
- break;
6218
- }
6219
- }
6379
+ runFormatCommand(editor?.nativeEditor, id);
6220
6380
  };
6221
6381
  var ccRow = document.getElementById("compose-cc-row");
6222
6382
  var bccRow = document.getElementById("compose-bcc-row");
@@ -6249,7 +6409,7 @@ function renderAttachmentChips() {
6249
6409
  const a = attachments[i];
6250
6410
  const chip = document.createElement("span");
6251
6411
  chip.className = "compose-att-chip";
6252
- chip.innerHTML = `\u{1F4CE} ${escapeHtml(a.filename)} (${formatSize(a.size)}) `;
6412
+ chip.innerHTML = `\u{1F4CE} ${escapeHtml2(a.filename)} (${formatSize(a.size)}) `;
6253
6413
  const rm = document.createElement("button");
6254
6414
  rm.type = "button";
6255
6415
  rm.title = "Remove attachment";
@@ -6262,7 +6422,7 @@ function renderAttachmentChips() {
6262
6422
  attEl.appendChild(chip);
6263
6423
  }
6264
6424
  }
6265
- function escapeHtml(s) {
6425
+ function escapeHtml2(s) {
6266
6426
  return s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
6267
6427
  }
6268
6428
  function formatSize(n) {
@@ -6404,9 +6564,9 @@ function showExternalEditHint(editorLabel) {
6404
6564
  const panel = document.createElement("div");
6405
6565
  panel.style.cssText = "background:var(--color-bg, #fff);color:var(--color-text, #000);border:1px solid var(--color-border, #ccc);border-radius:8px;padding:18px 22px;max-width:480px;box-shadow:0 8px 32px rgba(0,0,0,0.4);font:14px/1.5 system-ui;";
6406
6566
  panel.innerHTML = `
6407
- <div style="font-weight:600;font-size:16px;margin-bottom:10px;">Editing in ${escapeHtml(editorLabel)}</div>
6567
+ <div style="font-weight:600;font-size:16px;margin-bottom:10px;">Editing in ${escapeHtml2(editorLabel)}</div>
6408
6568
  <ol style="margin:0 0 12px 18px;padding:0;">
6409
- <li>Edit your message in <b>${escapeHtml(editorLabel)}</b>.</li>
6569
+ <li>Edit your message in <b>${escapeHtml2(editorLabel)}</b>.</li>
6410
6570
  <li>Save (<b>Ctrl+S</b>). Choose <b>"Web Page, Filtered (.htm)"</b> if asked for a format \u2014 keep the same filename.</li>
6411
6571
  <li>Switch back to this window (<b>Alt+Tab</b>). The body will reload here.</li>
6412
6572
  <li>Click <b>Send</b> in this window when ready.</li>