@bobfrankston/rmfmail 1.2.152 → 1.2.154
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 +1 -0
- package/bin/mailx.js.map +1 -1
- package/bin/mailx.ts +1 -0
- package/client/app.bundle.js +3 -2
- package/client/app.bundle.js.map +2 -2
- package/client/components/message-list.js +8 -2
- package/client/components/message-list.js.map +1 -1
- package/client/components/message-list.ts +8 -2
- package/client/compose/compose.bundle.js +81 -1
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/compose/compose.js +11 -0
- package/client/compose/compose.js.map +1 -1
- package/client/compose/compose.ts +8 -0
- package/client/compose/editor.js.map +1 -1
- package/client/compose/editor.ts +3 -0
- package/client/lib/rmf-tiny.js +95 -1
- package/package.json +3 -3
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-68532 → node_modules.npmglobalize-stash-31792}/.package-lock.json +0 -0
package/client/lib/rmf-tiny.js
CHANGED
|
@@ -134,7 +134,7 @@ export async function createTinyMceEditor(container, opts = {}) {
|
|
|
134
134
|
// window; TinyMCE's fullscreen plugin re-positions the container in
|
|
135
135
|
// ways that produce a blank body, so it's not in the plugin list.
|
|
136
136
|
menu: {
|
|
137
|
-
view: { title: "View", items: "
|
|
137
|
+
view: { title: "View", items: "rmfsource | visualaid visualchars visualblocks | preview | zoomIn zoomOut zoomReset" },
|
|
138
138
|
},
|
|
139
139
|
// Disable the "Get all features" upsell badge that TinyMCE 6+
|
|
140
140
|
// injects automatically when no premium plugins are listed.
|
|
@@ -557,6 +557,17 @@ export async function createTinyMceEditor(container, opts = {}) {
|
|
|
557
557
|
text: "Code block…",
|
|
558
558
|
onAction: openCodeDialog,
|
|
559
559
|
});
|
|
560
|
+
// View HTML source, caret-positioned (Bob 2026-07-18).
|
|
561
|
+
// Registered as a menu item (View menu) AND reachable from the
|
|
562
|
+
// compose NATIVE right-click menu via editor.showSource() —
|
|
563
|
+
// TinyMCE's own contextmenu is off (native menu carries
|
|
564
|
+
// spellcheck suggestions), so the right-click path is wired in
|
|
565
|
+
// mailx compose.ts, not here. openSourceDialog is module-scope.
|
|
566
|
+
ed.ui.registry.addMenuItem("rmfsource", {
|
|
567
|
+
icon: "sourcecode",
|
|
568
|
+
text: "HTML source…",
|
|
569
|
+
onAction: () => openSourceDialog(ed),
|
|
570
|
+
});
|
|
560
571
|
ed.ui.registry.addMenuItem("zoomIn", {
|
|
561
572
|
text: "Zoom in", shortcut: "Ctrl+=",
|
|
562
573
|
onAction: () => bumpZoom(ZOOM_STEP),
|
|
@@ -873,7 +884,90 @@ export async function createTinyMceEditor(container, opts = {}) {
|
|
|
873
884
|
get root() { return editor.getContainer(); },
|
|
874
885
|
on(event, handler) { editor.on(event, handler); },
|
|
875
886
|
off(event, handler) { editor.off(event, handler); },
|
|
887
|
+
showSource() { openSourceDialog(editor); },
|
|
876
888
|
nativeEditor: editor,
|
|
877
889
|
};
|
|
878
890
|
}
|
|
891
|
+
/** View/edit the full HTML source with the textarea caret placed at the
|
|
892
|
+
* same spot as the current DOM caret (Bob 2026-07-18: "show source that
|
|
893
|
+
* positions me at the current position in the source"). Drops a private-use
|
|
894
|
+
* sentinel char at the caret, serializes, finds the sentinel's byte offset,
|
|
895
|
+
* strips it from both the DOM (pristine again) and the displayed source,
|
|
896
|
+
* then opens a textarea dialog scrolled + selected to that offset. Save
|
|
897
|
+
* round-trips the edited source back through setContent. */
|
|
898
|
+
function openSourceDialog(editor) {
|
|
899
|
+
const SENTINEL = ""; // private-use area — never in real mail text
|
|
900
|
+
let offset = -1;
|
|
901
|
+
try {
|
|
902
|
+
// Insert the sentinel at the caret without polluting undo history.
|
|
903
|
+
const insert = () => editor.selection.setContent(SENTINEL);
|
|
904
|
+
if (typeof editor.undoManager?.ignore === "function")
|
|
905
|
+
editor.undoManager.ignore(insert);
|
|
906
|
+
else
|
|
907
|
+
insert();
|
|
908
|
+
const marked = editor.getContent();
|
|
909
|
+
offset = marked.indexOf(SENTINEL);
|
|
910
|
+
// Remove the sentinel char from the DOM directly — a targeted
|
|
911
|
+
// nodeValue edit keeps the rest of the document (and the user's real
|
|
912
|
+
// caret context) untouched, unlike a full setContent.
|
|
913
|
+
try {
|
|
914
|
+
const body = editor.getBody();
|
|
915
|
+
const doc = editor.getDoc();
|
|
916
|
+
const walk = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT, null);
|
|
917
|
+
let n;
|
|
918
|
+
while ((n = walk.nextNode())) {
|
|
919
|
+
if (n.nodeValue && n.nodeValue.indexOf(SENTINEL) >= 0) {
|
|
920
|
+
n.nodeValue = n.nodeValue.replace(SENTINEL, "");
|
|
921
|
+
break;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
catch { /* sentinel cleanup is best-effort */ }
|
|
926
|
+
}
|
|
927
|
+
catch { /* fall through to a plain source view at offset 0 */ }
|
|
928
|
+
// Display the pristine source (sentinel already stripped from the DOM);
|
|
929
|
+
// `offset` is where the sentinel sat, i.e. the caret position.
|
|
930
|
+
const source = editor.getContent();
|
|
931
|
+
const caretAt = offset >= 0 ? Math.min(offset, source.length) : source.length;
|
|
932
|
+
editor.windowManager.open({
|
|
933
|
+
title: "HTML source",
|
|
934
|
+
size: "large",
|
|
935
|
+
body: {
|
|
936
|
+
type: "panel",
|
|
937
|
+
items: [{ type: "textarea", name: "src", label: "", maximized: true }],
|
|
938
|
+
},
|
|
939
|
+
initialData: { src: source },
|
|
940
|
+
buttons: [
|
|
941
|
+
{ type: "cancel", text: "Cancel" },
|
|
942
|
+
{ type: "submit", text: "Save", primary: true },
|
|
943
|
+
],
|
|
944
|
+
onSubmit: (api) => {
|
|
945
|
+
const data = api.getData();
|
|
946
|
+
editor.undoManager.transact(() => editor.setContent(data.src || ""));
|
|
947
|
+
editor.nodeChanged();
|
|
948
|
+
api.close();
|
|
949
|
+
},
|
|
950
|
+
});
|
|
951
|
+
// Place the textarea caret at the DOM-caret offset. The dialog's textarea
|
|
952
|
+
// isn't exposed through the dialog API, so reach it via the DOM once the
|
|
953
|
+
// dialog has painted (rAF ×2 — TinyMCE focuses it after OpenWindow).
|
|
954
|
+
const win = (editor.getWin && editor.getWin()) || window;
|
|
955
|
+
const raf = win.requestAnimationFrame || ((f) => setTimeout(f, 16));
|
|
956
|
+
raf(() => raf(() => {
|
|
957
|
+
const tas = Array.from(document.querySelectorAll(".tox-dialog textarea"));
|
|
958
|
+
const ta = tas[tas.length - 1]; // the dialog just opened is the last
|
|
959
|
+
if (!ta)
|
|
960
|
+
return;
|
|
961
|
+
try {
|
|
962
|
+
ta.focus();
|
|
963
|
+
ta.setSelectionRange(caretAt, caretAt);
|
|
964
|
+
// Scroll the caret line into view: approximate by line count.
|
|
965
|
+
const before = ta.value.slice(0, caretAt);
|
|
966
|
+
const line = before.split("\n").length - 1;
|
|
967
|
+
const lineH = parseFloat(getComputedStyle(ta).lineHeight) || 18;
|
|
968
|
+
ta.scrollTop = Math.max(0, (line * lineH) - ta.clientHeight / 2);
|
|
969
|
+
}
|
|
970
|
+
catch { /* not the source textarea */ }
|
|
971
|
+
}));
|
|
972
|
+
}
|
|
879
973
|
//# sourceMappingURL=adapter.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/rmfmail",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.154",
|
|
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.400",
|
|
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.41",
|
|
47
47
|
"@bobfrankston/smtp-direct": "^0.1.9",
|
|
48
48
|
"@bobfrankston/tcp-transport": "^0.1.8",
|
|
49
49
|
"@capacitor/android": "^8.3.0",
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
"@bobfrankston/msger": "^0.1.400",
|
|
122
122
|
"@bobfrankston/node-tcp-transport": "^0.1.10",
|
|
123
123
|
"@bobfrankston/oauthsupport": "^1.0.34",
|
|
124
|
-
"@bobfrankston/rmf-tiny": "^0.1.
|
|
124
|
+
"@bobfrankston/rmf-tiny": "^0.1.41",
|
|
125
125
|
"@bobfrankston/smtp-direct": "^0.1.9",
|
|
126
126
|
"@bobfrankston/tcp-transport": "^0.1.8",
|
|
127
127
|
"@capacitor/android": "^8.3.0",
|