@bobfrankston/rmfmail 1.2.276 → 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 (87) hide show
  1. package/.commitmsg +28 -23
  2. package/bin/build-tinymce.js +27 -0
  3. package/client/app.bundle.js +108 -0
  4. package/client/app.bundle.js.map +4 -4
  5. package/client/app.js +65 -0
  6. package/client/app.js.map +1 -1
  7. package/client/app.ts +59 -0
  8. package/client/compose/compose.bundle.js +90 -2
  9. package/client/compose/compose.bundle.js.map +4 -4
  10. package/client/compose/compose.js +26 -1
  11. package/client/compose/compose.js.map +1 -1
  12. package/client/compose/compose.ts +22 -1
  13. package/client/compose/editor.js +12 -1
  14. package/client/compose/editor.js.map +1 -1
  15. package/client/compose/editor.ts +17 -2
  16. package/client/index.html +1 -0
  17. package/client/lib/rmf-tiny.js +3 -0
  18. package/client/lib/tinymce/langs/ar-SA.js +440 -0
  19. package/client/lib/tinymce/langs/ar.js +441 -0
  20. package/client/lib/tinymce/langs/az.js +440 -0
  21. package/client/lib/tinymce/langs/be.js +440 -0
  22. package/client/lib/tinymce/langs/bg-BG.js +440 -0
  23. package/client/lib/tinymce/langs/bn-BD.js +440 -0
  24. package/client/lib/tinymce/langs/bs.js +440 -0
  25. package/client/lib/tinymce/langs/ca.js +440 -0
  26. package/client/lib/tinymce/langs/cs.js +440 -0
  27. package/client/lib/tinymce/langs/cy.js +440 -0
  28. package/client/lib/tinymce/langs/da.js +440 -0
  29. package/client/lib/tinymce/langs/de.js +440 -0
  30. package/client/lib/tinymce/langs/el.js +440 -0
  31. package/client/lib/tinymce/langs/eo.js +440 -0
  32. package/client/lib/tinymce/langs/es-MX.js +440 -0
  33. package/client/lib/tinymce/langs/es.js +440 -0
  34. package/client/lib/tinymce/langs/et.js +440 -0
  35. package/client/lib/tinymce/langs/eu.js +440 -0
  36. package/client/lib/tinymce/langs/fa.js +441 -0
  37. package/client/lib/tinymce/langs/fi.js +440 -0
  38. package/client/lib/tinymce/langs/fr-FR.js +440 -0
  39. package/client/lib/tinymce/langs/gl.js +440 -0
  40. package/client/lib/tinymce/langs/he-IL.js +440 -0
  41. package/client/lib/tinymce/langs/hr.js +440 -0
  42. package/client/lib/tinymce/langs/hu-HU.js +440 -0
  43. package/client/lib/tinymce/langs/hy.js +440 -0
  44. package/client/lib/tinymce/langs/id.js +440 -0
  45. package/client/lib/tinymce/langs/index.json +63 -0
  46. package/client/lib/tinymce/langs/is-IS.js +440 -0
  47. package/client/lib/tinymce/langs/it.js +440 -0
  48. package/client/lib/tinymce/langs/ja.js +440 -0
  49. package/client/lib/tinymce/langs/ka-GE.js +440 -0
  50. package/client/lib/tinymce/langs/kab.js +440 -0
  51. package/client/lib/tinymce/langs/kk.js +440 -0
  52. package/client/lib/tinymce/langs/ko-KR.js +440 -0
  53. package/client/lib/tinymce/langs/lt.js +440 -0
  54. package/client/lib/tinymce/langs/lv.js +440 -0
  55. package/client/lib/tinymce/langs/nb-NO.js +440 -0
  56. package/client/lib/tinymce/langs/ne.js +440 -0
  57. package/client/lib/tinymce/langs/nl-BE.js +440 -0
  58. package/client/lib/tinymce/langs/nl.js +440 -0
  59. package/client/lib/tinymce/langs/oc.js +440 -0
  60. package/client/lib/tinymce/langs/pl.js +440 -0
  61. package/client/lib/tinymce/langs/pt-BR.js +440 -0
  62. package/client/lib/tinymce/langs/pt-PT.js +440 -0
  63. package/client/lib/tinymce/langs/ro.js +440 -0
  64. package/client/lib/tinymce/langs/ru.js +440 -0
  65. package/client/lib/tinymce/langs/sk.js +440 -0
  66. package/client/lib/tinymce/langs/sl-SI.js +440 -0
  67. package/client/lib/tinymce/langs/sr.js +440 -0
  68. package/client/lib/tinymce/langs/sv-SE.js +440 -0
  69. package/client/lib/tinymce/langs/ta.js +440 -0
  70. package/client/lib/tinymce/langs/tg.js +440 -0
  71. package/client/lib/tinymce/langs/th-TH.js +440 -0
  72. package/client/lib/tinymce/langs/tr.js +440 -0
  73. package/client/lib/tinymce/langs/ug.js +440 -0
  74. package/client/lib/tinymce/langs/uk.js +440 -0
  75. package/client/lib/tinymce/langs/vi.js +440 -0
  76. package/client/lib/tinymce/langs/zh-CN.js +440 -0
  77. package/client/lib/tinymce/langs/zh-HK.js +440 -0
  78. package/client/lib/tinymce/langs/zh-MO.js +440 -0
  79. package/client/lib/tinymce/langs/zh-TW.js +440 -0
  80. package/client/lib/tinymce-langs.js +96 -0
  81. package/client/lib/tinymce-langs.js.map +1 -0
  82. package/client/lib/tinymce-langs.ts +99 -0
  83. package/docs/editor.md +29 -3
  84. package/docs/preferences.md +2 -1
  85. package/npmchanges.md +29 -0
  86. package/package.json +11 -8
  87. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-59216 → node_modules.npmglobalize-stash-48352}/.package-lock.json +0 -0
