@bobfrankston/rmfmail 1.2.167 → 1.2.169
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.
- package/README.md +1 -0
- package/TODO.md +1 -0
- package/client/app.bundle.js +28 -0
- package/client/app.bundle.js.map +2 -2
- package/client/app.js +23 -0
- package/client/app.js.map +1 -1
- package/client/app.ts +18 -0
- package/client/compose/compose.bundle.js +292 -0
- package/client/compose/compose.bundle.js.map +4 -4
- package/client/compose/compose.js +23 -0
- package/client/compose/compose.js.map +1 -1
- package/client/compose/compose.ts +20 -0
- package/client/compose/harper-lint.js +269 -0
- package/client/compose/harper-lint.js.map +1 -0
- package/client/compose/harper-lint.ts +265 -0
- package/client/index.html +1 -0
- package/client/lib/api-client.js +8 -0
- package/client/lib/api-client.js.map +1 -1
- package/client/lib/api-client.ts +8 -0
- package/client/lib/mailxapi.js +6 -0
- package/docs/harper-integration.md +120 -0
- package/package.json +8 -5
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-service/index.d.ts +11 -0
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +60 -0
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +58 -0
- package/packages/mailx-service/jsonrpc.js +2 -0
- package/packages/mailx-service/jsonrpc.js.map +1 -1
- package/packages/mailx-service/jsonrpc.ts +2 -0
- package/packages/mailx-service/package.json +1 -1
- package/packages/mailx-settings/docs/harper-integration.md +120 -0
- package/packages/mailx-settings/package.json +1 -1
- package/packages/mailx-store/package.json +1 -1
- package/packages/mailx-store-web/package.json +1 -1
- package/packages/mailx-types/mailx-api.d.ts +12 -0
- package/packages/mailx-types/mailx-api.d.ts.map +1 -1
- package/packages/mailx-types/mailx-api.ts +4 -0
- package/packages/mailx-types/package.json +1 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-28172 → node_modules.npmglobalize-stash-69656}/.package-lock.json +0 -0
package/client/app.ts
CHANGED
|
@@ -5356,6 +5356,12 @@ getSettings().then((s: any) => {
|
|
|
5356
5356
|
const px = Number(s.ui?.composeFontSize);
|
|
5357
5357
|
fontEl.value = String(Number.isFinite(px) && px >= 8 && px <= 32 ? px : 15);
|
|
5358
5358
|
}
|
|
5359
|
+
const gcEl = document.getElementById("opt-grammar-check") as HTMLInputElement | null;
|
|
5360
|
+
if (gcEl) {
|
|
5361
|
+
const on = s.ui?.grammarCheck !== false; // default ON
|
|
5362
|
+
gcEl.checked = on;
|
|
5363
|
+
try { localStorage.setItem("mailx-grammar-check", on ? "1" : "0"); } catch { /* */ }
|
|
5364
|
+
}
|
|
5359
5365
|
}).catch(() => {});
|
|
5360
5366
|
|
|
5361
5367
|
document.getElementById("opt-compose-window")?.addEventListener("change", (e) => {
|
|
@@ -5367,6 +5373,18 @@ document.getElementById("opt-compose-window")?.addEventListener("change", (e) =>
|
|
|
5367
5373
|
}).catch(() => {});
|
|
5368
5374
|
});
|
|
5369
5375
|
|
|
5376
|
+
// Harper grammar squiggles in compose — same dual persistence as the
|
|
5377
|
+
// compose-window toggle: localStorage for the in-window compose iframe
|
|
5378
|
+
// (same origin), settings.ui for the popout's boot snapshot.
|
|
5379
|
+
document.getElementById("opt-grammar-check")?.addEventListener("change", (e) => {
|
|
5380
|
+
const on = (e.target as HTMLInputElement).checked;
|
|
5381
|
+
try { localStorage.setItem("mailx-grammar-check", on ? "1" : "0"); } catch { /* */ }
|
|
5382
|
+
getSettings().then((settings: any) => {
|
|
5383
|
+
settings.ui = { ...settings.ui, grammarCheck: on };
|
|
5384
|
+
saveSettings(settings);
|
|
5385
|
+
}).catch(() => {});
|
|
5386
|
+
});
|
|
5387
|
+
|
|
5370
5388
|
// Unified compose editing font size (px). Same sync-cache-then-persist shape
|
|
5371
5389
|
// as saveEditorSetting: compose.ts reads `mailx-compose-font-size` from
|
|
5372
5390
|
// localStorage at module load, so the very next compose-open is correct.
|
|
@@ -1069,6 +1069,7 @@ __export(api_client_exports, {
|
|
|
1069
1069
|
getUnifiedInbox: () => getUnifiedInbox,
|
|
1070
1070
|
getUserDict: () => getUserDict,
|
|
1071
1071
|
getVersion: () => getVersion,
|
|
1072
|
+
grammarLint: () => grammarLint,
|
|
1072
1073
|
hasBccHistoryTo: () => hasBccHistoryTo,
|
|
1073
1074
|
hasCcHistoryTo: () => hasCcHistoryTo,
|
|
1074
1075
|
installConsoleCapture: () => installConsoleCapture,
|
|
@@ -1629,6 +1630,12 @@ async function openAttachment(accountId, uid, attachmentId, folderId, filename)
|
|
|
1629
1630
|
async function getMessageSource(accountId, uid, folderId) {
|
|
1630
1631
|
return ipc().getMessageSource(accountId, uid, folderId);
|
|
1631
1632
|
}
|
|
1633
|
+
async function grammarLint(blocks) {
|
|
1634
|
+
const fn = ipc().grammarLint;
|
|
1635
|
+
if (!fn)
|
|
1636
|
+
throw new Error("no grammarLint bridge");
|
|
1637
|
+
return fn(blocks);
|
|
1638
|
+
}
|
|
1632
1639
|
async function registerMailto() {
|
|
1633
1640
|
const fn = ipc().registerMailto;
|
|
1634
1641
|
if (!fn)
|
|
@@ -2914,6 +2921,277 @@ var init_spellcheck = __esm({
|
|
|
2914
2921
|
}
|
|
2915
2922
|
});
|
|
2916
2923
|
|
|
2924
|
+
// client/compose/harper-lint.js
|
|
2925
|
+
var harper_lint_exports = {};
|
|
2926
|
+
__export(harper_lint_exports, {
|
|
2927
|
+
initHarperLint: () => initHarperLint
|
|
2928
|
+
});
|
|
2929
|
+
function initHarperLint(ed) {
|
|
2930
|
+
let disposed = false;
|
|
2931
|
+
let timer = null;
|
|
2932
|
+
let placed = [];
|
|
2933
|
+
let paintQueued = false;
|
|
2934
|
+
let bridgeDead = false;
|
|
2935
|
+
const target = () => {
|
|
2936
|
+
try {
|
|
2937
|
+
const nat = ed.nativeEditor;
|
|
2938
|
+
if (nat?.getBody) {
|
|
2939
|
+
const b = nat.getBody();
|
|
2940
|
+
return b ? { root: b, doc: b.ownerDocument } : null;
|
|
2941
|
+
}
|
|
2942
|
+
return ed.root ? { root: ed.root, doc: ed.root.ownerDocument } : null;
|
|
2943
|
+
} catch {
|
|
2944
|
+
return null;
|
|
2945
|
+
}
|
|
2946
|
+
};
|
|
2947
|
+
const overlayHost = (doc, root) => root === doc.body ? doc.documentElement : doc.body;
|
|
2948
|
+
const clearOverlay = (doc) => {
|
|
2949
|
+
doc.getElementById(OVERLAY_ID2)?.remove();
|
|
2950
|
+
doc.getElementById(POPUP_ID)?.remove();
|
|
2951
|
+
};
|
|
2952
|
+
const collectBlocks = (root) => {
|
|
2953
|
+
const kids = Array.from(root.children).filter((c) => c instanceof HTMLElement);
|
|
2954
|
+
const blocks = (kids.length ? kids : [root]).filter((b) => b.tagName !== "BLOCKQUOTE" && !b.closest("blockquote") && !b.querySelector("blockquote"));
|
|
2955
|
+
return blocks;
|
|
2956
|
+
};
|
|
2957
|
+
const blockText = (block, doc) => {
|
|
2958
|
+
const walker = doc.createTreeWalker(block, NodeFilter.SHOW_TEXT);
|
|
2959
|
+
const nodes = [];
|
|
2960
|
+
let text = "";
|
|
2961
|
+
for (let n = walker.nextNode(); n; n = walker.nextNode()) {
|
|
2962
|
+
nodes.push({ node: n, from: text.length });
|
|
2963
|
+
text += n.data;
|
|
2964
|
+
}
|
|
2965
|
+
return { text, nodes };
|
|
2966
|
+
};
|
|
2967
|
+
const spanToRange = (doc, nodes, start, end) => {
|
|
2968
|
+
let a = null;
|
|
2969
|
+
let b = null;
|
|
2970
|
+
for (const e of nodes) {
|
|
2971
|
+
const len = e.node.data.length;
|
|
2972
|
+
if (!a && start >= e.from && start <= e.from + len)
|
|
2973
|
+
a = { node: e.node, off: start - e.from };
|
|
2974
|
+
if (end >= e.from && end <= e.from + len)
|
|
2975
|
+
b = { node: e.node, off: end - e.from };
|
|
2976
|
+
}
|
|
2977
|
+
if (!a || !b)
|
|
2978
|
+
return null;
|
|
2979
|
+
try {
|
|
2980
|
+
const r = doc.createRange();
|
|
2981
|
+
r.setStart(a.node, a.off);
|
|
2982
|
+
r.setEnd(b.node, b.off);
|
|
2983
|
+
return r;
|
|
2984
|
+
} catch {
|
|
2985
|
+
return null;
|
|
2986
|
+
}
|
|
2987
|
+
};
|
|
2988
|
+
const paint = () => {
|
|
2989
|
+
if (disposed)
|
|
2990
|
+
return;
|
|
2991
|
+
const t = target();
|
|
2992
|
+
if (!t)
|
|
2993
|
+
return;
|
|
2994
|
+
clearOverlay(t.doc);
|
|
2995
|
+
if (placed.length === 0)
|
|
2996
|
+
return;
|
|
2997
|
+
const host = overlayHost(t.doc, t.root);
|
|
2998
|
+
const ov = t.doc.createElement("div");
|
|
2999
|
+
ov.id = OVERLAY_ID2;
|
|
3000
|
+
ov.style.cssText = "position:absolute;left:0;top:0;width:0;height:0;overflow:visible;pointer-events:none;z-index:5;";
|
|
3001
|
+
const win = t.doc.defaultView || window;
|
|
3002
|
+
for (const p of placed) {
|
|
3003
|
+
let rects = [];
|
|
3004
|
+
try {
|
|
3005
|
+
rects = p.range.getClientRects();
|
|
3006
|
+
} catch {
|
|
3007
|
+
continue;
|
|
3008
|
+
}
|
|
3009
|
+
for (const r of Array.from(rects)) {
|
|
3010
|
+
if (r.width < 2)
|
|
3011
|
+
continue;
|
|
3012
|
+
const u = t.doc.createElement("div");
|
|
3013
|
+
u.style.cssText = `position:absolute;left:${r.left + win.scrollX}px;top:${r.bottom + win.scrollY - 2}px;width:${r.width}px;height:0;border-bottom:2px dotted rgba(66,133,244,0.9);pointer-events:none;`;
|
|
3014
|
+
ov.appendChild(u);
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
host.appendChild(ov);
|
|
3018
|
+
};
|
|
3019
|
+
const queuePaint = () => {
|
|
3020
|
+
if (paintQueued || disposed)
|
|
3021
|
+
return;
|
|
3022
|
+
paintQueued = true;
|
|
3023
|
+
requestAnimationFrame(() => {
|
|
3024
|
+
paintQueued = false;
|
|
3025
|
+
paint();
|
|
3026
|
+
});
|
|
3027
|
+
};
|
|
3028
|
+
const lint = async () => {
|
|
3029
|
+
if (disposed || bridgeDead)
|
|
3030
|
+
return;
|
|
3031
|
+
const t = target();
|
|
3032
|
+
if (!t)
|
|
3033
|
+
return;
|
|
3034
|
+
const blocks = collectBlocks(t.root);
|
|
3035
|
+
const texts = blocks.map((b) => blockText(b, t.doc));
|
|
3036
|
+
let res;
|
|
3037
|
+
try {
|
|
3038
|
+
res = await grammarLint(texts.map((x) => x.text));
|
|
3039
|
+
} catch {
|
|
3040
|
+
bridgeDead = true;
|
|
3041
|
+
clearOverlayAll();
|
|
3042
|
+
return;
|
|
3043
|
+
}
|
|
3044
|
+
if (disposed)
|
|
3045
|
+
return;
|
|
3046
|
+
const next = [];
|
|
3047
|
+
(res?.blocks || []).forEach((lints, i) => {
|
|
3048
|
+
const tx = texts[i];
|
|
3049
|
+
if (!tx)
|
|
3050
|
+
return;
|
|
3051
|
+
for (const l of lints || []) {
|
|
3052
|
+
if (SKIP_KINDS.has(l.kind))
|
|
3053
|
+
continue;
|
|
3054
|
+
const range = spanToRange(t.doc, tx.nodes, l.start, l.end);
|
|
3055
|
+
if (range)
|
|
3056
|
+
next.push({ lint: l, range });
|
|
3057
|
+
}
|
|
3058
|
+
});
|
|
3059
|
+
placed = next;
|
|
3060
|
+
queuePaint();
|
|
3061
|
+
};
|
|
3062
|
+
const clearOverlayAll = () => {
|
|
3063
|
+
const t = target();
|
|
3064
|
+
if (t)
|
|
3065
|
+
clearOverlay(t.doc);
|
|
3066
|
+
placed = [];
|
|
3067
|
+
};
|
|
3068
|
+
const schedule = () => {
|
|
3069
|
+
if (timer)
|
|
3070
|
+
clearTimeout(timer);
|
|
3071
|
+
timer = setTimeout(() => {
|
|
3072
|
+
void lint();
|
|
3073
|
+
}, DEBOUNCE_MS);
|
|
3074
|
+
};
|
|
3075
|
+
const applyFix = (p, replacement, doc) => {
|
|
3076
|
+
try {
|
|
3077
|
+
const nat = ed.nativeEditor;
|
|
3078
|
+
if (nat?.selection?.setRng && nat?.insertContent) {
|
|
3079
|
+
nat.selection.setRng(p.range);
|
|
3080
|
+
const esc = replacement.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3081
|
+
nat.insertContent(esc);
|
|
3082
|
+
} else {
|
|
3083
|
+
const sel = (doc.defaultView || window).getSelection();
|
|
3084
|
+
if (!sel)
|
|
3085
|
+
return;
|
|
3086
|
+
sel.removeAllRanges();
|
|
3087
|
+
sel.addRange(p.range);
|
|
3088
|
+
doc.execCommand("insertText", false, replacement);
|
|
3089
|
+
}
|
|
3090
|
+
} catch {
|
|
3091
|
+
}
|
|
3092
|
+
doc.getElementById(POPUP_ID)?.remove();
|
|
3093
|
+
schedule();
|
|
3094
|
+
};
|
|
3095
|
+
const showPopup = (p, x, y, doc) => {
|
|
3096
|
+
doc.getElementById(POPUP_ID)?.remove();
|
|
3097
|
+
const pop = doc.createElement("div");
|
|
3098
|
+
pop.id = POPUP_ID;
|
|
3099
|
+
pop.style.cssText = `position:absolute;left:${x}px;top:${y + 6}px;z-index:2100;background:#fff;color:#1a1a1a;border:1px solid #bbb;border-radius:6px;box-shadow:0 4px 16px rgba(0,0,0,0.25);padding:6px 0;min-width:180px;max-width:340px;font:13px system-ui,sans-serif;`;
|
|
3100
|
+
const msg = doc.createElement("div");
|
|
3101
|
+
msg.style.cssText = "padding:4px 12px 6px;color:#555;border-bottom:1px solid #eee;";
|
|
3102
|
+
msg.textContent = p.lint.message;
|
|
3103
|
+
pop.appendChild(msg);
|
|
3104
|
+
for (const s of p.lint.suggestions.slice(0, 5)) {
|
|
3105
|
+
const item = doc.createElement("div");
|
|
3106
|
+
item.style.cssText = "padding:6px 12px;cursor:pointer;font-weight:600;";
|
|
3107
|
+
item.textContent = s;
|
|
3108
|
+
item.addEventListener("mouseenter", () => {
|
|
3109
|
+
item.style.background = "#eef3ff";
|
|
3110
|
+
});
|
|
3111
|
+
item.addEventListener("mouseleave", () => {
|
|
3112
|
+
item.style.background = "";
|
|
3113
|
+
});
|
|
3114
|
+
item.addEventListener("mousedown", (e) => {
|
|
3115
|
+
e.preventDefault();
|
|
3116
|
+
e.stopPropagation();
|
|
3117
|
+
applyFix(p, s, doc);
|
|
3118
|
+
});
|
|
3119
|
+
pop.appendChild(item);
|
|
3120
|
+
}
|
|
3121
|
+
if (p.lint.suggestions.length === 0) {
|
|
3122
|
+
const none = doc.createElement("div");
|
|
3123
|
+
none.style.cssText = "padding:6px 12px;color:#888;";
|
|
3124
|
+
none.textContent = "(no suggestion \u2014 reword manually)";
|
|
3125
|
+
pop.appendChild(none);
|
|
3126
|
+
}
|
|
3127
|
+
overlayHost(doc, target()?.root || doc.body).appendChild(pop);
|
|
3128
|
+
const dismiss2 = () => {
|
|
3129
|
+
pop.remove();
|
|
3130
|
+
doc.removeEventListener("mousedown", dismiss2, true);
|
|
3131
|
+
};
|
|
3132
|
+
setTimeout(() => doc.addEventListener("mousedown", dismiss2, true), 0);
|
|
3133
|
+
};
|
|
3134
|
+
const onClick = (e) => {
|
|
3135
|
+
if (disposed || placed.length === 0)
|
|
3136
|
+
return;
|
|
3137
|
+
const t = target();
|
|
3138
|
+
if (!t)
|
|
3139
|
+
return;
|
|
3140
|
+
for (const p of placed) {
|
|
3141
|
+
let rects = [];
|
|
3142
|
+
try {
|
|
3143
|
+
rects = Array.from(p.range.getClientRects());
|
|
3144
|
+
} catch {
|
|
3145
|
+
continue;
|
|
3146
|
+
}
|
|
3147
|
+
for (const r of rects) {
|
|
3148
|
+
if (e.clientX >= r.left - 2 && e.clientX <= r.right + 2 && e.clientY >= r.top - 2 && e.clientY <= r.bottom + 4) {
|
|
3149
|
+
const win = t.doc.defaultView || window;
|
|
3150
|
+
showPopup(p, r.left + win.scrollX, r.bottom + win.scrollY, t.doc);
|
|
3151
|
+
return;
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
};
|
|
3156
|
+
const t0 = target();
|
|
3157
|
+
ed.onContentChange(schedule);
|
|
3158
|
+
if (t0) {
|
|
3159
|
+
t0.doc.addEventListener("click", onClick);
|
|
3160
|
+
t0.doc.addEventListener("scroll", queuePaint, true);
|
|
3161
|
+
(t0.doc.defaultView || window).addEventListener("resize", queuePaint);
|
|
3162
|
+
}
|
|
3163
|
+
try {
|
|
3164
|
+
ed.getScrollContainer()?.addEventListener("scroll", queuePaint);
|
|
3165
|
+
} catch {
|
|
3166
|
+
}
|
|
3167
|
+
setTimeout(() => {
|
|
3168
|
+
void lint();
|
|
3169
|
+
}, 1500);
|
|
3170
|
+
return () => {
|
|
3171
|
+
disposed = true;
|
|
3172
|
+
if (timer)
|
|
3173
|
+
clearTimeout(timer);
|
|
3174
|
+
clearOverlayAll();
|
|
3175
|
+
const t = target();
|
|
3176
|
+
if (t) {
|
|
3177
|
+
t.doc.removeEventListener("click", onClick);
|
|
3178
|
+
t.doc.removeEventListener("scroll", queuePaint, true);
|
|
3179
|
+
(t.doc.defaultView || window).removeEventListener("resize", queuePaint);
|
|
3180
|
+
}
|
|
3181
|
+
};
|
|
3182
|
+
}
|
|
3183
|
+
var DEBOUNCE_MS, OVERLAY_ID2, POPUP_ID, SKIP_KINDS;
|
|
3184
|
+
var init_harper_lint = __esm({
|
|
3185
|
+
"client/compose/harper-lint.js"() {
|
|
3186
|
+
"use strict";
|
|
3187
|
+
init_api_client();
|
|
3188
|
+
DEBOUNCE_MS = 700;
|
|
3189
|
+
OVERLAY_ID2 = "harper-overlay";
|
|
3190
|
+
POPUP_ID = "harper-popup";
|
|
3191
|
+
SKIP_KINDS = /* @__PURE__ */ new Set(["Spelling"]);
|
|
3192
|
+
}
|
|
3193
|
+
});
|
|
3194
|
+
|
|
2917
3195
|
// client/compose/ghost-text.js
|
|
2918
3196
|
var ghost_text_exports = {};
|
|
2919
3197
|
__export(ghost_text_exports, {
|
|
@@ -4114,6 +4392,20 @@ async function tryEditor(type) {
|
|
|
4114
4392
|
if (native.initialized) attach();
|
|
4115
4393
|
else native.on("init", attach);
|
|
4116
4394
|
}
|
|
4395
|
+
try {
|
|
4396
|
+
const bootGrammar = window.__rmfBootSettings?.ui?.grammarCheck;
|
|
4397
|
+
let grammarOn = bootGrammar !== void 0 ? bootGrammar !== false : localStorage.getItem("mailx-grammar-check") !== "0";
|
|
4398
|
+
if (grammarOn && ed) {
|
|
4399
|
+
const attachHarper = () => {
|
|
4400
|
+
Promise.resolve().then(() => (init_harper_lint(), harper_lint_exports)).then((m) => m.initHarperLint(ed)).catch(() => {
|
|
4401
|
+
});
|
|
4402
|
+
};
|
|
4403
|
+
const native = ed.nativeEditor;
|
|
4404
|
+
if (type === "tinymce" && native && !native.initialized) native.on("init", attachHarper);
|
|
4405
|
+
else attachHarper();
|
|
4406
|
+
}
|
|
4407
|
+
} catch {
|
|
4408
|
+
}
|
|
4117
4409
|
return ed;
|
|
4118
4410
|
} catch (e) {
|
|
4119
4411
|
logClientEvent("compose-editor-create-failed", { type, error: String(e?.message || e) });
|