@bobfrankston/rmfmail 1.2.274 → 1.2.277

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 (112) hide show
  1. package/.commitmsg +31 -0
  2. package/bin/build-tinymce.js +27 -0
  3. package/client/android-bootstrap.bundle.js +169 -0
  4. package/client/android-bootstrap.bundle.js.map +2 -2
  5. package/client/app.bundle.js +231 -23
  6. package/client/app.bundle.js.map +4 -4
  7. package/client/app.js +81 -1
  8. package/client/app.js.map +1 -1
  9. package/client/app.ts +75 -1
  10. package/client/components/invite-card.js +107 -0
  11. package/client/components/invite-card.js.map +1 -0
  12. package/client/components/invite-card.ts +119 -0
  13. package/client/components/message-viewer.js +42 -17
  14. package/client/components/message-viewer.js.map +1 -1
  15. package/client/components/message-viewer.ts +42 -16
  16. package/client/compose/compose.bundle.js +90 -2
  17. package/client/compose/compose.bundle.js.map +4 -4
  18. package/client/compose/compose.js +26 -1
  19. package/client/compose/compose.js.map +1 -1
  20. package/client/compose/compose.ts +22 -1
  21. package/client/compose/editor.js +12 -1
  22. package/client/compose/editor.js.map +1 -1
  23. package/client/compose/editor.ts +17 -2
  24. package/client/index.html +1 -0
  25. package/client/lib/rmf-tiny.js +3 -0
  26. package/client/lib/tinymce/langs/ar-SA.js +440 -0
  27. package/client/lib/tinymce/langs/ar.js +441 -0
  28. package/client/lib/tinymce/langs/az.js +440 -0
  29. package/client/lib/tinymce/langs/be.js +440 -0
  30. package/client/lib/tinymce/langs/bg-BG.js +440 -0
  31. package/client/lib/tinymce/langs/bn-BD.js +440 -0
  32. package/client/lib/tinymce/langs/bs.js +440 -0
  33. package/client/lib/tinymce/langs/ca.js +440 -0
  34. package/client/lib/tinymce/langs/cs.js +440 -0
  35. package/client/lib/tinymce/langs/cy.js +440 -0
  36. package/client/lib/tinymce/langs/da.js +440 -0
  37. package/client/lib/tinymce/langs/de.js +440 -0
  38. package/client/lib/tinymce/langs/el.js +440 -0
  39. package/client/lib/tinymce/langs/eo.js +440 -0
  40. package/client/lib/tinymce/langs/es-MX.js +440 -0
  41. package/client/lib/tinymce/langs/es.js +440 -0
  42. package/client/lib/tinymce/langs/et.js +440 -0
  43. package/client/lib/tinymce/langs/eu.js +440 -0
  44. package/client/lib/tinymce/langs/fa.js +441 -0
  45. package/client/lib/tinymce/langs/fi.js +440 -0
  46. package/client/lib/tinymce/langs/fr-FR.js +440 -0
  47. package/client/lib/tinymce/langs/gl.js +440 -0
  48. package/client/lib/tinymce/langs/he-IL.js +440 -0
  49. package/client/lib/tinymce/langs/hr.js +440 -0
  50. package/client/lib/tinymce/langs/hu-HU.js +440 -0
  51. package/client/lib/tinymce/langs/hy.js +440 -0
  52. package/client/lib/tinymce/langs/id.js +440 -0
  53. package/client/lib/tinymce/langs/index.json +63 -0
  54. package/client/lib/tinymce/langs/is-IS.js +440 -0
  55. package/client/lib/tinymce/langs/it.js +440 -0
  56. package/client/lib/tinymce/langs/ja.js +440 -0
  57. package/client/lib/tinymce/langs/ka-GE.js +440 -0
  58. package/client/lib/tinymce/langs/kab.js +440 -0
  59. package/client/lib/tinymce/langs/kk.js +440 -0
  60. package/client/lib/tinymce/langs/ko-KR.js +440 -0
  61. package/client/lib/tinymce/langs/lt.js +440 -0
  62. package/client/lib/tinymce/langs/lv.js +440 -0
  63. package/client/lib/tinymce/langs/nb-NO.js +440 -0
  64. package/client/lib/tinymce/langs/ne.js +440 -0
  65. package/client/lib/tinymce/langs/nl-BE.js +440 -0
  66. package/client/lib/tinymce/langs/nl.js +440 -0
  67. package/client/lib/tinymce/langs/oc.js +440 -0
  68. package/client/lib/tinymce/langs/pl.js +440 -0
  69. package/client/lib/tinymce/langs/pt-BR.js +440 -0
  70. package/client/lib/tinymce/langs/pt-PT.js +440 -0
  71. package/client/lib/tinymce/langs/ro.js +440 -0
  72. package/client/lib/tinymce/langs/ru.js +440 -0
  73. package/client/lib/tinymce/langs/sk.js +440 -0
  74. package/client/lib/tinymce/langs/sl-SI.js +440 -0
  75. package/client/lib/tinymce/langs/sr.js +440 -0
  76. package/client/lib/tinymce/langs/sv-SE.js +440 -0
  77. package/client/lib/tinymce/langs/ta.js +440 -0
  78. package/client/lib/tinymce/langs/tg.js +440 -0
  79. package/client/lib/tinymce/langs/th-TH.js +440 -0
  80. package/client/lib/tinymce/langs/tr.js +440 -0
  81. package/client/lib/tinymce/langs/ug.js +440 -0
  82. package/client/lib/tinymce/langs/uk.js +440 -0
  83. package/client/lib/tinymce/langs/vi.js +440 -0
  84. package/client/lib/tinymce/langs/zh-CN.js +440 -0
  85. package/client/lib/tinymce/langs/zh-HK.js +440 -0
  86. package/client/lib/tinymce/langs/zh-MO.js +440 -0
  87. package/client/lib/tinymce/langs/zh-TW.js +440 -0
  88. package/client/lib/tinymce-langs.js +96 -0
  89. package/client/lib/tinymce-langs.js.map +1 -0
  90. package/client/lib/tinymce-langs.ts +99 -0
  91. package/client/package.json +1 -1
  92. package/client/styles/components.css +48 -0
  93. package/docs/editor.md +29 -3
  94. package/docs/preferences.md +2 -1
  95. package/npmchanges.md +29 -0
  96. package/package.json +18 -15
  97. package/packages/mailx-imap/package-lock.json +2 -2
  98. package/packages/mailx-imap/package.json +2 -2
  99. package/packages/mailx-settings/package.json +1 -1
  100. package/packages/mailx-store/package.json +1 -1
  101. package/packages/mailx-store/store.d.ts +5 -0
  102. package/packages/mailx-store/store.d.ts.map +1 -1
  103. package/packages/mailx-store/store.js +21 -0
  104. package/packages/mailx-store/store.js.map +1 -1
  105. package/packages/mailx-store/store.ts +27 -0
  106. package/packages/mailx-types/index.d.ts +56 -0
  107. package/packages/mailx-types/index.d.ts.map +1 -1
  108. package/packages/mailx-types/index.js +187 -0
  109. package/packages/mailx-types/index.js.map +1 -1
  110. package/packages/mailx-types/index.ts +192 -0
  111. package/packages/mailx-types/package.json +1 -1
  112. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-31304 → node_modules.npmglobalize-stash-48352}/.package-lock.json +0 -0