package/.commitmsg CHANGED
@@ -1,26 +1,31 @@
1
- Preview hotkeys fired twice Ctrl+F opened two compose windows
1
+ TinyMCE UI language: 61 packs, auto-detected from the browser
2
2
 
3
- A keydown in the message preview reached app.ts by TWO routes on desktop
4
- WebView2: the iframe's own inline script posts every keydown to the parent as
5
- `previewKey` (app.ts re-dispatches it), and installPreviewControls ALSO attached
6
- a contentDocument listener that re-dispatched a synthetic keydown of its own.
7
- The srcdoc document is same-origin, so both are live. One Ctrl+F therefore ran
8
- openCompose("forward") twice and opened two compose popouts — the second never
9
- finished loading and sat blank and "(Not Responding)" until it was killed (Bob
10
- 2026-08-23, "Compose: Fwd: FirstCare Slide Deck (Not Responding)"; the log shows
11
- openCompose-entry mode=forward twice in the same millisecond, two popoutCompose
12
- windows, and the loaded one exiting while the blank duplicate survived). Ctrl+D
13
- and Ctrl+P doubled the same way.
3
+ The tinymce package ships no langs/ directory at all the packs are a separate
4
+ community package (tinymce-i18n), one file per language, each a plain
5
+ tinymce.addI18n("de", {...}). build-tinymce.js now copies langs8/ next to
6
+ tinymce.min.js, where TinyMCE's own <baseURL>/langs/<code>.js rule finds it, and
7
+ writes langs/index.json listing what it copied. 61 packs, 1.6 MB; only the
8
+ language in force is ever fetched.
14
9
 
15
- The inline forwarder is the mechanism that survives it is the only one that
16
- works on Android WebView, where parent-side contentDocument listeners never
17
- fire. installPreviewControls keeps only what is genuinely iframe-local: zoom and
18
- Ctrl+A.
10
+ TinyMCE has no detection of its own: `language` defaults to "en" and a missing
11
+ langs/<code>.js raises a visible in-editor error notification. So "Auto" is
12
+ ours (client/lib/tinymce-langs.ts): walk navigator.languages and take the first
13
+ hit against the manifest — exact ("pt-BR"), then bare ("de-CH" → "de"), then
14
+ any regional variant ("he" → "he-IL"). English short-circuits, since TinyMCE's
15
+ built-in strings are English. Anything unmatched, and any pinned pack we no
16
+ longer ship, stays English rather than provoking the error. Resolving against
17
+ the manifest rather than a hardcoded list keeps the copied files the single
18
+ source of truth, and follows a mailx-tinymce-cdn override for free because the
19
+ manifest URL is derived from the script URL by the same rule TinyMCE uses.
19
20
 
