@bobfrankston/rmfmail 1.1.202 → 1.1.204
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/client/app.bundle.js +5 -1
- package/client/app.bundle.js.map +2 -2
- package/client/app.js +10 -2
- package/client/app.js.map +1 -1
- package/client/app.ts +10 -2
- package/client/compose/compose.bundle.js +100 -25
- package/client/compose/compose.bundle.js.map +3 -3
- package/client/lib/rmf-tiny.js +121 -25
- package/package.json +1 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-14408 → node_modules.npmglobalize-stash-41028}/.package-lock.json +0 -0
package/client/app.ts
CHANGED
|
@@ -3168,9 +3168,17 @@ document.addEventListener("keydown", (e) => {
|
|
|
3168
3168
|
e.preventDefault();
|
|
3169
3169
|
deleteSelection();
|
|
3170
3170
|
}
|
|
3171
|
-
// Ctrl+Z = pop the top of the undo stack (delete / move / flag).
|
|
3171
|
+
// Ctrl+Z = pop the top of the undo stack (delete / move / flag). Guard the
|
|
3172
|
+
// SAME way Delete does (the block above): never hijack Ctrl+Z when focus is
|
|
3173
|
+
// in a text field / contenteditable, and never while a compose overlay is
|
|
3174
|
+
// open — the editor (or the input) owns undo there. Without this guard the
|
|
3175
|
+
// mail-action undo could fire instead of an editor/text undo.
|
|
3172
3176
|
if (e.ctrlKey && e.key === "z") {
|
|
3173
|
-
|
|
3177
|
+
const t = e.target as HTMLElement | null;
|
|
3178
|
+
const tag = t?.tagName;
|
|
3179
|
+
const inEditable = tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || !!t?.isContentEditable;
|
|
3180
|
+
const composeOpen = !!document.querySelector(".compose-overlay");
|
|
3181
|
+
if (!inEditable && !composeOpen && undoStack.length > 0) {
|
|
3174
3182
|
e.preventDefault();
|
|
3175
3183
|
performUndo();
|
|
3176
3184
|
}
|
|
@@ -1900,6 +1900,26 @@ async function createTinyMceEditor(container2, opts = {}) {
|
|
|
1900
1900
|
target.id = `rmf-tiny-${Math.random().toString(36).slice(2, 10)}`;
|
|
1901
1901
|
target.style.cssText = "width:100%;height:100%;";
|
|
1902
1902
|
container2.appendChild(target);
|
|
1903
|
+
const CODE_LANGS = [
|
|
1904
|
+
{ text: "Text", value: "text" },
|
|
1905
|
+
{ text: "HTML/XML", value: "markup" },
|
|
1906
|
+
{ text: "JavaScript", value: "javascript" },
|
|
1907
|
+
{ text: "TypeScript", value: "typescript" },
|
|
1908
|
+
{ text: "CSS", value: "css" },
|
|
1909
|
+
{ text: "JSON", value: "json" },
|
|
1910
|
+
{ text: "Python", value: "python" },
|
|
1911
|
+
{ text: "Java", value: "java" },
|
|
1912
|
+
{ text: "C", value: "c" },
|
|
1913
|
+
{ text: "C++", value: "cpp" },
|
|
1914
|
+
{ text: "C#", value: "csharp" },
|
|
1915
|
+
{ text: "Go", value: "go" },
|
|
1916
|
+
{ text: "Rust", value: "rust" },
|
|
1917
|
+
{ text: "Ruby", value: "ruby" },
|
|
1918
|
+
{ text: "PHP", value: "php" },
|
|
1919
|
+
{ text: "Shell", value: "bash" },
|
|
1920
|
+
{ text: "PowerShell", value: "powershell" },
|
|
1921
|
+
{ text: "SQL", value: "sql" }
|
|
1922
|
+
];
|
|
1903
1923
|
const editor2 = await new Promise((resolve) => {
|
|
1904
1924
|
tinymce.init({
|
|
1905
1925
|
target,
|
|
@@ -1912,7 +1932,7 @@ async function createTinyMceEditor(container2, opts = {}) {
|
|
|
1912
1932
|
plugins: "paste lists advlist link table code codesample image searchreplace autolink wordcount emoticons charmap insertdatetime quickbars nonbreaking directionality help",
|
|
1913
1933
|
toolbar: [
|
|
1914
1934
|
"undo redo | bold italic underline strikethrough | forecolor backcolor",
|
|
1915
|
-
"bullist numlist outdent indent | link table image code
|
|
1935
|
+
"bullist numlist outdent indent | link table image code rmfcode | emoticons charmap | help"
|
|
1916
1936
|
].join(" | "),
|
|
1917
1937
|
// Include "tools" so wordcount and searchreplace are reachable.
|
|
1918
1938
|
menubar: "file edit view insert format tools",
|
|
@@ -1939,25 +1959,7 @@ async function createTinyMceEditor(container2, opts = {}) {
|
|
|
1939
1959
|
// which mangles unrelated paste content (Bob 2026-05-24).
|
|
1940
1960
|
// Adding Text first so it's the default; rest are the modern
|
|
1941
1961
|
// languages we actually paste.
|
|
1942
|
-
codesample_languages:
|
|
1943
|
-
{ text: "Text", value: "text" },
|
|
1944
|
-
{ text: "HTML/XML", value: "markup" },
|
|
1945
|
-
{ text: "JavaScript", value: "javascript" },
|
|
1946
|
-
{ text: "TypeScript", value: "typescript" },
|
|
1947
|
-
{ text: "CSS", value: "css" },
|
|
1948
|
-
{ text: "JSON", value: "json" },
|
|
1949
|
-
{ text: "Python", value: "python" },
|
|
1950
|
-
{ text: "Java", value: "java" },
|
|
1951
|
-
{ text: "C", value: "c" },
|
|
1952
|
-
{ text: "C++", value: "cpp" },
|
|
1953
|
-
{ text: "C#", value: "csharp" },
|
|
1954
|
-
{ text: "Go", value: "go" },
|
|
1955
|
-
{ text: "Rust", value: "rust" },
|
|
1956
|
-
{ text: "Ruby", value: "ruby" },
|
|
1957
|
-
{ text: "PHP", value: "php" },
|
|
1958
|
-
{ text: "Shell", value: "bash" },
|
|
1959
|
-
{ text: "SQL", value: "sql" }
|
|
1960
|
-
],
|
|
1962
|
+
codesample_languages: CODE_LANGS,
|
|
1961
1963
|
// WebView's native spell-check (red underlines, right-click
|
|
1962
1964
|
// "Add to dictionary"). Free; same UX as Quill's spellcheck=true.
|
|
1963
1965
|
// Premium tinymcespellchecker plugin would replace this with a
|
|
@@ -1975,6 +1977,17 @@ async function createTinyMceEditor(container2, opts = {}) {
|
|
|
1975
1977
|
statusbar: false,
|
|
1976
1978
|
branding: false,
|
|
1977
1979
|
license_key: "gpl",
|
|
1980
|
+
// Keep URLs verbatim. TinyMCE defaults to convert_urls:true +
|
|
1981
|
+
// relative_urls:true, which rewrites an absolute href
|
|
1982
|
+
// (https://github.com/…) to be RELATIVE to the editor's base URL
|
|
1983
|
+
// (the msger custom-protocol page). A link that worked in the
|
|
1984
|
+
// original message arrives in the reply quote rewritten/dead
|
|
1985
|
+
// (Bob 2026-05-31: "URL works in the original but not in the
|
|
1986
|
+
// reply"). false on both = hrefs travel into the sent message
|
|
1987
|
+
// exactly as the sender wrote them.
|
|
1988
|
+
convert_urls: false,
|
|
1989
|
+
relative_urls: false,
|
|
1990
|
+
remove_script_host: false,
|
|
1978
1991
|
paste_data_images: true,
|
|
1979
1992
|
// Permissive valid_elements — preserve as much of the source
|
|
1980
1993
|
// formatting as possible. The default is more aggressive about
|
|
@@ -2063,6 +2076,60 @@ async function createTinyMceEditor(container2, opts = {}) {
|
|
|
2063
2076
|
applyZoom();
|
|
2064
2077
|
saveZoom();
|
|
2065
2078
|
};
|
|
2079
|
+
const BORDER_STYLES = { "border": "1px solid #888", "border-radius": "4px", "padding": "10px" };
|
|
2080
|
+
const openCodeDialog = () => {
|
|
2081
|
+
const cs = ed.plugins.codesample;
|
|
2082
|
+
const preEl = ed.dom.getParent(ed.selection.getNode(), "pre");
|
|
2083
|
+
let curLang = "text";
|
|
2084
|
+
let curBorder = false;
|
|
2085
|
+
if (preEl) {
|
|
2086
|
+
const m = (preEl.className || "").match(/language-([\w-]+)/);
|
|
2087
|
+
if (m)
|
|
2088
|
+
curLang = m[1];
|
|
2089
|
+
curBorder = !!(preEl.style && preEl.style.border && preEl.style.border !== "none");
|
|
2090
|
+
}
|
|
2091
|
+
const curCode = cs && cs.getCurrentCode ? cs.getCurrentCode(ed) || "" : "";
|
|
2092
|
+
ed.windowManager.open({
|
|
2093
|
+
title: "Insert code",
|
|
2094
|
+
size: "large",
|
|
2095
|
+
body: {
|
|
2096
|
+
type: "panel",
|
|
2097
|
+
items: [
|
|
2098
|
+
{ type: "listbox", name: "language", label: "Language", items: CODE_LANGS },
|
|
2099
|
+
{ type: "textarea", name: "code", label: "Code" },
|
|
2100
|
+
{ type: "checkbox", name: "border", label: "Box it in (border around the code)" }
|
|
2101
|
+
]
|
|
2102
|
+
},
|
|
2103
|
+
initialData: { language: curLang, code: curCode, border: curBorder },
|
|
2104
|
+
buttons: [
|
|
2105
|
+
{ type: "cancel", text: "Cancel" },
|
|
2106
|
+
{ type: "submit", text: "Save", primary: true }
|
|
2107
|
+
],
|
|
2108
|
+
onSubmit: (api) => {
|
|
2109
|
+
const data = api.getData();
|
|
2110
|
+
if (cs && cs.insertCodeSample)
|
|
2111
|
+
cs.insertCodeSample(data.language, data.code);
|
|
2112
|
+
const pre = ed.dom.getParent(ed.selection.getNode(), "pre");
|
|
2113
|
+
if (pre) {
|
|
2114
|
+
if (data.border)
|
|
2115
|
+
ed.dom.setStyles(pre, BORDER_STYLES);
|
|
2116
|
+
else
|
|
2117
|
+
ed.dom.setStyles(pre, { "border": "", "border-radius": "", "padding": "" });
|
|
2118
|
+
}
|
|
2119
|
+
api.close();
|
|
2120
|
+
}
|
|
2121
|
+
});
|
|
2122
|
+
};
|
|
2123
|
+
ed.ui.registry.addButton("rmfcode", {
|
|
2124
|
+
icon: "code-sample",
|
|
2125
|
+
tooltip: "Insert/edit code block",
|
|
2126
|
+
onAction: openCodeDialog
|
|
2127
|
+
});
|
|
2128
|
+
ed.ui.registry.addMenuItem("rmfcode", {
|
|
2129
|
+
icon: "code-sample",
|
|
2130
|
+
text: "Code block\u2026",
|
|
2131
|
+
onAction: openCodeDialog
|
|
2132
|
+
});
|
|
2066
2133
|
ed.ui.registry.addMenuItem("zoomIn", {
|
|
2067
2134
|
text: "Zoom in",
|
|
2068
2135
|
shortcut: "Ctrl+=",
|
|
@@ -2114,11 +2181,19 @@ async function createTinyMceEditor(container2, opts = {}) {
|
|
|
2114
2181
|
}
|
|
2115
2182
|
if (tail.toString().length > 0)
|
|
2116
2183
|
return;
|
|
2117
|
-
const
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2184
|
+
const parent2 = link.parentNode;
|
|
2185
|
+
const idx = parent2 ? Array.prototype.indexOf.call(parent2.childNodes, link) + 1 : -1;
|
|
2186
|
+
if (parent2 && idx >= 0) {
|
|
2187
|
+
try {
|
|
2188
|
+
ed.selection.setCursorLocation(parent2, idx);
|
|
2189
|
+
} catch {
|
|
2190
|
+
const after = doc.createRange();
|
|
2191
|
+
after.setStartAfter(link);
|
|
2192
|
+
after.collapse(true);
|
|
2193
|
+
sel.removeAllRanges();
|
|
2194
|
+
sel.addRange(after);
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2122
2197
|
}, true);
|
|
2123
2198
|
};
|
|
2124
2199
|
ed.on("init", installLinkEscape);
|