@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.
@@ -146,7 +146,12 @@ export async function createTinyMceEditor(container, opts = {}) {
146
146
  // clutters more than it helps in an email composer.
147
147
  quickbars_selection_toolbar: "bold italic underline | forecolor | quicklink blockquote",
148
148
  quickbars_insert_toolbar: false,
149
- quickbars_image_toolbar: "alignleft aligncenter alignright",
149
+ // Word-style layout buttons (registered in setup below): in-line
150
+ // with text, image-left/text-wraps-right, image-right/text-wraps-
151
+ // left. Replaced the generic alignleft/center/right icons, which
152
+ // don't communicate wrapping (Bob 2026-07-18: "the align is
153
+ // confusing, better to use the Word icons for the three cases").
154
+ quickbars_image_toolbar: "rmfwrapinline rmfwrapleft rmfwrapright",
150
155
  // Code-sample dropdown languages. TinyMCE's default list omits
151
156
  // "Text" / plain — every option triggers syntax highlighting
152
157
  // which mangles unrelated paste content (Bob 2026-05-24).
@@ -284,6 +289,48 @@ export async function createTinyMceEditor(container, opts = {}) {
284
289
  setup: (ed) => {
285
290
  if (opts.initialHtml)
286
291
  ed.on("init", () => ed.setContent(opts.initialHtml));
292
+ // ── Image layout buttons (Word "Layout Options" style) ──
293
+ // Icons mimic Word's glyphs: an arch (the image) among text
294
+ // lines, so each button SHOWS its result instead of a generic
295
+ // align arrow. Three cases: in line with text, image left
296
+ // (text wraps right), image right (text wraps left).
297
+ const ln = (x1, x2, y) => `<rect x="${x1}" y="${y}" width="${x2 - x1}" height="1.5" rx="0.75" fill="currentColor"/>`;
298
+ const arch = (cx, base) => `<path d="M ${cx - 4.5} ${base} a 4.5 4.5 0 0 1 9 0 z" fill="currentColor"/>`;
299
+ const svg = (body) => `<svg width="24" height="24" viewBox="0 0 24 24">${body}</svg>`;
300
+ 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)));
301
+ 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)));
302
+ 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)));
303
+ const applyWrap = (mode) => {
304
+ const node = ed.selection.getNode();
305
+ if (!node || node.nodeName !== "IMG")
306
+ return;
307
+ ed.undoManager.transact(() => {
308
+ ed.dom.setStyles(node, {
309
+ float: mode === "inline" ? "" : mode,
310
+ margin: mode === "inline" ? "" :
311
+ mode === "left" ? "0 12px 8px 0" : "0 0 8px 12px",
312
+ });
313
+ });
314
+ ed.nodeChanged();
315
+ };
316
+ const wrapButton = (name, icon, tip, mode) => {
317
+ ed.ui.registry.addToggleButton(name, {
318
+ icon, tooltip: tip,
319
+ onAction: () => applyWrap(mode),
320
+ onSetup: (api) => {
321
+ const update = () => {
322
+ const node = ed.selection.getNode();
323
+ const f = node?.nodeName === "IMG" ? (node.style.float || "inline") : "";
324
+ api.setActive(f === mode || (mode === "inline" && f === "none"));
325
+ };
326
+ ed.on("NodeChange", update);
327
+ return () => ed.off("NodeChange", update);
328
+ },
329
+ });
330
+ };
331
+ wrapButton("rmfwrapinline", "rmf-wrap-inline", "In line with text", "inline");
332
+ wrapButton("rmfwrapleft", "rmf-wrap-left", "Image left — text wraps around the right", "left");
333
+ wrapButton("rmfwrapright", "rmf-wrap-right", "Image right — text wraps around the left", "right");
287
334
  // Bitmap-first paste. An image copied from a web app (Gemini,
288
335
  // browser "Copy image") puts BOTH a bitmap file item and an
289
336
  // html flavor on the clipboard — and the html flavor is a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/rmfmail",
3
- "version": "1.2.148",
3
+ "version": "1.2.149",
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.398",
44
44
  "@bobfrankston/node-tcp-transport": "^0.1.10",
45
45
  "@bobfrankston/oauthsupport": "^1.0.34",
46
- "@bobfrankston/rmf-tiny": "^0.1.39",
46
+ "@bobfrankston/rmf-tiny": "^0.1.40",
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.398",
122
122
  "@bobfrankston/node-tcp-transport": "^0.1.10",
123
123
  "@bobfrankston/oauthsupport": "^1.0.34",
124
- "@bobfrankston/rmf-tiny": "^0.1.39",
124
+ "@bobfrankston/rmf-tiny": "^0.1.40",
125
125
  "@bobfrankston/smtp-direct": "^0.1.9",
126
126
  "@bobfrankston/tcp-transport": "^0.1.8",
127
127
  "@capacitor/android": "^8.3.0",