@bobfrankston/rmfmail 1.2.286 → 1.2.288
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/.commitmsg +23 -37
- package/client/android-bootstrap.bundle.js +21 -5
- package/client/android-bootstrap.bundle.js.map +2 -2
- package/client/app.bundle.js.map +1 -1
- package/client/compose/compose.bundle.js +169 -39
- package/client/compose/compose.bundle.js.map +3 -3
- package/client/compose/harper-lint.js +125 -42
- package/client/compose/harper-lint.js.map +1 -1
- package/client/compose/harper-lint.ts +120 -40
- package/client/lib/rmf-tiny.js +85 -0
- package/npmchanges.md +82 -0
- package/package.json +8 -8
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +20 -3
- package/packages/mailx-service/index.js.map +1 -1
- 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/package.json +1 -1
- package/packages/mailx-types/trust.d.ts.map +1 -1
- package/packages/mailx-types/trust.js +43 -4
- package/packages/mailx-types/trust.js.map +1 -1
- package/packages/mailx-types/trust.ts +50 -5
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-50896 → node_modules.npmglobalize-stash-39836}/.package-lock.json +0 -0
|
@@ -5,6 +5,125 @@ const POPUP_ID = "harper-popup";
|
|
|
5
5
|
// Spelling squiggles stay with the native WebView2 spellchecker (red, with
|
|
6
6
|
// its own suggestion menu) — double-underlining every typo helps no one.
|
|
7
7
|
const SKIP_KINDS = new Set(["Spelling"]);
|
|
8
|
+
/* ── Pure helpers ───────────────────────────────────────────────────────────
|
|
9
|
+
* Module scope, and exported, because they are the part with rules worth
|
|
10
|
+
* testing: where a line break falls in the extracted text, which spans may be
|
|
11
|
+
* painted, and which sentence the writer is still in the middle of. jsdom can
|
|
12
|
+
* exercise all three without an editor — tests/dom/harper-lint.test.ts.
|
|
13
|
+
*/
|
|
14
|
+
/** Blocks = top-level element children of the editable root (or the root
|
|
15
|
+
* itself when it has none). Quoted reply chains are the author's
|
|
16
|
+
* correspondent's text — skip them (also keeps lint cost proportional
|
|
17
|
+
* to what's actually being written). */
|
|
18
|
+
export const collectBlocks = (root) => {
|
|
19
|
+
const kids = Array.from(root.children).filter((c) => c instanceof HTMLElement);
|
|
20
|
+
const blocks = (kids.length ? kids : [root]).filter(b => b.tagName !== "BLOCKQUOTE" && !b.closest("blockquote") && !b.querySelector("blockquote"));
|
|
21
|
+
return blocks;
|
|
22
|
+
};
|
|
23
|
+
/** Elements that end a line on screen. A reader sees a break here, so the
|
|
24
|
+
* text handed to the grammar engine has to contain one too. */
|
|
25
|
+
const LINE_BREAKING = new Set([
|
|
26
|
+
"BR", "P", "DIV", "LI", "UL", "OL", "TR", "TD", "TH", "TABLE", "PRE", "HR",
|
|
27
|
+
"BLOCKQUOTE", "H1", "H2", "H3", "H4", "H5", "H6", "SECTION", "ARTICLE", "FIGURE",
|
|
28
|
+
]);
|
|
29
|
+
/**
|
|
30
|
+
* Flatten a block to plain text + a map back to its text nodes.
|
|
31
|
+
*
|
|
32
|
+
* Line breaks are REAL characters in that text. Concatenating the text
|
|
33
|
+
* nodes alone glues the last word of one paragraph to the first word of the
|
|
34
|
+
* next, and Harper — correctly, for the string it was given — reports the
|
|
35
|
+
* seam: Bob 2026-08-27 forwarding a message got "`networkTo` should
|
|
36
|
+
* probably be written as `network To`" out of "…of the network" followed by
|
|
37
|
+
* "To: …". Worse, the lint's span then covers a junction that on screen is
|
|
38
|
+
* two blank lines, and getClientRects paints a full-width dotted underline
|
|
39
|
+
* across the whitespace — an error message pointing at nothing.
|
|
40
|
+
*
|
|
41
|
+
* The separators occupy offsets no text node owns, which is deliberate: a
|
|
42
|
+
* lint that starts or ends inside one cannot be placed, and spanToRange
|
|
43
|
+
* drops it rather than guessing.
|
|
44
|
+
*/
|
|
45
|
+
export const blockText = (block, doc) => {
|
|
46
|
+
const walker = doc.createTreeWalker(block, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT);
|
|
47
|
+
const nodes = [];
|
|
48
|
+
let text = "";
|
|
49
|
+
let pendingBreak = false;
|
|
50
|
+
for (let n = walker.nextNode(); n; n = walker.nextNode()) {
|
|
51
|
+
if (n.nodeType === Node.ELEMENT_NODE) {
|
|
52
|
+
if (LINE_BREAKING.has(n.tagName))
|
|
53
|
+
pendingBreak = true;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const data = n.data;
|
|
57
|
+
if (!data)
|
|
58
|
+
continue;
|
|
59
|
+
if (pendingBreak && text)
|
|
60
|
+
text += "\n";
|
|
61
|
+
pendingBreak = false;
|
|
62
|
+
nodes.push({ node: n, from: text.length });
|
|
63
|
+
text += data;
|
|
64
|
+
}
|
|
65
|
+
return { text, nodes };
|
|
66
|
+
};
|
|
67
|
+
/** The nearest thing that renders as its own line — used to refuse any
|
|
68
|
+
* squiggle whose two ends live in different ones. */
|
|
69
|
+
export const lineBox = (node) => {
|
|
70
|
+
for (let e = node; e; e = e.parentNode)
|
|
71
|
+
if (e.nodeType === Node.ELEMENT_NODE && LINE_BREAKING.has(e.tagName))
|
|
72
|
+
return e;
|
|
73
|
+
return null;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Where the caret is inside this block's flattened text, or -1.
|
|
77
|
+
*
|
|
78
|
+
* A sentence is wrong until it is finished. Underlining the words someone
|
|
79
|
+
* is still typing — Bob 2026-08-27, "annoying ... appearing even as I type
|
|
80
|
+
* a sentence in progress" — is the editor arguing with a thought that isn't
|
|
81
|
+
* out yet.
|
|
82
|
+
*/
|
|
83
|
+
export const caretOffset = (doc, nodes) => {
|
|
84
|
+
const sel = doc.getSelection?.();
|
|
85
|
+
if (!sel || !sel.focusNode)
|
|
86
|
+
return -1;
|
|
87
|
+
for (const e of nodes)
|
|
88
|
+
if (e.node === sel.focusNode)
|
|
89
|
+
return e.from + sel.focusOffset;
|
|
90
|
+
return -1;
|
|
91
|
+
};
|
|
92
|
+
/** Start of the sentence the caret sits in — everything after the last
|
|
93
|
+
* terminator that precedes it. */
|
|
94
|
+
export const sentenceStart = (text, caret) => {
|
|
95
|
+
const before = text.slice(0, caret);
|
|
96
|
+
const m = before.match(/[.!?…\n][^.!?…\n]*$/);
|
|
97
|
+
return m ? caret - (m[0].length - 1) : 0;
|
|
98
|
+
};
|
|
99
|
+
export const spanToRange = (doc, nodes, start, end) => {
|
|
100
|
+
let a = null;
|
|
101
|
+
let b = null;
|
|
102
|
+
for (const e of nodes) {
|
|
103
|
+
const len = e.node.data.length;
|
|
104
|
+
if (!a && start >= e.from && start <= e.from + len)
|
|
105
|
+
a = { node: e.node, off: start - e.from };
|
|
106
|
+
if (end >= e.from && end <= e.from + len)
|
|
107
|
+
b = { node: e.node, off: end - e.from };
|
|
108
|
+
}
|
|
109
|
+
if (!a || !b)
|
|
110
|
+
return null;
|
|
111
|
+
// Both ends must sit on the same rendered line-box. A range that spans
|
|
112
|
+
// a paragraph boundary paints a full-width underline across every line
|
|
113
|
+
// between its ends, blank ones included — which is what the reader sees
|
|
114
|
+
// as "a blue line over white space".
|
|
115
|
+
if (lineBox(a.node) !== lineBox(b.node))
|
|
116
|
+
return null;
|
|
117
|
+
try {
|
|
118
|
+
const r = doc.createRange();
|
|
119
|
+
r.setStart(a.node, a.off);
|
|
120
|
+
r.setEnd(b.node, b.off);
|
|
121
|
+
return r;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
8
127
|
export function initHarperLint(ed) {
|
|
9
128
|
let disposed = false;
|
|
10
129
|
let timer = null;
|
|
@@ -32,48 +151,6 @@ export function initHarperLint(ed) {
|
|
|
32
151
|
doc.getElementById(OVERLAY_ID)?.remove();
|
|
33
152
|
doc.getElementById(POPUP_ID)?.remove();
|
|
34
153
|
};
|
|
35
|
-
/** Blocks = top-level element children of the editable root (or the root
|
|
36
|
-
* itself when it has none). Quoted reply chains are the author's
|
|
37
|
-
* correspondent's text — skip them (also keeps lint cost proportional
|
|
38
|
-
* to what's actually being written). */
|
|
39
|
-
const collectBlocks = (root) => {
|
|
40
|
-
const kids = Array.from(root.children).filter((c) => c instanceof HTMLElement);
|
|
41
|
-
const blocks = (kids.length ? kids : [root]).filter(b => b.tagName !== "BLOCKQUOTE" && !b.closest("blockquote") && !b.querySelector("blockquote"));
|
|
42
|
-
return blocks;
|
|
43
|
-
};
|
|
44
|
-
/** Flatten a block to plain text + a map back to its text nodes. */
|
|
45
|
-
const blockText = (block, doc) => {
|
|
46
|
-
const walker = doc.createTreeWalker(block, NodeFilter.SHOW_TEXT);
|
|
47
|
-
const nodes = [];
|
|
48
|
-
let text = "";
|
|
49
|
-
for (let n = walker.nextNode(); n; n = walker.nextNode()) {
|
|
50
|
-
nodes.push({ node: n, from: text.length });
|
|
51
|
-
text += n.data;
|
|
52
|
-
}
|
|
53
|
-
return { text, nodes };
|
|
54
|
-
};
|
|
55
|
-
const spanToRange = (doc, nodes, start, end) => {
|
|
56
|
-
let a = null;
|
|
57
|
-
let b = null;
|
|
58
|
-
for (const e of nodes) {
|
|
59
|
-
const len = e.node.data.length;
|
|
60
|
-
if (!a && start >= e.from && start <= e.from + len)
|
|
61
|
-
a = { node: e.node, off: start - e.from };
|
|
62
|
-
if (end >= e.from && end <= e.from + len)
|
|
63
|
-
b = { node: e.node, off: end - e.from };
|
|
64
|
-
}
|
|
65
|
-
if (!a || !b)
|
|
66
|
-
return null;
|
|
67
|
-
try {
|
|
68
|
-
const r = doc.createRange();
|
|
69
|
-
r.setStart(a.node, a.off);
|
|
70
|
-
r.setEnd(b.node, b.off);
|
|
71
|
-
return r;
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
154
|
const paint = () => {
|
|
78
155
|
if (disposed)
|
|
79
156
|
return;
|
|
@@ -148,9 +225,15 @@ export function initHarperLint(ed) {
|
|
|
148
225
|
const tx = texts[i];
|
|
149
226
|
if (!tx)
|
|
150
227
|
return;
|
|
228
|
+
// The unfinished sentence under the caret is exempt: it is still
|
|
229
|
+
// being written, and half a sentence is not an error yet.
|
|
230
|
+
const caret = caretOffset(t.doc, tx.nodes);
|
|
231
|
+
const writingFrom = caret < 0 ? -1 : sentenceStart(tx.text, caret);
|
|
151
232
|
for (const l of lints || []) {
|
|
152
233
|
if (SKIP_KINDS.has(l.kind))
|
|
153
234
|
continue;
|
|
235
|
+
if (writingFrom >= 0 && l.end > writingFrom && l.start <= caret)
|
|
236
|
+
continue;
|
|
154
237
|
const range = spanToRange(t.doc, tx.nodes, l.start, l.end);
|
|
155
238
|
if (range)
|
|
156
239
|
next.push({ lint: l, range });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harper-lint.js","sourceRoot":"","sources":["harper-lint.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"harper-lint.js","sourceRoot":"","sources":["harper-lint.ts"],"names":[],"mappings":"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"}
|
|
@@ -26,6 +26,121 @@ const POPUP_ID = "harper-popup";
|
|
|
26
26
|
// its own suggestion menu) — double-underlining every typo helps no one.
|
|
27
27
|
const SKIP_KINDS = new Set(["Spelling"]);
|
|
28
28
|
|
|
29
|
+
/* ── Pure helpers ───────────────────────────────────────────────────────────
|
|
30
|
+
* Module scope, and exported, because they are the part with rules worth
|
|
31
|
+
* testing: where a line break falls in the extracted text, which spans may be
|
|
32
|
+
* painted, and which sentence the writer is still in the middle of. jsdom can
|
|
33
|
+
* exercise all three without an editor — tests/dom/harper-lint.test.ts.
|
|
34
|
+
*/
|
|
35
|
+
/** Blocks = top-level element children of the editable root (or the root
|
|
36
|
+
* itself when it has none). Quoted reply chains are the author's
|
|
37
|
+
* correspondent's text — skip them (also keeps lint cost proportional
|
|
38
|
+
* to what's actually being written). */
|
|
39
|
+
export const collectBlocks = (root: HTMLElement): HTMLElement[] => {
|
|
40
|
+
const kids = Array.from(root.children).filter((c): c is HTMLElement => c instanceof HTMLElement);
|
|
41
|
+
const blocks = (kids.length ? kids : [root]).filter(b =>
|
|
42
|
+
b.tagName !== "BLOCKQUOTE" && !b.closest("blockquote") && !b.querySelector("blockquote"));
|
|
43
|
+
return blocks;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Elements that end a line on screen. A reader sees a break here, so the
|
|
47
|
+
* text handed to the grammar engine has to contain one too. */
|
|
48
|
+
const LINE_BREAKING = new Set([
|
|
49
|
+
"BR", "P", "DIV", "LI", "UL", "OL", "TR", "TD", "TH", "TABLE", "PRE", "HR",
|
|
50
|
+
"BLOCKQUOTE", "H1", "H2", "H3", "H4", "H5", "H6", "SECTION", "ARTICLE", "FIGURE",
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Flatten a block to plain text + a map back to its text nodes.
|
|
55
|
+
*
|
|
56
|
+
* Line breaks are REAL characters in that text. Concatenating the text
|
|
57
|
+
* nodes alone glues the last word of one paragraph to the first word of the
|
|
58
|
+
* next, and Harper — correctly, for the string it was given — reports the
|
|
59
|
+
* seam: Bob 2026-08-27 forwarding a message got "`networkTo` should
|
|
60
|
+
* probably be written as `network To`" out of "…of the network" followed by
|
|
61
|
+
* "To: …". Worse, the lint's span then covers a junction that on screen is
|
|
62
|
+
* two blank lines, and getClientRects paints a full-width dotted underline
|
|
63
|
+
* across the whitespace — an error message pointing at nothing.
|
|
64
|
+
*
|
|
65
|
+
* The separators occupy offsets no text node owns, which is deliberate: a
|
|
66
|
+
* lint that starts or ends inside one cannot be placed, and spanToRange
|
|
67
|
+
* drops it rather than guessing.
|
|
68
|
+
*/
|
|
69
|
+
export const blockText = (block: HTMLElement, doc: Document): { text: string; nodes: { node: Text; from: number }[] } => {
|
|
70
|
+
const walker = doc.createTreeWalker(block, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT);
|
|
71
|
+
const nodes: { node: Text; from: number }[] = [];
|
|
72
|
+
let text = "";
|
|
73
|
+
let pendingBreak = false;
|
|
74
|
+
for (let n = walker.nextNode(); n; n = walker.nextNode()) {
|
|
75
|
+
if (n.nodeType === Node.ELEMENT_NODE) {
|
|
76
|
+
if (LINE_BREAKING.has((n as HTMLElement).tagName)) pendingBreak = true;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const data = (n as Text).data;
|
|
80
|
+
if (!data) continue;
|
|
81
|
+
if (pendingBreak && text) text += "\n";
|
|
82
|
+
pendingBreak = false;
|
|
83
|
+
nodes.push({ node: n as Text, from: text.length });
|
|
84
|
+
text += data;
|
|
85
|
+
}
|
|
86
|
+
return { text, nodes };
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/** The nearest thing that renders as its own line — used to refuse any
|
|
90
|
+
* squiggle whose two ends live in different ones. */
|
|
91
|
+
export const lineBox = (node: Node): Node => {
|
|
92
|
+
for (let e: Node = node; e; e = e.parentNode)
|
|
93
|
+
if (e.nodeType === Node.ELEMENT_NODE && LINE_BREAKING.has((e as HTMLElement).tagName)) return e;
|
|
94
|
+
return null;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Where the caret is inside this block's flattened text, or -1.
|
|
99
|
+
*
|
|
100
|
+
* A sentence is wrong until it is finished. Underlining the words someone
|
|
101
|
+
* is still typing — Bob 2026-08-27, "annoying ... appearing even as I type
|
|
102
|
+
* a sentence in progress" — is the editor arguing with a thought that isn't
|
|
103
|
+
* out yet.
|
|
104
|
+
*/
|
|
105
|
+
export const caretOffset = (doc: Document, nodes: { node: Text; from: number }[]): number => {
|
|
106
|
+
const sel = doc.getSelection?.();
|
|
107
|
+
if (!sel || !sel.focusNode) return -1;
|
|
108
|
+
for (const e of nodes)
|
|
109
|
+
if (e.node === sel.focusNode) return e.from + sel.focusOffset;
|
|
110
|
+
return -1;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/** Start of the sentence the caret sits in — everything after the last
|
|
114
|
+
* terminator that precedes it. */
|
|
115
|
+
export const sentenceStart = (text: string, caret: number): number => {
|
|
116
|
+
const before = text.slice(0, caret);
|
|
117
|
+
const m = before.match(/[.!?…\n][^.!?…\n]*$/);
|
|
118
|
+
return m ? caret - (m[0].length - 1) : 0;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const spanToRange = (doc: Document, nodes: { node: Text; from: number }[], start: number, end: number): Range | null => {
|
|
122
|
+
let a: { node: Text; off: number } | null = null;
|
|
123
|
+
let b: { node: Text; off: number } | null = null;
|
|
124
|
+
for (const e of nodes) {
|
|
125
|
+
const len = e.node.data.length;
|
|
126
|
+
if (!a && start >= e.from && start <= e.from + len) a = { node: e.node, off: start - e.from };
|
|
127
|
+
if (end >= e.from && end <= e.from + len) b = { node: e.node, off: end - e.from };
|
|
128
|
+
}
|
|
129
|
+
if (!a || !b) return null;
|
|
130
|
+
// Both ends must sit on the same rendered line-box. A range that spans
|
|
131
|
+
// a paragraph boundary paints a full-width underline across every line
|
|
132
|
+
// between its ends, blank ones included — which is what the reader sees
|
|
133
|
+
// as "a blue line over white space".
|
|
134
|
+
if (lineBox(a.node) !== lineBox(b.node)) return null;
|
|
135
|
+
try {
|
|
136
|
+
const r = doc.createRange();
|
|
137
|
+
r.setStart(a.node, a.off);
|
|
138
|
+
r.setEnd(b.node, b.off);
|
|
139
|
+
return r;
|
|
140
|
+
} catch { return null; }
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
|
|
29
144
|
export function initHarperLint(ed: MailxEditor): () => void {
|
|
30
145
|
let disposed = false;
|
|
31
146
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -55,46 +170,6 @@ export function initHarperLint(ed: MailxEditor): () => void {
|
|
|
55
170
|
doc.getElementById(POPUP_ID)?.remove();
|
|
56
171
|
};
|
|
57
172
|
|
|
58
|
-
/** Blocks = top-level element children of the editable root (or the root
|
|
59
|
-
* itself when it has none). Quoted reply chains are the author's
|
|
60
|
-
* correspondent's text — skip them (also keeps lint cost proportional
|
|
61
|
-
* to what's actually being written). */
|
|
62
|
-
const collectBlocks = (root: HTMLElement): HTMLElement[] => {
|
|
63
|
-
const kids = Array.from(root.children).filter((c): c is HTMLElement => c instanceof HTMLElement);
|
|
64
|
-
const blocks = (kids.length ? kids : [root]).filter(b =>
|
|
65
|
-
b.tagName !== "BLOCKQUOTE" && !b.closest("blockquote") && !b.querySelector("blockquote"));
|
|
66
|
-
return blocks;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
/** Flatten a block to plain text + a map back to its text nodes. */
|
|
70
|
-
const blockText = (block: HTMLElement, doc: Document): { text: string; nodes: { node: Text; from: number }[] } => {
|
|
71
|
-
const walker = doc.createTreeWalker(block, NodeFilter.SHOW_TEXT);
|
|
72
|
-
const nodes: { node: Text; from: number }[] = [];
|
|
73
|
-
let text = "";
|
|
74
|
-
for (let n = walker.nextNode(); n; n = walker.nextNode()) {
|
|
75
|
-
nodes.push({ node: n as Text, from: text.length });
|
|
76
|
-
text += (n as Text).data;
|
|
77
|
-
}
|
|
78
|
-
return { text, nodes };
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const spanToRange = (doc: Document, nodes: { node: Text; from: number }[], start: number, end: number): Range | null => {
|
|
82
|
-
let a: { node: Text; off: number } | null = null;
|
|
83
|
-
let b: { node: Text; off: number } | null = null;
|
|
84
|
-
for (const e of nodes) {
|
|
85
|
-
const len = e.node.data.length;
|
|
86
|
-
if (!a && start >= e.from && start <= e.from + len) a = { node: e.node, off: start - e.from };
|
|
87
|
-
if (end >= e.from && end <= e.from + len) b = { node: e.node, off: end - e.from };
|
|
88
|
-
}
|
|
89
|
-
if (!a || !b) return null;
|
|
90
|
-
try {
|
|
91
|
-
const r = doc.createRange();
|
|
92
|
-
r.setStart(a.node, a.off);
|
|
93
|
-
r.setEnd(b.node, b.off);
|
|
94
|
-
return r;
|
|
95
|
-
} catch { return null; }
|
|
96
|
-
};
|
|
97
|
-
|
|
98
173
|
const paint = (): void => {
|
|
99
174
|
if (disposed) return;
|
|
100
175
|
const t = target();
|
|
@@ -156,8 +231,13 @@ export function initHarperLint(ed: MailxEditor): () => void {
|
|
|
156
231
|
(res?.blocks || []).forEach((lints, i) => {
|
|
157
232
|
const tx = texts[i];
|
|
158
233
|
if (!tx) return;
|
|
234
|
+
// The unfinished sentence under the caret is exempt: it is still
|
|
235
|
+
// being written, and half a sentence is not an error yet.
|
|
236
|
+
const caret = caretOffset(t.doc, tx.nodes);
|
|
237
|
+
const writingFrom = caret < 0 ? -1 : sentenceStart(tx.text, caret);
|
|
159
238
|
for (const l of lints || []) {
|
|
160
239
|
if (SKIP_KINDS.has(l.kind)) continue;
|
|
240
|
+
if (writingFrom >= 0 && l.end > writingFrom && l.start <= caret) continue;
|
|
161
241
|
const range = spanToRange(t.doc, tx.nodes, l.start, l.end);
|
|
162
242
|
if (range) next.push({ lint: l, range });
|
|
163
243
|
}
|
package/client/lib/rmf-tiny.js
CHANGED
|
@@ -157,6 +157,14 @@ export async function createTinyMceEditor(container, opts = {}) {
|
|
|
157
157
|
// ways that produce a blank body, so it's not in the plugin list.
|
|
158
158
|
menu: {
|
|
159
159
|
view: { title: "View", items: "rmfsource | visualaid visualchars visualblocks | preview | zoomIn zoomOut zoomReset" },
|
|
160
|
+
// Insert menu — TinyMCE's default list minus the items whose
|
|
161
|
+
// plugins we don't load (media, pagebreak, anchor, accordion),
|
|
162
|
+
// plus the two quoting items. Listed in full because naming a
|
|
163
|
+
// menu REPLACES its item list; the defaults do not carry over.
|
|
164
|
+
insert: {
|
|
165
|
+
title: "Insert",
|
|
166
|
+
items: "image link inserttable | rmfblockquote rmfquotepaste | rmfcode codesample charmap emoticons | hr nonbreaking insertdatetime",
|
|
167
|
+
},
|
|
160
168
|
},
|
|
161
169
|
// Disable the "Get all features" upsell badge that TinyMCE 6+
|
|
162
170
|
// injects automatically when no premium plugins are listed.
|
|
@@ -673,6 +681,21 @@ export async function createTinyMceEditor(container, opts = {}) {
|
|
|
673
681
|
text: "Code block…",
|
|
674
682
|
onAction: openCodeDialog,
|
|
675
683
|
});
|
|
684
|
+
// Quoting, on the Insert menu (Bob 2026-08-27: "it would be
|
|
685
|
+
// nice if the insert had blockquote … I currently go add
|
|
686
|
+
// <blockquote> manually"). Two items, because quoting is two
|
|
687
|
+
// different acts: wrap what is already there, or bring in
|
|
688
|
+
// someone else's words as a quote in one step.
|
|
689
|
+
ed.ui.registry.addMenuItem("rmfblockquote", {
|
|
690
|
+
icon: "quote",
|
|
691
|
+
text: "Blockquote",
|
|
692
|
+
onAction: () => ed.execCommand("mceBlockQuote"),
|
|
693
|
+
});
|
|
694
|
+
ed.ui.registry.addMenuItem("rmfquotepaste", {
|
|
695
|
+
icon: "quote",
|
|
696
|
+
text: "Quoted text (paste)…",
|
|
697
|
+
onAction: () => { void pasteAsQuote(ed); },
|
|
698
|
+
});
|
|
676
699
|
// View HTML source, caret-positioned (Bob 2026-07-18).
|
|
677
700
|
// Registered as a menu item (View menu) AND reachable from the
|
|
678
701
|
// compose NATIVE right-click menu via editor.showSource() —
|
|
@@ -1004,6 +1027,68 @@ export async function createTinyMceEditor(container, opts = {}) {
|
|
|
1004
1027
|
nativeEditor: editor,
|
|
1005
1028
|
};
|
|
1006
1029
|
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Paste the clipboard into a blockquote, in one step.
|
|
1032
|
+
*
|
|
1033
|
+
* The manual version is: paste, select what you pasted, press the quote
|
|
1034
|
+
* button — or, when the paste brought its own block structure, open the HTML
|
|
1035
|
+
* source and type `<blockquote>` around it by hand (Bob 2026-08-27).
|
|
1036
|
+
*
|
|
1037
|
+
* Reads text/html when the clipboard has it, so formatting and links survive;
|
|
1038
|
+
* falls back to text/plain, escaped, with blank lines becoming paragraphs and
|
|
1039
|
+
* single breaks `<br>`. Browsers and Word wrap their HTML flavour in a whole
|
|
1040
|
+
* document plus `<!--StartFragment-->` markers; only the fragment is wanted.
|
|
1041
|
+
*
|
|
1042
|
+
* Clipboard READ is permission-gated and can legitimately be refused. That is
|
|
1043
|
+
* reported in the editor's own notification bar rather than swallowed — a menu
|
|
1044
|
+
* item that silently does nothing is worse than no menu item.
|
|
1045
|
+
*/
|
|
1046
|
+
async function pasteAsQuote(ed) {
|
|
1047
|
+
const notify = (text, type) => {
|
|
1048
|
+
try {
|
|
1049
|
+
ed.notificationManager.open({ text, type, timeout: 4000 });
|
|
1050
|
+
}
|
|
1051
|
+
catch { /* editor closing */ }
|
|
1052
|
+
};
|
|
1053
|
+
const escape = (s) => s
|
|
1054
|
+
.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1055
|
+
/** Just the copied fragment: no <html>/<head>/<body>, no Office markers. */
|
|
1056
|
+
const fragment = (html) => {
|
|
1057
|
+
const marked = html.match(/<!--\s*StartFragment\s*-->([\s\S]*?)<!--\s*EndFragment\s*-->/i);
|
|
1058
|
+
const inner = marked ? marked[1] : html.replace(/[\s\S]*<body[^>]*>|<\/body>[\s\S]*/gi, "");
|
|
1059
|
+
return inner.trim();
|
|
1060
|
+
};
|
|
1061
|
+
let html = "";
|
|
1062
|
+
try {
|
|
1063
|
+
const clip = navigator.clipboard;
|
|
1064
|
+
if (clip?.read) {
|
|
1065
|
+
const items = await clip.read();
|
|
1066
|
+
for (const item of items) {
|
|
1067
|
+
if (!item.types.includes("text/html"))
|
|
1068
|
+
continue;
|
|
1069
|
+
html = fragment(await (await item.getType("text/html")).text());
|
|
1070
|
+
break;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
if (!html) {
|
|
1074
|
+
const text = await navigator.clipboard?.readText?.() || "";
|
|
1075
|
+
if (text.trim()) {
|
|
1076
|
+
html = text.trim().split(/\n\s*\n/)
|
|
1077
|
+
.map(para => `<p>${escape(para).replace(/\n/g, "<br>")}</p>`).join("");
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
catch (e) {
|
|
1082
|
+
notify(`Could not read the clipboard: ${e?.message || e}`, "error");
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
if (!html) {
|
|
1086
|
+
notify("There is nothing on the clipboard to quote.", "warning");
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
ed.insertContent(`<blockquote>${html}</blockquote>`);
|
|
1090
|
+
ed.undoManager.add(); // snapshot + fire AddUndo → draft save
|
|
1091
|
+
}
|
|
1007
1092
|
/** View/edit the full HTML source with the textarea caret placed at the
|
|
1008
1093
|
* same spot as the current DOM caret (Bob 2026-07-18: "show source that
|
|
1009
1094
|
* positions me at the current position in the source"). Drops a private-use
|
package/npmchanges.md
CHANGED
|
@@ -868,3 +868,85 @@ seconds ago when it had been up for minutes.
|
|
|
868
868
|
Ships separately from the activate fix in 1.2.284 — verified that release did
|
|
869
869
|
not already contain it.
|
|
870
870
|
|
|
871
|
+
## v1.2.286 — 2026-08-27
|
|
872
|
+
|
|
873
|
+
Spam banner: classify the rules instead of reusing the delivery score
|
|
874
|
+
|
|
875
|
+
The trust banner asked "is this forged" and answered it with SpamAssassin's
|
|
876
|
+
bottom line, a number tuned to answer "should this be delivered". Four
|
|
877
|
+
newsletters Bob subscribed to (Constant Contact, Adobe MAX, Mokin, ActBlue)
|
|
878
|
+
scored 2.2 to 10.9 and two of them raised "This message is not what it says
|
|
879
|
+
it is" — on mail that passes DMARC aligned on a published reject policy,
|
|
880
|
+
where that headline is factually false. What scored them was bulk-mail rules
|
|
881
|
+
and third-party blocklist entries about hosts they merely link to: Mokin's
|
|
882
|
+
6.0 URIBL_SBL is the Spamhaus listing of the IP of the nameserver of a linked
|
|
883
|
+
domain, fourth-order guilt by association, more than the whole 4.5 threshold
|
|
884
|
+
on its own.
|
|
885
|
+
|
|
886
|
+
trust.ts now reads the `tests=` list that was sitting in the same header and
|
|
887
|
+
sorts the rules into what they are evidence OF — forgery, third-party
|
|
888
|
+
association, bulk mail, or unclassified — and reads the topmost
|
|
889
|
+
Authentication-Results for a DMARC-aligned pass. Severity follows the rules,
|
|
890
|
+
never the number:
|
|
891
|
+
|
|
892
|
+
- a forgery rule fired, or the server itself recorded an SPF/DKIM/DMARC
|
|
893
|
+
failure -> danger, unchanged. Phishing and malware URL listings count as
|
|
894
|
+
identity evidence, so a lookalike domain with its own valid DKIM cannot
|
|
895
|
+
buy silence.
|
|
896
|
+
- sender DMARC-proved and every rule that scored is one this file can name,
|
|
897
|
+
or the whole score is bulk rules -> info: a new neutral severity with no
|
|
898
|
+
headline and no "do not open anything" footer.
|
|
899
|
+
- anything else -> caution, including a proved sender whose score came from
|
|
900
|
+
rules with no class. Unknown is never a reason to go quiet.
|
|
901
|
+
|
|
902
|
+
Findings and the score chip now come from one analysis, so they cannot
|
|
903
|
+
disagree; the chip goes grey rather than red when the sender is proved or the
|
|
904
|
+
score is all bulk, and both name their evidence ("8.0 SH_DBL_HEADERS (A domain
|
|
905
|
+
found in headers is listed in DBL)") instead of showing a bare number. The
|
|
906
|
+
`threshold/2` chip floor is untouched — see docs/spam-false-positives.md on
|
|
907
|
+
why tuning it is not the fix.
|
|
908
|
+
|
|
909
|
+
Over 20,291 messages in the store, 1,131 were flagged spam and every one of
|
|
910
|
+
them used to get the red banner: now 294 do, 672 are caution, 165 are quiet
|
|
911
|
+
notes. Tests cover all four sample messages verbatim, plus the two ways the
|
|
912
|
+
proof could be gamed (a forged Authentication-Results below the server's own,
|
|
913
|
+
and one naming a different From).
|
|
914
|
+
|
|
915
|
+
TODO C165 first cut. Sender trust via allowlist.jsonc still open.
|
|
916
|
+
|
|
917
|
+
## v1.2.287 — 2026-08-27
|
|
918
|
+
|
|
919
|
+
Grammar squiggles: stop inventing words, stop underlining white space
|
|
920
|
+
|
|
921
|
+
Three complaints from one compose session (Bob 2026-08-27, forwarding a
|
|
922
|
+
message), all of them the checker arguing with prose that was fine:
|
|
923
|
+
|
|
924
|
+
1. "keypoint". Harper's CompoundNouns rule fires on any noun+noun pair whose
|
|
925
|
+
joined form is in its dictionary, so "One key point in my recent efforts"
|
|
926
|
+
came back as an error suggesting a word he did not write. Disabled in the
|
|
927
|
+
daemon next to AvoidCurses — same objection as that one: the tool
|
|
928
|
+
proofreads, it does not get a vote on word choice. Its siblings (MergeWords,
|
|
929
|
+
MakeupCompoundNoun, OvertimeCompoundNoun, ThereAfterCompound,
|
|
930
|
+
EasyGoingCompoundAdjective) are named in a comment there, one line each to
|
|
931
|
+
turn off if they start nagging.
|
|
932
|
+
|
|
933
|
+
2. A blue dotted line across two blank lines. blockText concatenated a block's
|
|
934
|
+
text nodes with no separator, so the last word of a paragraph was glued to
|
|
935
|
+
the first word of the next: "…of the network" + "To: x@y.com" reached Harper
|
|
936
|
+
as "networkTo", and it reported — correctly, for the string it was given —
|
|
937
|
+
that the word should be split. The span then covered a junction that on
|
|
938
|
+
screen is two empty lines, and getClientRects painted a full-width underline
|
|
939
|
+
over the whitespace. Verified both halves against the real engine: gluing
|
|
940
|
+
produces `networkTo` and `FrankstonSubject` lints that vanish once the
|
|
941
|
+
breaks are there. blockText now emits "\n" at every line-breaking element,
|
|
942
|
+
and spanToRange refuses any span whose ends sit in different line boxes, so
|
|
943
|
+
no squiggle can ever be drawn across a paragraph boundary again.
|
|
944
|
+
|
|
945
|
+
3. Lints on the sentence being typed. The unfinished sentence under the caret
|
|
946
|
+
is now exempt — a thought that is not out yet is not an error.
|
|
947
|
+
|
|
948
|
+
The pure helpers (collectBlocks, blockText, lineBox, caretOffset,
|
|
949
|
+
sentenceStart, spanToRange) moved to module scope and are exported, because
|
|
950
|
+
they hold the decisions worth testing; tests/dom/harper-lint.test.ts covers all
|
|
951
|
+
three rules in jsdom, using the forward markup client/app.ts actually builds.
|
|
952
|
+
|