@bobfrankston/rmfmail 1.2.221 → 1.2.222
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/bin/mailx.js +30 -18
- package/bin/mailx.js.map +1 -1
- package/bin/mailx.ts +31 -18
- package/client/app.bundle.js +11 -0
- package/client/app.bundle.js.map +2 -2
- package/client/app.js +21 -0
- package/client/app.js.map +1 -1
- package/client/app.ts +21 -0
- package/client/compose/compose.bundle.js +65 -1
- package/client/compose/compose.bundle.js.map +3 -3
- package/client/compose/compose.js +90 -0
- package/client/compose/compose.js.map +1 -1
- package/client/compose/compose.ts +72 -0
- package/client/lib/rmf-tiny.js +7 -1
- package/package.json +3 -3
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-39780 → node_modules.npmglobalize-stash-104584}/.package-lock.json +0 -0
|
@@ -2178,6 +2178,77 @@ async function popoutHandoff(): Promise<void> {
|
|
|
2178
2178
|
}
|
|
2179
2179
|
window.addEventListener("compose-popout", () => { void popoutHandoff(); });
|
|
2180
2180
|
|
|
2181
|
+
// ── Native context-menu clipboard commands ──
|
|
2182
|
+
// Chromium drops Cut/Copy from its default context menu whenever the caret is
|
|
2183
|
+
// collapsed — and right-clicking a word IS a collapsed caret — so the menu you
|
|
2184
|
+
// get while editing has no Cut and no Copy (Bob 2026-08-07). These entries
|
|
2185
|
+
// expand to the word under the caret first, matching what the formatting
|
|
2186
|
+
// entries already do, and go through the async Clipboard API rather than the
|
|
2187
|
+
// deprecated document.execCommand (which needs a user gesture the native menu
|
|
2188
|
+
// callback doesn't carry).
|
|
2189
|
+
//
|
|
2190
|
+
// Clipboard READ is permission-gated in a way write is not, so paste can
|
|
2191
|
+
// legitimately be refused. Say so out loud rather than no-op'ing — a menu item
|
|
2192
|
+
// that silently does nothing is worse than one that isn't there.
|
|
2193
|
+
async function runClipboardCommand(id: "cut" | "copy" | "paste"): Promise<void> {
|
|
2194
|
+
const ne: any = (editor as any)?.nativeEditor;
|
|
2195
|
+
const isTiny = !!ne && typeof ne.execCommand === "function" && !!ne.selection;
|
|
2196
|
+
const label = id === "cut" ? "Cut" : id === "copy" ? "Copy" : "Paste";
|
|
2197
|
+
const shortcut = id === "cut" ? "Ctrl+X" : id === "copy" ? "Ctrl+C" : "Ctrl+V";
|
|
2198
|
+
try {
|
|
2199
|
+
if (id === "paste") {
|
|
2200
|
+
let html = "";
|
|
2201
|
+
let text = "";
|
|
2202
|
+
if (navigator.clipboard?.read) {
|
|
2203
|
+
for (const item of await navigator.clipboard.read()) {
|
|
2204
|
+
if (item.types.includes("text/html")) html = await (await item.getType("text/html")).text();
|
|
2205
|
+
if (item.types.includes("text/plain")) text = await (await item.getType("text/plain")).text();
|
|
2206
|
+
}
|
|
2207
|
+
} else {
|
|
2208
|
+
text = await navigator.clipboard.readText();
|
|
2209
|
+
}
|
|
2210
|
+
// Prefer HTML so formatting survives, exactly as Ctrl+V would.
|
|
2211
|
+
// Plain text has to be escaped and line-broken by hand — inserted
|
|
2212
|
+
// raw it would be parsed as markup.
|
|
2213
|
+
const content = html || (text
|
|
2214
|
+
? text.replace(/[&<>]/g, c => ({ "&": "&", "<": "<", ">": ">" }[c] as string)).replace(/\r?\n/g, "<br>")
|
|
2215
|
+
: "");
|
|
2216
|
+
if (!content) return;
|
|
2217
|
+
if (isTiny) ne.execCommand("mceInsertContent", false, content);
|
|
2218
|
+
else if (ne && typeof ne.getSelection === "function") {
|
|
2219
|
+
const sel = ne.getSelection(true);
|
|
2220
|
+
ne.clipboard?.dangerouslyPasteHTML?.(sel?.index ?? 0, content);
|
|
2221
|
+
} else throw new Error("no editor");
|
|
2222
|
+
return;
|
|
2223
|
+
}
|
|
2224
|
+
// Cut / Copy. Expand a collapsed caret to the word under it so the
|
|
2225
|
+
// command has something to act on.
|
|
2226
|
+
if (isTiny) {
|
|
2227
|
+
try { if (ne.selection.isCollapsed() && typeof ne.selection.expand === "function") ne.selection.expand({ type: "word" }); } catch { /* older TinyMCE */ }
|
|
2228
|
+
}
|
|
2229
|
+
const html = isTiny ? ne.selection.getContent({ format: "html" }) : "";
|
|
2230
|
+
const text = isTiny ? ne.selection.getContent({ format: "text" }) : (window.getSelection()?.toString() || "");
|
|
2231
|
+
if (!text && !html) return;
|
|
2232
|
+
if (html && typeof ClipboardItem === "function" && navigator.clipboard?.write) {
|
|
2233
|
+
await navigator.clipboard.write([new ClipboardItem({
|
|
2234
|
+
"text/html": new Blob([html], { type: "text/html" }),
|
|
2235
|
+
"text/plain": new Blob([text], { type: "text/plain" }),
|
|
2236
|
+
})]);
|
|
2237
|
+
} else {
|
|
2238
|
+
await navigator.clipboard.writeText(text);
|
|
2239
|
+
}
|
|
2240
|
+
if (id === "cut") {
|
|
2241
|
+
if (isTiny) ne.execCommand("Delete");
|
|
2242
|
+
else if (ne && typeof ne.deleteText === "function") {
|
|
2243
|
+
const sel = ne.getSelection();
|
|
2244
|
+
if (sel?.length) ne.deleteText(sel.index, sel.length);
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
} catch (e: any) {
|
|
2248
|
+
showDraftStatus(`${label} failed: ${e?.message || e}. ${shortcut} still works.`, true);
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2181
2252
|
// ── Native context-menu app commands ──
|
|
2182
2253
|
// msger appends app items to the WEBVIEW's native right-click menu (the
|
|
2183
2254
|
// native menu must stay — Chromium's spellcheck suggestions exist only
|
|
@@ -2193,6 +2264,7 @@ window.addEventListener("compose-popout", () => { void popoutHandoff(); });
|
|
|
2193
2264
|
try { (editor as any)?.showSource?.(); } catch { /* non-TinyMCE engine */ }
|
|
2194
2265
|
return;
|
|
2195
2266
|
}
|
|
2267
|
+
if (id === "cut" || id === "copy" || id === "paste") { void runClipboardCommand(id); return; }
|
|
2196
2268
|
const ne: any = (editor as any)?.nativeEditor;
|
|
2197
2269
|
if (!ne) return;
|
|
2198
2270
|
const isTiny = typeof ne.execCommand === "function" && ne.selection;
|
package/client/lib/rmf-tiny.js
CHANGED
|
@@ -124,7 +124,13 @@ export async function createTinyMceEditor(container, opts = {}) {
|
|
|
124
124
|
// 2026-06-01). The paste_* OPTIONS below remain valid (core).
|
|
125
125
|
plugins: "lists advlist link table code codesample image searchreplace autolink wordcount emoticons charmap insertdatetime quickbars nonbreaking directionality help",
|
|
126
126
|
toolbar: [
|
|
127
|
-
|
|
127
|
+
// fontsize on the main bar (Bob 2026-08-07: "there should also
|
|
128
|
+
// be font sizing on the tool bar"). `fontsize` is the v6+ name
|
|
129
|
+
// for v5's `fontsizeselect`; it renders the size dropdown and
|
|
130
|
+
// reflects the size at the caret. The Format ▸ Font sizes
|
|
131
|
+
// submenu had it all along, which is two clicks deeper than a
|
|
132
|
+
// size control has any business being.
|
|
133
|
+
"undo redo | fontsize | bold italic underline strikethrough | forecolor backcolor",
|
|
128
134
|
// blockquote on the main bar (was quickbar-only) — Bob
|
|
129
135
|
// 2026-07-28: pasted/inserted text needs a one-click quote.
|
|
130
136
|
"blockquote bullist numlist outdent indent | link table image code rmfcode | emoticons charmap | help",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/rmfmail",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.222",
|
|
4
4
|
"description": "Local-first email client with IMAP sync and standalone native app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/mailx.js",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@bobfrankston/msger": "^0.1.422",
|
|
44
44
|
"@bobfrankston/node-tcp-transport": "^0.1.10",
|
|
45
45
|
"@bobfrankston/oauthsupport": "^1.0.34",
|
|
46
|
-
"@bobfrankston/rmf-tiny": "^0.1.
|
|
46
|
+
"@bobfrankston/rmf-tiny": "^0.1.44",
|
|
47
47
|
"@bobfrankston/smtp-direct": "^0.1.9",
|
|
48
48
|
"@bobfrankston/tcp-transport": "^0.1.8",
|
|
49
49
|
"@capacitor/android": "^8.3.0",
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"@bobfrankston/msger": "^0.1.422",
|
|
124
124
|
"@bobfrankston/node-tcp-transport": "^0.1.10",
|
|
125
125
|
"@bobfrankston/oauthsupport": "^1.0.34",
|
|
126
|
-
"@bobfrankston/rmf-tiny": "^0.1.
|
|
126
|
+
"@bobfrankston/rmf-tiny": "^0.1.44",
|
|
127
127
|
"@bobfrankston/smtp-direct": "^0.1.9",
|
|
128
128
|
"@bobfrankston/tcp-transport": "^0.1.8",
|
|
129
129
|
"@capacitor/android": "^8.3.0",
|