@bobfrankston/rmfmail 1.2.148 → 1.2.149

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.
@@ -2064,7 +2064,12 @@ async function createTinyMceEditor(container2, opts = {}) {
2064
2064
  // clutters more than it helps in an email composer.
2065
2065
  quickbars_selection_toolbar: "bold italic underline | forecolor | quicklink blockquote",
2066
2066
  quickbars_insert_toolbar: false,
2067
- quickbars_image_toolbar: "alignleft aligncenter alignright",
2067
+ // Word-style layout buttons (registered in setup below): in-line
2068
+ // with text, image-left/text-wraps-right, image-right/text-wraps-
2069
+ // left. Replaced the generic alignleft/center/right icons, which
2070
+ // don't communicate wrapping (Bob 2026-07-18: "the align is
2071
+ // confusing, better to use the Word icons for the three cases").
2072
+ quickbars_image_toolbar: "rmfwrapinline rmfwrapleft rmfwrapright",
2068
2073
  // Code-sample dropdown languages. TinyMCE's default list omits
2069
2074
  // "Text" / plain — every option triggers syntax highlighting
2070
2075
  // which mangles unrelated paste content (Bob 2026-05-24).
@@ -2202,6 +2207,43 @@ async function createTinyMceEditor(container2, opts = {}) {
2202
2207
  setup: (ed) => {
2203
2208
  if (opts.initialHtml)
2204
2209
  ed.on("init", () => ed.setContent(opts.initialHtml));
2210
+ const ln = (x1, x2, y) => `<rect x="${x1}" y="${y}" width="${x2 - x1}" height="1.5" rx="0.75" fill="currentColor"/>`;
2211
+ const arch = (cx, base) => `<path d="M ${cx - 4.5} ${base} a 4.5 4.5 0 0 1 9 0 z" fill="currentColor"/>`;
2212
+ const svg = (body) => `<svg width="24" height="24" viewBox="0 0 24 24">${body}</svg>`;
2213
+ ed.ui.registry.addIcon("rmf-wrap-inline", svg(ln(3, 21, 4.5) + ln(3, 6.5, 14.5) + arch(12, 16) + ln(17.5, 21, 14.5) + ln(3, 21, 19.5)));
2214
+ ed.ui.registry.addIcon("rmf-wrap-left", svg(ln(3, 21, 4.5) + arch(7.5, 16) + ln(14, 21, 9.5) + ln(14, 21, 14.5) + ln(3, 21, 19.5)));
2215
+ ed.ui.registry.addIcon("rmf-wrap-right", svg(ln(3, 21, 4.5) + ln(3, 10, 9.5) + ln(3, 10, 14.5) + arch(16.5, 16) + ln(3, 21, 19.5)));
2216
+ const applyWrap = (mode) => {
2217
+ const node = ed.selection.getNode();
2218
+ if (!node || node.nodeName !== "IMG")
2219
+ return;
2220
+ ed.undoManager.transact(() => {
2221
+ ed.dom.setStyles(node, {
2222
+ float: mode === "inline" ? "" : mode,
2223
+ margin: mode === "inline" ? "" : mode === "left" ? "0 12px 8px 0" : "0 0 8px 12px"
2224
+ });
2225
+ });
2226
+ ed.nodeChanged();
2227
+ };
2228
+ const wrapButton = (name, icon, tip, mode) => {
2229
+ ed.ui.registry.addToggleButton(name, {
2230
+ icon,
2231
+ tooltip: tip,
2232
+ onAction: () => applyWrap(mode),
2233
+ onSetup: (api) => {
2234
+ const update = () => {
2235
+ const node = ed.selection.getNode();
2236
+ const f = node?.nodeName === "IMG" ? node.style.float || "inline" : "";
2237
+ api.setActive(f === mode || mode === "inline" && f === "none");
2238
+ };
2239
+ ed.on("NodeChange", update);
2240
+ return () => ed.off("NodeChange", update);
2241
+ }
2242
+ });
2243
+ };
2244
+ wrapButton("rmfwrapinline", "rmf-wrap-inline", "In line with text", "inline");
2245
+ wrapButton("rmfwrapleft", "rmf-wrap-left", "Image left \u2014 text wraps around the right", "left");
2246
+ wrapButton("rmfwrapright", "rmf-wrap-right", "Image right \u2014 text wraps around the left", "right");
2205
2247
  ed.on("paste", (e) => {
2206
2248
  const cb = e.clipboardData;
2207
2249
  if (!cb)