@bobfrankston/rmfmail 1.2.254 → 1.2.256
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/compose/compose.bundle.js +77 -1
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/lib/rmf-tiny.js +105 -1
- package/client/package.json +1 -1
- package/package.json +9 -9
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-77932 → node_modules.npmglobalize-stash-47236}/.package-lock.json +0 -0
|
@@ -2699,7 +2699,11 @@ async function createTinyMceEditor(container2, opts = {}) {
|
|
|
2699
2699
|
// left. Replaced the generic alignleft/center/right icons, which
|
|
2700
2700
|
// don't communicate wrapping (Bob 2026-07-18: "the align is
|
|
2701
2701
|
// confusing, better to use the Word icons for the three cases").
|
|
2702
|
-
|
|
2702
|
+
// …plus a border control (Bob 2026-08-13: "image editing should have
|
|
2703
|
+
// an option to put a border around the image. It could be a check box
|
|
2704
|
+
// or a width size"). Split button: click = 1px border on/off, ▾ = pick
|
|
2705
|
+
// the width. Registered in setup below.
|
|
2706
|
+
quickbars_image_toolbar: "rmfwrapinline rmfwrapleft rmfwrapright | rmfborder",
|
|
2703
2707
|
// Code-sample dropdown languages. TinyMCE's default list omits
|
|
2704
2708
|
// "Text" / plain — every option triggers syntax highlighting
|
|
2705
2709
|
// which mangles unrelated paste content (Bob 2026-05-24).
|
|
@@ -2718,6 +2722,11 @@ async function createTinyMceEditor(container2, opts = {}) {
|
|
|
2718
2722
|
// OS file-picker the compose Attach button uses (<input type=file>,
|
|
2719
2723
|
// which works in the msger WebView), then inlines the chosen image as
|
|
2720
2724
|
// a base64 data URL — matching the drag-drop-an-image behavior.
|
|
2725
|
+
// Insert/Edit Image → Advanced tab: border width + style, and the
|
|
2726
|
+
// vertical/horizontal spacing that goes with a framed image. This is
|
|
2727
|
+
// the precise-control half of the border feature; the quickbar split
|
|
2728
|
+
// button below is the one-click half.
|
|
2729
|
+
image_advtab: true,
|
|
2721
2730
|
file_picker_types: "image",
|
|
2722
2731
|
file_picker_callback: (cb, _value, meta) => {
|
|
2723
2732
|
if (meta && meta.filetype && meta.filetype !== "image")
|
|
@@ -2874,6 +2883,56 @@ async function createTinyMceEditor(container2, opts = {}) {
|
|
|
2874
2883
|
wrapButton("rmfwrapinline", "rmf-wrap-inline", "In line with text", "inline");
|
|
2875
2884
|
wrapButton("rmfwrapleft", "rmf-wrap-left", "Image left \u2014 text wraps around the right", "left");
|
|
2876
2885
|
wrapButton("rmfwrapright", "rmf-wrap-right", "Image right \u2014 text wraps around the left", "right");
|
|
2886
|
+
const BORDER_COLOR = "#888888";
|
|
2887
|
+
const BORDER_WIDTHS = [1, 2, 3, 5];
|
|
2888
|
+
ed.ui.registry.addIcon("rmf-border", `<svg width="24" height="24" viewBox="0 0 24 24"><rect x="3.5" y="4.5" width="17" height="15" rx="1" fill="none" stroke="currentColor" stroke-width="2"/>` + arch(12, 16.5) + `</svg>`);
|
|
2889
|
+
const borderWidthOf = (node) => {
|
|
2890
|
+
if (!node || node.nodeName !== "IMG")
|
|
2891
|
+
return 0;
|
|
2892
|
+
const style = node.style?.borderStyle || "";
|
|
2893
|
+
if (!style || style === "none")
|
|
2894
|
+
return 0;
|
|
2895
|
+
const w = parseFloat(node.style?.borderWidth || "");
|
|
2896
|
+
return Number.isFinite(w) ? w : 0;
|
|
2897
|
+
};
|
|
2898
|
+
const applyBorder = (px) => {
|
|
2899
|
+
const node = ed.selection.getNode();
|
|
2900
|
+
if (!node || node.nodeName !== "IMG")
|
|
2901
|
+
return;
|
|
2902
|
+
ed.undoManager.transact(() => {
|
|
2903
|
+
ed.dom.setStyles(node, px > 0 ? { border: `${px}px solid ${BORDER_COLOR}` } : { border: "" });
|
|
2904
|
+
});
|
|
2905
|
+
ed.nodeChanged();
|
|
2906
|
+
};
|
|
2907
|
+
ed.ui.registry.addSplitButton("rmfborder", {
|
|
2908
|
+
icon: "rmf-border",
|
|
2909
|
+
tooltip: "Border around the image \u2014 click to toggle, \u25BE to pick the width",
|
|
2910
|
+
// Click toggles: bordered → none, unbordered → 1px hairline.
|
|
2911
|
+
onAction: () => applyBorder(borderWidthOf(ed.selection.getNode()) > 0 ? 0 : 1),
|
|
2912
|
+
onItemAction: (_api, value) => applyBorder(Number(value)),
|
|
2913
|
+
select: (value) => Number(value) === borderWidthOf(ed.selection.getNode()),
|
|
2914
|
+
fetch: (cb) => cb([
|
|
2915
|
+
{ type: "choiceitem", text: "No border", value: "0" },
|
|
2916
|
+
...BORDER_WIDTHS.map((w) => ({ type: "choiceitem", text: `${w} px`, value: String(w) }))
|
|
2917
|
+
]),
|
|
2918
|
+
onSetup: (api) => {
|
|
2919
|
+
const update = () => api.setActive(borderWidthOf(ed.selection.getNode()) > 0);
|
|
2920
|
+
ed.on("NodeChange", update);
|
|
2921
|
+
return () => ed.off("NodeChange", update);
|
|
2922
|
+
}
|
|
2923
|
+
});
|
|
2924
|
+
ed.on("NodeChange", () => {
|
|
2925
|
+
const n = ed.selection.getNode();
|
|
2926
|
+
if (!n || n.nodeName !== "IMG")
|
|
2927
|
+
return;
|
|
2928
|
+
const w = parseFloat(n.style?.borderWidth || "");
|
|
2929
|
+
const s = n.style?.borderStyle || "";
|
|
2930
|
+
if (Number.isFinite(w) && w > 0 && (!s || s === "none")) {
|
|
2931
|
+
ed.dom.setStyle(n, "border-style", "solid");
|
|
2932
|
+
if (!n.style.borderColor)
|
|
2933
|
+
ed.dom.setStyle(n, "border-color", BORDER_COLOR);
|
|
2934
|
+
}
|
|
2935
|
+
});
|
|
2877
2936
|
ed.on("paste", (e) => {
|
|
2878
2937
|
const cb = e.clipboardData;
|
|
2879
2938
|
if (!cb)
|
|
@@ -3268,7 +3327,18 @@ async function createTinyMceEditor(container2, opts = {}) {
|
|
|
3268
3327
|
function openSourceDialog(editor2) {
|
|
3269
3328
|
const SENTINEL = "\uE000";
|
|
3270
3329
|
let offset = -1;
|
|
3330
|
+
let savedRng = null;
|
|
3271
3331
|
try {
|
|
3332
|
+
const sel = editor2.selection;
|
|
3333
|
+
const rng = sel.getRng();
|
|
3334
|
+
if (rng) {
|
|
3335
|
+
savedRng = rng.cloneRange();
|
|
3336
|
+
if (!rng.collapsed) {
|
|
3337
|
+
const collapsed = rng.cloneRange();
|
|
3338
|
+
collapsed.collapse(true);
|
|
3339
|
+
sel.setRng(collapsed);
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3272
3342
|
const insert = () => editor2.selection.setContent(SENTINEL);
|
|
3273
3343
|
if (typeof editor2.undoManager?.ignore === "function")
|
|
3274
3344
|
editor2.undoManager.ignore(insert);
|
|
@@ -3289,6 +3359,12 @@ function openSourceDialog(editor2) {
|
|
|
3289
3359
|
}
|
|
3290
3360
|
} catch {
|
|
3291
3361
|
}
|
|
3362
|
+
if (savedRng) {
|
|
3363
|
+
try {
|
|
3364
|
+
editor2.selection.setRng(savedRng);
|
|
3365
|
+
} catch {
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3292
3368
|
} catch {
|
|
3293
3369
|
}
|
|
3294
3370
|
const source = editor2.getContent();
|