@@ -1467,6 +1467,11 @@ function formatSender(addr) {
1467
1467
  return { text: address, claimed: null };
1468
1468
  return { text: `${name} <${address}>`, claimed: displayNameClaimsOtherAddress(name, address) };
1469
1469
  }
1470
+ function inviteHasGoogleRsvp(bodyHtml) {
1471
+ if (!bodyHtml)
1472
+ return false;
1473
+ return /calendar\.google\.com\/calendar\/event\?action=RESPOND/i.test(bodyHtml);
1474
+ }
1470
1475
  var _SEEN, _FLAGGED, _DRAFT, seenOf, flaggedOf, draftOf, setSeen, setFlagged;
1471
1476
  var init_mailx_types = __esm({
1472
1477
  "packages/mailx-types/index.js"() {
@@ -1488,6 +1493,100 @@ var init_mailx_types = __esm({
1488
1493
  }
1489
1494
  });
1490
1495
 
1496
+ // client/components/invite-card.js
1497
+ function formatWhen(inv) {
1498
+ if (!inv.start)
1499
+ return "";
1500
+ if (inv.allDay) {
1501
+ try {
1502
+ const d = /* @__PURE__ */ new Date(inv.start + "T00:00:00");
1503
+ return d.toLocaleDateString(void 0, { weekday: "long", year: "numeric", month: "long", day: "numeric" }) + " (all day)";
1504
+ } catch {
1505
+ return inv.start + " (all day)";
1506
+ }
1507
+ }
1508
+ const instant = (() => {
1509
+ if (/Z$/.test(inv.start))
1510
+ return new Date(inv.start);
1511
+ if (!inv.tzid)
1512
+ return new Date(inv.start);
1513
+ try {
1514
+ const guess = /* @__PURE__ */ new Date(inv.start + "Z");
1515
+ const asZone = new Date(guess.toLocaleString("en-US", { timeZone: inv.tzid }));
1516
+ const asUtc = new Date(guess.toLocaleString("en-US", { timeZone: "UTC" }));
1517
+ return new Date(guess.getTime() + (asUtc.getTime() - asZone.getTime()));
1518
+ } catch {
1519
+ return new Date(inv.start);
1520
+ }
1521
+ })();
1522
+ if (isNaN(instant.getTime()))
1523
+ return inv.start;
1524
+ const localZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
1525
+ const opts = {
1526
+ weekday: "short",
1527
+ month: "short",
1528
+ day: "numeric",
1529
+ hour: "numeric",
1530
+ minute: "2-digit"
1531
+ };
1532
+ let out = instant.toLocaleString(void 0, opts);
1533
+ if (inv.end && !/Z$/.test(inv.end)) {
1534
+ try {
1535
+ const endInstant = new Date(instant.getTime() + ((/* @__PURE__ */ new Date(inv.end + "Z")).getTime() - (/* @__PURE__ */ new Date(inv.start + "Z")).getTime()));
1536
+ out += " \u2013 " + endInstant.toLocaleString(void 0, { hour: "numeric", minute: "2-digit" });
1537
+ } catch {
1538
+ }
1539
+ }
1540
+ if (inv.tzid && inv.tzid !== localZone) {
1541
+ out += ` (sent as ${inv.start.slice(11, 16)} ${inv.tzid})`;
1542
+ }
1543
+ return out;
1544
+ }
1545
+ function buildInviteCard(inv, bodyHtml) {
1546
+ if (!inv || !inv.summary && !inv.start)
1547
+ return null;
1548
+ const card = document.createElement("div");
1549
+ card.className = "mv-invite";
1550
+ if (inv.method === "CANCEL")
1551
+ card.classList.add("mv-invite-cancelled");
1552
+ const when = formatWhen(inv);
1553
+ const isMeetLink = /^https?:/;
1554
+ const googleHandles = inv.fromGoogle && inviteHasGoogleRsvp(bodyHtml);
1555
+ const rows = [];
1556
+ if (when)
1557
+ rows.push(`<div class="mv-invite-when">${esc(when)}</div>`);
1558
+ if (inv.location) {
1559
+ rows.push(isMeetLink ? `<div class="mv-invite-row"><span class="mv-invite-label">Where</span><a href="${esc(inv.location)}" target="_blank" rel="noopener noreferrer">${esc(inv.location)}</a></div>` : `<div class="mv-invite-row"><span class="mv-invite-label">Where</span>${esc(inv.location)}</div>`);
1560
+ }
1561
+ if (inv.organizer.email) {
1562
+ rows.push(`<div class="mv-invite-row"><span class="mv-invite-label">Organizer</span>${esc(inv.organizer.name)}${inv.organizer.name !== inv.organizer.email ? " &lt;" + esc(inv.organizer.email) + "&gt;" : ""}</div>`);
1563
+ }
1564
+ if (inv.attendees.length) {
1565
+ const who = inv.attendees.map((a) => `${esc(a.name || a.email)} <span class="mv-invite-partstat">(${esc(PARTSTAT_LABEL[a.status] || a.status.toLowerCase())})</span>`).join(", ");
1566
+ rows.push(`<div class="mv-invite-row"><span class="mv-invite-label">Guests</span>${who}</div>`);
1567
+ }
1568
+ if (inv.rrule)
1569
+ rows.push(`<div class="mv-invite-row"><span class="mv-invite-label">Repeats</span><code>${esc(inv.rrule)}</code></div>`);
1570
+ const badge = inv.method === "CANCEL" ? "Cancelled" : inv.method === "REPLY" ? "Reply" : inv.status === "TENTATIVE" ? "Tentative" : "Invitation";
1571
+ card.innerHTML = `<div class="mv-invite-head"><span class="mv-invite-badge">${esc(badge)}</span><span class="mv-invite-summary">${esc(inv.summary || "(no title)")}</span></div>` + rows.join("") + (googleHandles ? `<div class="mv-invite-note">Respond with the Yes / No / Maybe buttons in the message below \u2014 they go straight to Google Calendar.</div>` : "");
1572
+ return card;
1573
+ }
1574
+ var PARTSTAT_LABEL, esc;
1575
+ var init_invite_card = __esm({
1576
+ "client/components/invite-card.js"() {
1577
+ "use strict";
1578
+ init_mailx_types();
1579
+ PARTSTAT_LABEL = {
1580
+ "ACCEPTED": "accepted",
1581
+ "DECLINED": "declined",
1582
+ "TENTATIVE": "maybe",
1583
+ "NEEDS-ACTION": "no reply yet",
1584
+ "DELEGATED": "delegated"
1585
+ };
1586
+ esc = (s) => (s || "").replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
1587
+ }
1588
+ });
1589
+
1491
1590
  // client/components/address-book.js
1492
1591
  var address_book_exports = {};
1493
1592
  __export(address_book_exports, {
@@ -2361,19 +2460,6 @@ function installPreviewControls(iframe) {
2361
2460
  }
2362
2461
  if (e.ctrlKey && !e.altKey && !e.metaKey && (e.key === "a" || e.key === "A"))
2363
2462
  return;
2364
- const synth = new KeyboardEvent("keydown", {
2365
- key: e.key,
2366
- code: e.code,
2367
- ctrlKey: e.ctrlKey,
2368
- shiftKey: e.shiftKey,
2369
- altKey: e.altKey,
2370
- metaKey: e.metaKey,
2371
- bubbles: true,
2372
- cancelable: true
2373
- });
2374
- const allowDefault = document.dispatchEvent(synth);
2375
- if (!allowDefault)
2376
- e.preventDefault();
2377
2463
  });
2378
2464
  doc.addEventListener("wheel", (e) => {
2379
2465
  if (!e.ctrlKey)
@@ -2410,6 +2496,7 @@ function clearViewer() {
2410
2496
  headerEl.hidden = true;
2411
2497
  if (attEl)
2412
2498
  attEl.hidden = true;
2499
+ document.getElementById("mv-invite")?.remove();
2413
2500
  setTimeout(() => {
2414
2501
  try {
2415
2502
  const stillPlaceholder = !!document.querySelector("#mv-body .mv-empty");
@@ -2669,6 +2756,16 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
2669
2756
  fromEl.textContent = formatAddr(msg.from);
2670
2757
  setRecipientLine(toEl, msg.to, msg.cc, msg.deliveredTo);
2671
2758
  headerEl.querySelector(".mv-subject").textContent = msg.subject;
2759
+ document.getElementById("mv-invite")?.remove();
2760
+ try {
2761
+ const card = buildInviteCard(msg.invite, String(msg.bodyHtml || ""));
2762
+ if (card) {
2763
+ card.id = "mv-invite";
2764
+ headerEl.insertAdjacentElement("afterend", card);
2765
+ }
2766
+ } catch (e) {
2767
+ console.warn("[viewer] invite card failed:", e?.message || e);
2768
+ }
2672
2769
  document.dispatchEvent(new CustomEvent("mailx-message-shown", { detail: { accountId } }));
2673
2770
  for (const el of [fromEl, toEl]) {
2674
2771
  el.addEventListener("contextmenu", (e) => {
@@ -3980,11 +4077,11 @@ function buildPrintableDoc(m) {
3980
4077
  if (!m)
3981
4078
  return null;
3982
4079
  const fmt = (a) => a?.name ? `${a.name} <${a.address || ""}>` : a?.address || "";
3983
- const esc = escapeHtmlLocal;
3984
- const row = (label, value) => value ? `<div><strong>${label}:</strong> ${esc(value)}</div>` : "";
4080
+ const esc2 = escapeHtmlLocal;
4081
+ const row = (label, value) => value ? `<div><strong>${label}:</strong> ${esc2(value)}</div>` : "";
3985
4082
  const headerHtml = `<div style="font-family:system-ui,Segoe UI,sans-serif;font-size:11pt;border-bottom:1px solid #999;padding-bottom:8px;margin-bottom:14px;line-height:1.5;">` + row("From", fmt(m.from || {})) + row("To", (m.to || []).map(fmt).join(", ")) + (m.cc?.length ? row("Cc", m.cc.map(fmt).join(", ")) : "") + row("Subject", m.subject || "") + row("Date", m.date ? new Date(m.date).toLocaleString() : "") + `</div>`;
3986
- const bodyHtml = m.bodyHtml ? m.bodyHtml : `<pre style="white-space:pre-wrap;word-break:break-word;font-family:system-ui,sans-serif;">${esc(m.bodyText || "")}</pre>`;
3987
- return `<!DOCTYPE html><html><head><meta charset="utf-8"><title>${esc(m.subject || "Message")}</title><style>@media print{body{margin:0;}}body{margin:16px;}img{max-width:100%;}</style></head><body>${headerHtml}${bodyHtml}</body></html>`;
4083
+ const bodyHtml = m.bodyHtml ? m.bodyHtml : `<pre style="white-space:pre-wrap;word-break:break-word;font-family:system-ui,sans-serif;">${esc2(m.bodyText || "")}</pre>`;
4084
+ return `<!DOCTYPE html><html><head><meta charset="utf-8"><title>${esc2(m.subject || "Message")}</title><style>@media print{body{margin:0;}}body{margin:16px;}img{max-width:100%;}</style></head><body>${headerHtml}${bodyHtml}</body></html>`;
3988
4085
  }
3989
4086
  function printCurrentMessage() {
3990
4087
  if (!currentMessage)
@@ -4197,6 +4294,7 @@ var init_message_viewer = __esm({
4197
4294
  "use strict";
4198
4295
  init_api_client();
4199
4296
  init_approved_senders();
4297
+ init_invite_card();
4200
4298
  init_context_menu();
4201
4299
  init_message_state();
4202
4300
  init_message_list();
@@ -7386,7 +7484,7 @@ async function collectDueAlarms(now) {
7386
7484
  }
7387
7485
  return items;
7388
7486
  }
7389
- function formatWhen(ms) {
7487
+ function formatWhen2(ms) {
7390
7488
  if (!ms)
7391
7489
  return "";
7392
7490
  const d = new Date(ms);
@@ -7509,7 +7607,7 @@ async function firePopupForItem(item) {
7509
7607
  .actions button.action[data-btn="Delete"]:hover { background: #800; }
7510
7608
  </style></head><body>
7511
7609
  <div class="title"><span class="icon">${icon}</span>${escapeHtml7(item.title)}</div>
7512
- <div class="when">${escapeHtml7(formatWhen(item.whenMs))}</div>
7610
+ <div class="when">${escapeHtml7(formatWhen2(item.whenMs))}</div>
7513
7611
  <div class="kind">${kindLabel}</div>
7514
7612
  <div class="row">
7515
7613
  <span class="row-label">Snooze:</span>
@@ -7839,6 +7937,67 @@ whatever it finds.</p>
7839
7937
  }
7840
7938
  });
7841
7939
 
7940
+ // client/lib/tinymce-langs.js
7941
+ var tinymce_langs_exports = {};
7942
+ __export(tinymce_langs_exports, {
7943
+ LANGUAGE_AUTO: () => LANGUAGE_AUTO,
7944
+ LANGUAGE_ENGLISH: () => LANGUAGE_ENGLISH,
7945
+ availableLanguages: () => availableLanguages,
7946
+ langManifestUrl: () => langManifestUrl,
7947
+ languageLabel: () => languageLabel,
7948
+ resolveLanguage: () => resolveLanguage
7949
+ });
7950
+ function langManifestUrl(tinymceScriptUrl) {
7951
+ return new URL("langs/index.json", new URL(tinymceScriptUrl, location.href)).href;
7952
+ }
7953
+ async function availableLanguages(tinymceScriptUrl) {
7954
+ try {
7955
+ const r = await fetch(langManifestUrl(tinymceScriptUrl));
7956
+ if (!r.ok)
7957
+ return [];
7958
+ const list = await r.json();
7959
+ return Array.isArray(list) ? list.filter((x) => typeof x === "string") : [];
7960
+ } catch {
7961
+ return [];
7962
+ }
7963
+ }
7964
+ function resolveLanguage(setting, available, preferred = navigator.languages || [navigator.language]) {
7965
+ const byLower = new Map(available.map((code) => [code.toLowerCase(), code]));
7966
+ if (setting && setting !== LANGUAGE_AUTO) {
7967
+ if (setting === LANGUAGE_ENGLISH)
7968
+ return LANGUAGE_ENGLISH;
7969
+ return byLower.get(setting.toLowerCase()) || LANGUAGE_ENGLISH;
7970
+ }
7971
+ for (const tag of preferred) {
7972
+ if (!tag)
7973
+ continue;
7974
+ const lower = tag.toLowerCase();
7975
+ const base = lower.split("-")[0];
7976
+ if (base === LANGUAGE_ENGLISH)
7977
+ return LANGUAGE_ENGLISH;
7978
+ const hit = byLower.get(lower) || byLower.get(base) || available.find((code) => code.toLowerCase().split("-")[0] === base);
7979
+ if (hit)
7980
+ return hit;
7981
+ }
7982
+ return LANGUAGE_ENGLISH;
7983
+ }
7984
+ function languageLabel(code) {
7985
+ try {
7986
+ const names = new Intl.DisplayNames(navigator.languages || void 0, { type: "language" });
7987
+ return names.of(code) || code;
7988
+ } catch {
7989
+ return code;
7990
+ }
7991
+ }
7992
+ var LANGUAGE_AUTO, LANGUAGE_ENGLISH;
7993
+ var init_tinymce_langs = __esm({
7994
+ "client/lib/tinymce-langs.js"() {
7995
+ "use strict";
7996
+ LANGUAGE_AUTO = "auto";
7997
+ LANGUAGE_ENGLISH = "en";
7998
+ }
7999
+ });
8000
+
7842
8001
  // client/components/folder-tree.js
7843
8002
  init_api_client();
7844
8003
  init_context_menu();
@@ -9261,6 +9420,7 @@ window.__btick && window.__btick("app.ts module body executing");
9261
9420
  var baseTitle = APP_NAME;
9262
9421
  var lastSeenCount = 0;
9263
9422
  var badgeCount = 0;
9423
+ var lastOverlayCount = -1;
9264
9424
  function updateBadge(count) {
9265
9425
  badgeCount = count;
9266
9426
  document.title = count > 0 ? `(${count}) ${baseTitle}` : baseTitle;
@@ -9297,7 +9457,8 @@ function updateBadge(count) {
9297
9457
  link.href = dataUrl;
9298
9458
  try {
9299
9459
  const hostApi = window.mailxapi;
9300
- if (hostApi?.setTaskbarOverlay) {
9460
+ if (hostApi?.setTaskbarOverlay && count !== lastOverlayCount) {
9461
+ lastOverlayCount = count;
9301
9462
  if (count > 0) {
9302
9463
  const bc = document.createElement("canvas");
9303
9464
  bc.width = 32;
@@ -13006,11 +13167,11 @@ function offsetToLineCol(src, pos) {
13006
13167
  return { pos, line, col };
13007
13168
  }
13008
13169
  function renderMarkdown(md) {
13009
- const esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
13170
+ const esc2 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
13010
13171
  const blocks = [];
13011
13172
  let src = md.replace(/```(\w*)\n([\s\S]*?)```/g, (_m, _lang, code) => {
13012
13173
  const i = blocks.length;
13013
- blocks.push(`<pre class="mailx-help-code"><code>${esc(code)}</code></pre>`);
13174
+ blocks.push(`<pre class="mailx-help-code"><code>${esc2(code)}</code></pre>`);
13014
13175
  return `\0BLOCK${i}\0`;
13015
13176
  });
13016
13177
  const lines = src.split(/\r?\n/);
@@ -13030,7 +13191,7 @@ function renderMarkdown(md) {
13030
13191
  }
13031
13192
  };
13032
13193
  function inline(s) {
13033
- s = esc(s);
13194
+ s = esc2(s);
13034
13195
  s = s.replace(/`([^`]+)`/g, (_m, c) => `<code>${c}</code>`);
13035
13196
  s = s.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
13036
13197
  s = s.replace(/(^|[^*])\*([^*\n]+)\*/g, "$1<em>$2</em>");
@@ -13375,6 +13536,53 @@ document.getElementById("opt-compose-font-size")?.addEventListener("change", (e)
13375
13536
  }).catch(() => {
13376
13537
  });
13377
13538
  });
13539
+ var optEditorLanguage = document.getElementById("opt-editor-language");
13540
+ if (optEditorLanguage) {
13541
+ (async () => {
13542
+ const scriptUrl = localStorage.getItem("mailx-tinymce-cdn") || "lib/tinymce/tinymce.min.js";
13543
+ const { availableLanguages: availableLanguages2, resolveLanguage: resolveLanguage2, languageLabel: languageLabel2, LANGUAGE_AUTO: LANGUAGE_AUTO2, LANGUAGE_ENGLISH: LANGUAGE_ENGLISH2 } = await Promise.resolve().then(() => (init_tinymce_langs(), tinymce_langs_exports));
13544
+ const available = await availableLanguages2(scriptUrl);
13545
+ const autoPick = resolveLanguage2(LANGUAGE_AUTO2, available);
13546
+ const add = (value, text) => {
13547
+ const o = document.createElement("option");
13548
+ o.value = value;
13549
+ o.textContent = text;
13550
+ optEditorLanguage.appendChild(o);
13551
+ };
13552
+ add(LANGUAGE_AUTO2, `Auto \u2014 ${languageLabel2(autoPick)}`);
13553
+ add(LANGUAGE_ENGLISH2, languageLabel2(LANGUAGE_ENGLISH2));
13554
+ for (const code of [...available].sort((a, b) => languageLabel2(a).localeCompare(languageLabel2(b)))) {
13555
+ add(code, `${languageLabel2(code)} (${code})`);
13556
+ }
13557
+ if (!available.length) {
13558
+ optEditorLanguage.disabled = true;
13559
+ optEditorLanguage.title = "No language packs found next to tinymce.min.js \u2014 English only";
13560
+ }
13561
+ let current = LANGUAGE_AUTO2;
13562
+ try {
13563
+ current = localStorage.getItem("mailx-editor-language") || LANGUAGE_AUTO2;
13564
+ } catch {
13565
+ }
13566
+ try {
13567
+ const settings = await getSettings();
13568
+ if (typeof settings?.ui?.editorLanguage === "string" && settings.ui.editorLanguage) current = settings.ui.editorLanguage;
13569
+ } catch {
13570
+ }
13571
+ optEditorLanguage.value = current;
13572
+ if (!optEditorLanguage.value) optEditorLanguage.value = LANGUAGE_AUTO2;
13573
+ })().catch((e) => console.error("[settings] editor-language menu failed:", e?.message || e));
13574
+ optEditorLanguage.addEventListener("change", () => {
13575
+ const value = optEditorLanguage.value;
13576
+ try {
13577
+ localStorage.setItem("mailx-editor-language", value);
13578
+ } catch {
13579
+ }
13580
+ getSettings().then((settings) => {
13581
+ settings.ui = { ...settings.ui, editorLanguage: value };
13582
+ saveSettings(settings);
13583
+ }).catch((e) => console.error("[settings] editor language save failed:", e?.message || e));
13584
+ });
13585
+ }
13378
13586
  function saveEditorSetting(editor) {
13379
13587
  try {
13380
13588
  localStorage.setItem("mailx-editor-type", editor);