@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/lib/rmf-tiny.js
CHANGED
|
@@ -81,6 +81,31 @@ export async function createTinyMceEditor(container, opts = {}) {
|
|
|
81
81
|
target.id = `rmf-tiny-${Math.random().toString(36).slice(2, 10)}`;
|
|
82
82
|
target.style.cssText = "width:100%;height:100%;";
|
|
83
83
|
container.appendChild(target);
|
|
84
|
+
// Code-block languages — shared by the stock codesample dialog
|
|
85
|
+
// (codesample_languages) AND our custom "Insert code" dialog below, so a
|
|
86
|
+
// language added here shows up in both. "Text" first = default (no
|
|
87
|
+
// highlighting, which would mangle plain pasted snippets — Bob 2026-05-24).
|
|
88
|
+
// PowerShell added 2026-05-31 (Bob: ".ps1 is another file type").
|
|
89
|
+
const CODE_LANGS = [
|
|
90
|
+
{ text: "Text", value: "text" },
|
|
91
|
+
{ text: "HTML/XML", value: "markup" },
|
|
92
|
+
{ text: "JavaScript", value: "javascript" },
|
|
93
|
+
{ text: "TypeScript", value: "typescript" },
|
|
94
|
+
{ text: "CSS", value: "css" },
|
|
95
|
+
{ text: "JSON", value: "json" },
|
|
96
|
+
{ text: "Python", value: "python" },
|
|
97
|
+
{ text: "Java", value: "java" },
|
|
98
|
+
{ text: "C", value: "c" },
|
|
99
|
+
{ text: "C++", value: "cpp" },
|
|
100
|
+
{ text: "C#", value: "csharp" },
|
|
101
|
+
{ text: "Go", value: "go" },
|
|
102
|
+
{ text: "Rust", value: "rust" },
|
|
103
|
+
{ text: "Ruby", value: "ruby" },
|
|
104
|
+
{ text: "PHP", value: "php" },
|
|
105
|
+
{ text: "Shell", value: "bash" },
|
|
106
|
+
{ text: "PowerShell", value: "powershell" },
|
|
107
|
+
{ text: "SQL", value: "sql" },
|
|
108
|
+
];
|
|
84
109
|
const editor = await new Promise((resolve) => {
|
|
85
110
|
tinymce.init({
|
|
86
111
|
target,
|
|
@@ -93,7 +118,7 @@ export async function createTinyMceEditor(container, opts = {}) {
|
|
|
93
118
|
plugins: "paste lists advlist link table code codesample image searchreplace autolink wordcount emoticons charmap insertdatetime quickbars nonbreaking directionality help",
|
|
94
119
|
toolbar: [
|
|
95
120
|
"undo redo | bold italic underline strikethrough | forecolor backcolor",
|
|
96
|
-
"bullist numlist outdent indent | link table image code
|
|
121
|
+
"bullist numlist outdent indent | link table image code rmfcode | emoticons charmap | help",
|
|
97
122
|
].join(" | "),
|
|
98
123
|
// Include "tools" so wordcount and searchreplace are reachable.
|
|
99
124
|
menubar: "file edit view insert format tools",
|
|
@@ -120,25 +145,7 @@ export async function createTinyMceEditor(container, opts = {}) {
|
|
|
120
145
|
// which mangles unrelated paste content (Bob 2026-05-24).
|
|
121
146
|
// Adding Text first so it's the default; rest are the modern
|
|
122
147
|
// languages we actually paste.
|
|
123
|
-
codesample_languages:
|
|
124
|
-
{ text: "Text", value: "text" },
|
|
125
|
-
{ text: "HTML/XML", value: "markup" },
|
|
126
|
-
{ text: "JavaScript", value: "javascript" },
|
|
127
|
-
{ text: "TypeScript", value: "typescript" },
|
|
128
|
-
{ text: "CSS", value: "css" },
|
|
129
|
-
{ text: "JSON", value: "json" },
|
|
130
|
-
{ text: "Python", value: "python" },
|
|
131
|
-
{ text: "Java", value: "java" },
|
|
132
|
-
{ text: "C", value: "c" },
|
|
133
|
-
{ text: "C++", value: "cpp" },
|
|
134
|
-
{ text: "C#", value: "csharp" },
|
|
135
|
-
{ text: "Go", value: "go" },
|
|
136
|
-
{ text: "Rust", value: "rust" },
|
|
137
|
-
{ text: "Ruby", value: "ruby" },
|
|
138
|
-
{ text: "PHP", value: "php" },
|
|
139
|
-
{ text: "Shell", value: "bash" },
|
|
140
|
-
{ text: "SQL", value: "sql" },
|
|
141
|
-
],
|
|
148
|
+
codesample_languages: CODE_LANGS,
|
|
142
149
|
// WebView's native spell-check (red underlines, right-click
|
|
143
150
|
// "Add to dictionary"). Free; same UX as Quill's spellcheck=true.
|
|
144
151
|
// Premium tinymcespellchecker plugin would replace this with a
|
|
@@ -156,6 +163,17 @@ export async function createTinyMceEditor(container, opts = {}) {
|
|
|
156
163
|
statusbar: false,
|
|
157
164
|
branding: false,
|
|
158
165
|
license_key: "gpl",
|
|
166
|
+
// Keep URLs verbatim. TinyMCE defaults to convert_urls:true +
|
|
167
|
+
// relative_urls:true, which rewrites an absolute href
|
|
168
|
+
// (https://github.com/…) to be RELATIVE to the editor's base URL
|
|
169
|
+
// (the msger custom-protocol page). A link that worked in the
|
|
170
|
+
// original message arrives in the reply quote rewritten/dead
|
|
171
|
+
// (Bob 2026-05-31: "URL works in the original but not in the
|
|
172
|
+
// reply"). false on both = hrefs travel into the sent message
|
|
173
|
+
// exactly as the sender wrote them.
|
|
174
|
+
convert_urls: false,
|
|
175
|
+
relative_urls: false,
|
|
176
|
+
remove_script_host: false,
|
|
159
177
|
paste_data_images: true,
|
|
160
178
|
// Permissive valid_elements — preserve as much of the source
|
|
161
179
|
// formatting as possible. The default is more aggressive about
|
|
@@ -250,6 +268,67 @@ export async function createTinyMceEditor(container, opts = {}) {
|
|
|
250
268
|
applyZoom();
|
|
251
269
|
saveZoom();
|
|
252
270
|
};
|
|
271
|
+
// Custom "Insert code" — wraps the stock codesample plugin
|
|
272
|
+
// (keeps its Prism highlighting + edit-in-place) but adds a
|
|
273
|
+
// "Box it in" border checkbox so a snippet is visually
|
|
274
|
+
// demarcated from surrounding prose. The border is applied as
|
|
275
|
+
// an INLINE style on the <pre>, NOT a content_style class, so
|
|
276
|
+
// it survives into the sent email where the recipient has none
|
|
277
|
+
// of our editor CSS. Bob 2026-05-31.
|
|
278
|
+
const BORDER_STYLES = { "border": "1px solid #888", "border-radius": "4px", "padding": "10px" };
|
|
279
|
+
const openCodeDialog = () => {
|
|
280
|
+
const cs = ed.plugins.codesample;
|
|
281
|
+
const preEl = ed.dom.getParent(ed.selection.getNode(), "pre");
|
|
282
|
+
let curLang = "text";
|
|
283
|
+
let curBorder = false;
|
|
284
|
+
if (preEl) {
|
|
285
|
+
const m = (preEl.className || "").match(/language-([\w-]+)/);
|
|
286
|
+
if (m)
|
|
287
|
+
curLang = m[1];
|
|
288
|
+
curBorder = !!(preEl.style && preEl.style.border && preEl.style.border !== "none");
|
|
289
|
+
}
|
|
290
|
+
const curCode = (cs && cs.getCurrentCode) ? (cs.getCurrentCode(ed) || "") : "";
|
|
291
|
+
ed.windowManager.open({
|
|
292
|
+
title: "Insert code",
|
|
293
|
+
size: "large",
|
|
294
|
+
body: {
|
|
295
|
+
type: "panel",
|
|
296
|
+
items: [
|
|
297
|
+
{ type: "listbox", name: "language", label: "Language", items: CODE_LANGS },
|
|
298
|
+
{ type: "textarea", name: "code", label: "Code" },
|
|
299
|
+
{ type: "checkbox", name: "border", label: "Box it in (border around the code)" },
|
|
300
|
+
],
|
|
301
|
+
},
|
|
302
|
+
initialData: { language: curLang, code: curCode, border: curBorder },
|
|
303
|
+
buttons: [
|
|
304
|
+
{ type: "cancel", text: "Cancel" },
|
|
305
|
+
{ type: "submit", text: "Save", primary: true },
|
|
306
|
+
],
|
|
307
|
+
onSubmit: (api) => {
|
|
308
|
+
const data = api.getData();
|
|
309
|
+
if (cs && cs.insertCodeSample)
|
|
310
|
+
cs.insertCodeSample(data.language, data.code);
|
|
311
|
+
const pre = ed.dom.getParent(ed.selection.getNode(), "pre");
|
|
312
|
+
if (pre) {
|
|
313
|
+
if (data.border)
|
|
314
|
+
ed.dom.setStyles(pre, BORDER_STYLES);
|
|
315
|
+
else
|
|
316
|
+
ed.dom.setStyles(pre, { "border": "", "border-radius": "", "padding": "" });
|
|
317
|
+
}
|
|
318
|
+
api.close();
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
};
|
|
322
|
+
ed.ui.registry.addButton("rmfcode", {
|
|
323
|
+
icon: "code-sample",
|
|
324
|
+
tooltip: "Insert/edit code block",
|
|
325
|
+
onAction: openCodeDialog,
|
|
326
|
+
});
|
|
327
|
+
ed.ui.registry.addMenuItem("rmfcode", {
|
|
328
|
+
icon: "code-sample",
|
|
329
|
+
text: "Code block…",
|
|
330
|
+
onAction: openCodeDialog,
|
|
331
|
+
});
|
|
253
332
|
ed.ui.registry.addMenuItem("zoomIn", {
|
|
254
333
|
text: "Zoom in", shortcut: "Ctrl+=",
|
|
255
334
|
onAction: () => bumpZoom(ZOOM_STEP),
|
|
@@ -326,11 +405,28 @@ export async function createTinyMceEditor(container, opts = {}) {
|
|
|
326
405
|
}
|
|
327
406
|
if (tail.toString().length > 0)
|
|
328
407
|
return;
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
408
|
+
// Move the caret to just after the link. Use TinyMCE's
|
|
409
|
+
// own selection API rather than a raw
|
|
410
|
+
// removeAllRanges/addRange: the raw mutation ran ahead
|
|
411
|
+
// of TinyMCE's beforeinput handling and desynced its
|
|
412
|
+
// UndoManager, leaving Ctrl+Z erratic (Bob 2026-05-31:
|
|
413
|
+
// "^z is very broken in TinyMCE"). Going through
|
|
414
|
+
// ed.selection keeps TinyMCE's selection + undo state
|
|
415
|
+
// consistent. Raw range as a fallback if it throws.
|
|
416
|
+
const parent = link.parentNode;
|
|
417
|
+
const idx = parent ? Array.prototype.indexOf.call(parent.childNodes, link) + 1 : -1;
|
|
418
|
+
if (parent && idx >= 0) {
|
|
419
|
+
try {
|
|
420
|
+
ed.selection.setCursorLocation(parent, idx);
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
const after = doc.createRange();
|
|
424
|
+
after.setStartAfter(link);
|
|
425
|
+
after.collapse(true);
|
|
426
|
+
sel.removeAllRanges();
|
|
427
|
+
sel.addRange(after);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
334
430
|
}, true);
|
|
335
431
|
};
|
|
336
432
|
ed.on("init", installLinkEscape);
|
package/package.json
CHANGED