@bobfrankston/rmfmail 1.2.287 → 1.2.289

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.
@@ -2686,7 +2686,15 @@ async function createTinyMceEditor(container2, opts = {}) {
2686
2686
  // window; TinyMCE's fullscreen plugin re-positions the container in
2687
2687
  // ways that produce a blank body, so it's not in the plugin list.
2688
2688
  menu: {
2689
- view: { title: "View", items: "rmfsource | visualaid visualchars visualblocks | preview | zoomIn zoomOut zoomReset" }
2689
+ view: { title: "View", items: "rmfsource | visualaid visualchars visualblocks | preview | zoomIn zoomOut zoomReset" },
2690
+ // Insert menu — TinyMCE's default list minus the items whose
2691
+ // plugins we don't load (media, pagebreak, anchor, accordion),
2692
+ // plus the two quoting items. Listed in full because naming a
2693
+ // menu REPLACES its item list; the defaults do not carry over.
2694
+ insert: {
2695
+ title: "Insert",
2696
+ items: "image link inserttable | rmfblockquote rmfquotepaste | rmfcode codesample charmap emoticons | hr nonbreaking insertdatetime"
2697
+ }
2690
2698
  },
2691
2699
  // Disable the "Get all features" upsell badge that TinyMCE 6+
2692
2700
  // injects automatically when no premium plugins are listed.
@@ -3107,6 +3115,18 @@ async function createTinyMceEditor(container2, opts = {}) {
3107
3115
  text: "Code block\u2026",
3108
3116
  onAction: openCodeDialog
3109
3117
  });
3118
+ ed.ui.registry.addMenuItem("rmfblockquote", {
3119
+ icon: "quote",
3120
+ text: "Blockquote",
3121
+ onAction: () => ed.execCommand("mceBlockQuote")
3122
+ });
3123
+ ed.ui.registry.addMenuItem("rmfquotepaste", {
3124
+ icon: "quote",
3125
+ text: "Quoted text (paste)\u2026",
3126
+ onAction: () => {
3127
+ void pasteAsQuote(ed);
3128
+ }
3129
+ });
3110
3130
  ed.ui.registry.addMenuItem("rmfsource", {
3111
3131
  icon: "sourcecode",
3112
3132
  text: "HTML source\u2026",
@@ -3345,6 +3365,48 @@ async function createTinyMceEditor(container2, opts = {}) {
3345
3365
  nativeEditor: editor2
3346
3366
  };
3347
3367
  }
3368
+ async function pasteAsQuote(ed) {
3369
+ const notify = (text, type) => {
3370
+ try {
3371
+ ed.notificationManager.open({ text, type, timeout: 4e3 });
3372
+ } catch {
3373
+ }
3374
+ };
3375
+ const escape = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3376
+ const fragment = (html2) => {
3377
+ const marked = html2.match(/<!--\s*StartFragment\s*-->([\s\S]*?)<!--\s*EndFragment\s*-->/i);
3378
+ const inner = marked ? marked[1] : html2.replace(/[\s\S]*<body[^>]*>|<\/body>[\s\S]*/gi, "");
3379
+ return inner.trim();
3380
+ };
3381
+ let html = "";
3382
+ try {
3383
+ const clip = navigator.clipboard;
3384
+ if (clip?.read) {
3385
+ const items = await clip.read();
3386
+ for (const item of items) {
3387
+ if (!item.types.includes("text/html"))
3388
+ continue;
3389
+ html = fragment(await (await item.getType("text/html")).text());
3390
+ break;
3391
+ }
3392
+ }
3393
+ if (!html) {
3394
+ const text = await navigator.clipboard?.readText?.() || "";
3395
+ if (text.trim()) {
3396
+ html = text.trim().split(/\n\s*\n/).map((para) => `<p>${escape(para).replace(/\n/g, "<br>")}</p>`).join("");
3397
+ }
3398
+ }
3399
+ } catch (e) {
3400
+ notify(`Could not read the clipboard: ${e?.message || e}`, "error");
3401
+ return;
3402
+ }
3403
+ if (!html) {
3404
+ notify("There is nothing on the clipboard to quote.", "warning");
3405
+ return;
3406
+ }
3407
+ ed.insertContent(`<blockquote>${html}</blockquote>`);
3408
+ ed.undoManager.add();
3409
+ }
3348
3410
  function openSourceDialog(editor2) {
3349
3411
  const SENTINEL = "\uE000";
3350
3412
  let offset = -1;