20
- Also: push the Windows taskbar overlay only when the unread count CHANGES.
21
- Windows holds the overlay icon until the next SetOverlayIcon, so the host cannot
22
- free the icon it just handed over; re-sending an identical badge every few
23
- seconds cost GDI objects for nothing and helped drive rmfmail.exe to the
24
- 10,000-object per-process cap (measured at GDI=9995, with 3423
25
- "CreateIconIndirect failed: Not enough memory resources" in one day's log).
26
- msger 0.1.429 fixes the leak itself by freeing the retired icon.
21
+ Settings Editor Editor language: Auto (naming what it resolves to right
22
+ now), English, then every pack, named via Intl.DisplayNames so no name table is
23
+ maintained here. Stored as ui.editorLanguage; same synchronous-localStorage-
24
+ then-persist shape as the editor type, so the next compose-open is correct.
25
+
26
+ Scope, stated in the menu tooltip and docs/editor.md: these translate the
27
+ editor CHROME only. Message content, the spellcheck dictionary (English) and
28
+ Harper (English only) are untouched, and Quill/tiptap have no localized chrome.
29
+
30
+ tests/tinymce-langs.test.ts pins the resolution against the real manifest.
31
+ Needs rmf-tiny 0.1.52 for the adapter's `language` option.
@@ -10,6 +10,13 @@ import path from "node:path";
10
10
  const root = path.resolve(import.meta.dirname, "..");
11
11
  const src = path.join(root, "node_modules", "tinymce");
12
12
  const dst = path.join(root, "client", "lib", "tinymce");
13
+ // UI translations. The tinymce package itself ships NO langs/ directory —
14
+ // the packs are a separate community package, one file per language, each
15
+ // a plain `tinymce.addI18n("de", {...})` call. TinyMCE resolves them
16
+ // itself as `<baseURL>/langs/<code>.js`, so dropping them beside the
17
+ // script is the whole integration (Claude Code 2026-08-23).
18
+ const langSrc = path.join(root, "node_modules", "tinymce-i18n", "langs8");
19
+ const langDst = path.join(dst, "langs");
13
20
 
14
21
  if (!fs.existsSync(src)) {
15
22
  console.error(`build-tinymce: source not found at ${src} — run npm install first`);
@@ -32,6 +39,26 @@ function copyTree(s, d) {
32
39
  fs.rmSync(dst, { recursive: true, force: true });
33
40
  copyTree(src, dst);
34
41
 
42
+ // Language packs. Only the one language in force is ever fetched at
43
+ // runtime, so shipping every pack costs disk and nothing else. The manifest
44
+ // is what the client resolves "auto" against — the copied files ARE the list
45
+ // of what can be offered, so nothing hardcodes a second copy of it.
46
+ let langs = [];
47
+ if (fs.existsSync(langSrc)) {
48
+ fs.mkdirSync(langDst, { recursive: true });
49
+ for (const name of fs.readdirSync(langSrc)) {
50
+ if (!name.endsWith(".js")) continue;
51
+ fs.copyFileSync(path.join(langSrc, name), path.join(langDst, name));
52
+ langs.push(name.slice(0, -3));
53
+ }
54
+ langs.sort();
55
+ fs.writeFileSync(path.join(langDst, "index.json"), JSON.stringify(langs, null, 2) + "\n");
56
+ console.log(`build-tinymce: copied ${langs.length} language packs → client/lib/tinymce/langs/`);
57
+ } else {
58
+ // Never silently ship an editor whose language menu is empty.
59
+ console.error(`build-tinymce: WARNING no language packs at ${langSrc} — run npm install; the editor will be English-only`);
60
+ }
61
+
35
62
  let count = 0, bytes = 0;
36
63
  function tally(p) {
37
64
  const st = fs.statSync(p);
@@ -7937,6 +7937,67 @@ whatever it finds.</p>
7937
7937
  }
7938
7938
  });
7939
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
+
7940
8001
  // client/components/folder-tree.js
7941
8002
  init_api_client();
7942
8003
  init_context_menu();
@@ -13475,6 +13536,53 @@ document.getElementById("opt-compose-font-size")?.addEventListener("change", (e)
13475
13536
  }).catch(() => {
13476
13537
  });
13477
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
+ }
13478
13586
  function saveEditorSetting(editor) {
13479
13587
  try {
13480
13588
  localStorage.setItem("mailx-editor-type", editor);