@bobfrankston/rmfmail 1.2.229 → 1.2.231

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.
Files changed (31) hide show
  1. package/TODO.md +27 -0
  2. package/client/app.bundle.js +349 -66
  3. package/client/app.bundle.js.map +4 -4
  4. package/client/app.js +16 -2
  5. package/client/app.js.map +1 -1
  6. package/client/app.ts +14 -1
  7. package/client/components/calendar-sidebar.js +1 -1
  8. package/client/components/calendar-sidebar.js.map +1 -1
  9. package/client/components/calendar-sidebar.ts +1 -1
  10. package/client/components/context-menu.js +12 -1
  11. package/client/components/context-menu.js.map +1 -1
  12. package/client/components/context-menu.ts +36 -1
  13. package/client/components/edit-menu.js +339 -0
  14. package/client/components/edit-menu.js.map +1 -0
  15. package/client/components/edit-menu.ts +343 -0
  16. package/client/components/folder-tree.js +2 -2
  17. package/client/components/folder-tree.js.map +1 -1
  18. package/client/components/folder-tree.ts +2 -2
  19. package/client/components/message-list.js +3 -1
  20. package/client/components/message-list.js.map +1 -1
  21. package/client/components/message-list.ts +3 -1
  22. package/client/compose/compose.bundle.js +254 -83
  23. package/client/compose/compose.bundle.js.map +4 -4
  24. package/client/compose/compose.js +5 -28
  25. package/client/compose/compose.js.map +1 -1
  26. package/client/compose/compose.ts +4 -13
  27. package/client/compose/edit-commands.js +10 -89
  28. package/client/compose/edit-commands.js.map +1 -1
  29. package/client/compose/edit-commands.ts +11 -77
  30. package/package.json +1 -1
  31. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-146480 → node_modules.npmglobalize-stash-7472}/.package-lock.json +0 -0
@@ -1983,18 +1983,74 @@ var init_spellcheck_core = __esm({
1983
1983
  }
1984
1984
  });
1985
1985
 
1986
- // client/compose/edit-commands.js
1987
- var edit_commands_exports = {};
1988
- __export(edit_commands_exports, {
1989
- runClipboardCommand: () => runClipboardCommand,
1990
- runFormatCommand: () => runFormatCommand,
1991
- standardEditItems: () => standardEditItems
1992
- });
1993
- function isTinyEditor(ne) {
1986
+ // client/components/edit-menu.js
1987
+ function isTinyEngine(ne) {
1994
1988
  return !!ne && typeof ne.execCommand === "function" && !!ne.selection;
1995
1989
  }
1990
+ function describeEditTarget(target) {
1991
+ const none = { el: null, editable: false, selection: "", canRead: true, isField: false };
1992
+ const node = target;
1993
+ if (!node)
1994
+ return { ...none, selection: pageSelection() };
1995
+ const start = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
1996
+ if (!start)
1997
+ return { ...none, selection: pageSelection() };
1998
+ const field = start.closest("input, textarea");
1999
+ if (field) {
2000
+ const type = (field instanceof HTMLInputElement ? field.type : "text").toLowerCase();
2001
+ if (!TEXTUAL_INPUT_TYPES.has(type))
2002
+ return { ...none, selection: pageSelection() };
2003
+ const s = field.selectionStart ?? 0, e = field.selectionEnd ?? 0;
2004
+ return {
2005
+ el: field,
2006
+ editable: !field.readOnly && !field.disabled,
2007
+ selection: e > s ? field.value.slice(s, e) : "",
2008
+ canRead: type !== "password",
2009
+ isField: true
2010
+ };
2011
+ }
2012
+ const ce = start.closest("[contenteditable]");
2013
+ const editable = !!ce && ce.isContentEditable;
2014
+ return { el: ce || start, editable, selection: pageSelection(), canRead: true, isField: false };
2015
+ }
2016
+ function pageSelection() {
2017
+ try {
2018
+ return window.getSelection()?.toString() || "";
2019
+ } catch {
2020
+ return "";
2021
+ }
2022
+ }
2023
+ function escapeHtml(s) {
2024
+ return s.replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c]);
2025
+ }
2026
+ async function readClipboard() {
2027
+ let html = "", text = "";
2028
+ if (navigator.clipboard?.read) {
2029
+ for (const item of await navigator.clipboard.read()) {
2030
+ if (item.types.includes("text/html"))
2031
+ html = await (await item.getType("text/html")).text();
2032
+ if (item.types.includes("text/plain"))
2033
+ text = await (await item.getType("text/plain")).text();
2034
+ }
2035
+ if (!html && !text)
2036
+ text = await navigator.clipboard.readText();
2037
+ } else {
2038
+ text = await navigator.clipboard.readText();
2039
+ }
2040
+ return { html, text };
2041
+ }
2042
+ async function writeClipboard(text, html) {
2043
+ if (html && typeof ClipboardItem === "function" && navigator.clipboard?.write) {
2044
+ await navigator.clipboard.write([new ClipboardItem({
2045
+ "text/html": new Blob([html], { type: "text/html" }),
2046
+ "text/plain": new Blob([text], { type: "text/plain" })
2047
+ })]);
2048
+ return;
2049
+ }
2050
+ await navigator.clipboard.writeText(text);
2051
+ }
1996
2052
  function expandToWord(ne) {
1997
- if (isTinyEditor(ne)) {
2053
+ if (isTinyEngine(ne)) {
1998
2054
  try {
1999
2055
  if (ne.selection.isCollapsed() && typeof ne.selection.expand === "function") {
2000
2056
  ne.selection.expand({ type: "word" });
@@ -2017,24 +2073,59 @@ function expandToWord(ne) {
2017
2073
  ne.setSelection(a, b - a);
2018
2074
  }
2019
2075
  }
2020
- function escapeHtml(s) {
2021
- return s.replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c]);
2076
+ function replaceFieldSelection(field, text) {
2077
+ const s = field.selectionStart ?? field.value.length;
2078
+ const e = field.selectionEnd ?? field.value.length;
2079
+ field.value = field.value.slice(0, s) + text + field.value.slice(e);
2080
+ field.selectionStart = field.selectionEnd = s + text.length;
2081
+ field.dispatchEvent(new Event("input", { bubbles: true }));
2022
2082
  }
2023
- async function runClipboardCommand(ne, id) {
2024
- const isTiny = isTinyEditor(ne);
2083
+ async function runClipboard(ctx, id) {
2084
+ if (ctx.engine)
2085
+ return runEngineClipboard(ctx.engine, id);
2086
+ const info = describeEditTarget(ctx.target);
2025
2087
  if (id === "paste") {
2026
- let html2 = "";
2027
- let text2 = "";
2028
- if (navigator.clipboard?.read) {
2029
- for (const item of await navigator.clipboard.read()) {
2030
- if (item.types.includes("text/html"))
2031
- html2 = await (await item.getType("text/html")).text();
2032
- if (item.types.includes("text/plain"))
2033
- text2 = await (await item.getType("text/plain")).text();
2034
- }
2035
- } else {
2036
- text2 = await navigator.clipboard.readText();
2088
+ if (!info.editable || !info.el)
2089
+ throw new Error("nothing here accepts a paste");
2090
+ const { html, text } = await readClipboard();
2091
+ if (info.isField) {
2092
+ if (!text && !html)
2093
+ return;
2094
+ replaceFieldSelection(info.el, text || stripHtml(html));
2095
+ return;
2037
2096
  }
2097
+ const content = html || (text ? escapeHtml(text).replace(/\r?\n/g, "<br>") : "");
2098
+ if (!content)
2099
+ return;
2100
+ insertHtmlAtSelection(info.el, content);
2101
+ return;
2102
+ }
2103
+ if (!info.canRead)
2104
+ throw new Error("this field's contents can't be copied");
2105
+ if (!info.selection)
2106
+ return;
2107
+ if (info.isField) {
2108
+ await writeClipboard(info.selection);
2109
+ } else {
2110
+ await writeClipboard(info.selection, selectionHtml());
2111
+ }
2112
+ if (id !== "cut")
2113
+ return;
2114
+ if (!info.editable)
2115
+ throw new Error("this text is read-only \u2014 copied instead of cut");
2116
+ if (info.isField) {
2117
+ replaceFieldSelection(info.el, "");
2118
+ return;
2119
+ }
2120
+ try {
2121
+ window.getSelection()?.deleteFromDocument();
2122
+ } catch {
2123
+ }
2124
+ }
2125
+ async function runEngineClipboard(ne, id) {
2126
+ const isTiny = isTinyEngine(ne);
2127
+ if (id === "paste") {
2128
+ const { html: html2, text: text2 } = await readClipboard();
2038
2129
  const content = html2 || (text2 ? escapeHtml(text2).replace(/\r?\n/g, "<br>") : "");
2039
2130
  if (!content)
2040
2131
  return;
@@ -2050,24 +2141,17 @@ async function runClipboardCommand(ne, id) {
2050
2141
  throw new Error("editor doesn't support paste");
2051
2142
  }
2052
2143
  expandToWord(ne);
2053
- let html = "";
2054
- let text = "";
2144
+ let html = "", text = "";
2055
2145
  if (isTiny) {
2056
2146
  html = ne.selection.getContent({ format: "html" });
2057
2147
  text = ne.selection.getContent({ format: "text" });
2058
2148
  } else {
2059
- text = window.getSelection()?.toString() || "";
2149
+ text = pageSelection();
2150
+ html = selectionHtml();
2060
2151
  }
2061
2152
  if (!text && !html)
2062
2153
  return;
2063
- if (html && typeof ClipboardItem === "function" && navigator.clipboard?.write) {
2064
- await navigator.clipboard.write([new ClipboardItem({
2065
- "text/html": new Blob([html], { type: "text/html" }),
2066
- "text/plain": new Blob([text], { type: "text/plain" })
2067
- })]);
2068
- } else {
2069
- await navigator.clipboard.writeText(text);
2070
- }
2154
+ await writeClipboard(text, html || void 0);
2071
2155
  if (id !== "cut")
2072
2156
  return;
2073
2157
  if (isTiny) {
@@ -2080,10 +2164,132 @@ async function runClipboardCommand(ne, id) {
2080
2164
  ne.deleteText(sel.index, sel.length);
2081
2165
  }
2082
2166
  }
2167
+ function selectionHtml() {
2168
+ try {
2169
+ const sel = window.getSelection();
2170
+ if (!sel || sel.rangeCount === 0 || sel.isCollapsed)
2171
+ return "";
2172
+ const div = document.createElement("div");
2173
+ div.appendChild(sel.getRangeAt(0).cloneContents());
2174
+ return div.innerHTML;
2175
+ } catch {
2176
+ return "";
2177
+ }
2178
+ }
2179
+ function stripHtml(html) {
2180
+ const div = document.createElement("div");
2181
+ div.innerHTML = html;
2182
+ return div.textContent || "";
2183
+ }
2184
+ function insertHtmlAtSelection(host, html) {
2185
+ const sel = window.getSelection();
2186
+ const frag = document.createRange().createContextualFragment(html);
2187
+ if (!sel || sel.rangeCount === 0) {
2188
+ host?.appendChild(frag);
2189
+ return;
2190
+ }
2191
+ const range = sel.getRangeAt(0);
2192
+ range.deleteContents();
2193
+ const last = frag.lastChild;
2194
+ range.insertNode(frag);
2195
+ if (last) {
2196
+ range.setStartAfter(last);
2197
+ range.collapse(true);
2198
+ sel.removeAllRanges();
2199
+ sel.addRange(range);
2200
+ }
2201
+ }
2202
+ function selectAll(info) {
2203
+ if (info.isField && info.el) {
2204
+ info.el.select();
2205
+ return;
2206
+ }
2207
+ const host = info.el;
2208
+ if (!host)
2209
+ return;
2210
+ const range = document.createRange();
2211
+ range.selectNodeContents(host);
2212
+ const sel = window.getSelection();
2213
+ sel?.removeAllRanges();
2214
+ sel?.addRange(range);
2215
+ }
2216
+ function editMenuItems(ctx, opts = {}) {
2217
+ const report = opts.onError || ((m) => console.warn(`[edit-menu] ${m}`));
2218
+ const info = ctx.engine ? { el: null, editable: true, selection: pageSelection(), canRead: true, isField: false } : describeEditTarget(ctx.target);
2219
+ if (!ctx.engine && !info.editable && !info.selection)
2220
+ return [];
2221
+ const run = (id) => () => {
2222
+ void runClipboard(ctx, id).catch((e) => {
2223
+ const key = id === "cut" ? "Ctrl+X" : id === "copy" ? "Ctrl+C" : "Ctrl+V";
2224
+ const what = id[0].toUpperCase() + id.slice(1);
2225
+ report(`${what} failed: ${e?.message || e}. ${key} still works.`);
2226
+ });
2227
+ };
2228
+ const hasSel = !!ctx.engine || !!info.selection;
2229
+ const items = [
2230
+ { label: "Cut", action: run("cut"), disabled: !info.editable || !hasSel || !info.canRead },
2231
+ { label: "Copy", action: run("copy"), disabled: !hasSel || !info.canRead },
2232
+ { label: "Paste", action: run("paste"), disabled: !info.editable }
2233
+ ];
2234
+ if (opts.selectAll !== false && !ctx.engine) {
2235
+ items.push({ label: "Select all", action: () => selectAll(info) });
2236
+ }
2237
+ return items;
2238
+ }
2239
+ var TEXTUAL_INPUT_TYPES;
2240
+ var init_edit_menu = __esm({
2241
+ "client/components/edit-menu.js"() {
2242
+ "use strict";
2243
+ TEXTUAL_INPUT_TYPES = /* @__PURE__ */ new Set([
2244
+ "text",
2245
+ "search",
2246
+ "email",
2247
+ "url",
2248
+ "tel",
2249
+ "number",
2250
+ "password",
2251
+ ""
2252
+ ]);
2253
+ }
2254
+ });
2255
+
2256
+ // client/compose/edit-commands.js
2257
+ var edit_commands_exports = {};
2258
+ __export(edit_commands_exports, {
2259
+ runFormatCommand: () => runFormatCommand,
2260
+ standardEditItems: () => standardEditItems
2261
+ });
2262
+ function isTinyEditor(ne) {
2263
+ return !!ne && typeof ne.execCommand === "function" && !!ne.selection;
2264
+ }
2265
+ function expandToWord2(ne) {
2266
+ if (isTinyEditor(ne)) {
2267
+ try {
2268
+ if (ne.selection.isCollapsed() && typeof ne.selection.expand === "function") {
2269
+ ne.selection.expand({ type: "word" });
2270
+ }
2271
+ } catch {
2272
+ }
2273
+ return;
2274
+ }
2275
+ if (ne && typeof ne.getSelection === "function" && typeof ne.setSelection === "function") {
2276
+ const sel = ne.getSelection();
2277
+ if (!sel || sel.length > 0)
2278
+ return;
2279
+ const text = ne.getText();
2280
+ let a = sel.index, b = sel.index;
2281
+ while (a > 0 && /\S/.test(text[a - 1]))
2282
+ a--;
2283
+ while (b < text.length && /\S/.test(text[b]))
2284
+ b++;
2285
+ if (b > a)
2286
+ ne.setSelection(a, b - a);
2287
+ }
2288
+ }
2083
2289
  function runFormatCommand(ne, id) {
2084
2290
  if (!ne)
2085
2291
  return;
2086
- expandToWord(ne);
2292
+ expandToWord2(ne);
2087
2293
  if (isTinyEditor(ne)) {
2088
2294
  const cmd = {
2089
2295
  bold: "Bold",
@@ -2129,20 +2335,11 @@ function runFormatCommand(ne, id) {
2129
2335
  }
2130
2336
  }
2131
2337
  function standardEditItems(getEditor, opts = {}) {
2132
- const report = opts.onError || ((m) => console.warn(`[edit-commands] ${m}`));
2133
- const clip = (id) => () => {
2134
- void runClipboardCommand(getEditor(), id).catch((e) => {
2135
- const key = id === "cut" ? "Ctrl+X" : id === "copy" ? "Ctrl+C" : "Ctrl+V";
2136
- report(`${id[0].toUpperCase()}${id.slice(1)} failed: ${e?.message || e}. ${key} still works.`);
2137
- });
2138
- };
2139
2338
  const fmt = (id) => () => runFormatCommand(getEditor(), id);
2140
2339
  const items = [
2141
2340
  { label: "", action: () => {
2142
2341
  }, separator: true },
2143
- { label: "Cut", action: clip("cut") },
2144
- { label: "Copy", action: clip("copy") },
2145
- { label: "Paste", action: clip("paste") },
2342
+ ...editMenuItems({ engine: getEditor() }, { onError: opts.onError }),
2146
2343
  { label: "", action: () => {
2147
2344
  }, separator: true },
2148
2345
  { label: "Bold", action: fmt("bold") },
@@ -2162,6 +2359,7 @@ function standardEditItems(getEditor, opts = {}) {
2162
2359
  var init_edit_commands = __esm({
2163
2360
  "client/compose/edit-commands.js"() {
2164
2361
  "use strict";
2362
+ init_edit_menu();
2165
2363
  }
2166
2364
  });
2167
2365
 
@@ -4620,6 +4818,7 @@ init_api_client();
4620
4818
  init_edit_commands();
4621
4819
 
4622
4820
  // client/components/context-menu.js
4821
+ init_edit_menu();
4623
4822
  var activeMenu = null;
4624
4823
  var dismissListener = null;
4625
4824
  var escapeListener = null;
@@ -4684,8 +4883,15 @@ function openSubmenu(parentRow, items) {
4684
4883
  sub.style.top = `${Math.max(4, top)}px`;
4685
4884
  activeSubmenu = sub;
4686
4885
  }
4687
- function showContextMenu(x, y, items) {
4886
+ function showContextMenu(x, y, items, opts = {}) {
4688
4887
  closeContextMenu();
4888
+ if (opts.editTarget !== void 0 || opts.editEngine) {
4889
+ const edit = editMenuItems({ target: opts.editTarget, engine: opts.editEngine }, { onError: opts.onEditError });
4890
+ if (edit.length > 0) {
4891
+ items = items.length > 0 ? [...edit, { label: "", action: () => {
4892
+ }, separator: true }, ...items] : edit;
4893
+ }
4894
+ }
4689
4895
  const menu = document.createElement("div");
4690
4896
  menu.className = "ctx-menu";
4691
4897
  for (const item of items) {
@@ -5129,43 +5335,8 @@ for (const el of [toInput, ccInput, bccInput]) {
5129
5335
  items.push({ label: `Copy address: ${addr.email}`, action: () => {
5130
5336
  void navigator.clipboard.writeText(addr.email);
5131
5337
  } });
5132
- items.push({ label: "", action: () => {
5133
- }, separator: true });
5134
5338
  }
5135
- const selText = () => el.value.slice(el.selectionStart ?? 0, el.selectionEnd ?? 0);
5136
- const replaceSelection = (text) => {
5137
- const s = el.selectionStart ?? el.value.length, en = el.selectionEnd ?? el.value.length;
5138
- el.value = el.value.slice(0, s) + text + el.value.slice(en);
5139
- el.selectionStart = el.selectionEnd = s + text.length;
5140
- el.dispatchEvent(new Event("input", { bubbles: true }));
5141
- };
5142
- items.push({ label: "Cut", action: async () => {
5143
- const t = selText();
5144
- if (t) {
5145
- try {
5146
- await navigator.clipboard.writeText(t);
5147
- } catch {
5148
- }
5149
- replaceSelection("");
5150
- }
5151
- } });
5152
- items.push({ label: "Copy", action: async () => {
5153
- const t = selText();
5154
- if (t) {
5155
- try {
5156
- await navigator.clipboard.writeText(t);
5157
- } catch {
5158
- }
5159
- }
5160
- } });
5161
- items.push({ label: "Paste", action: async () => {
5162
- try {
5163
- replaceSelection(await navigator.clipboard.readText());
5164
- } catch {
5165
- }
5166
- } });
5167
- items.push({ label: "Select all", action: () => el.select() });
5168
- showContextMenu(me.clientX, me.clientY, items);
5339
+ showContextMenu(me.clientX, me.clientY, items, { editTarget: el });
5169
5340
  });
5170
5341
  }
5171
5342
  var knownAccounts = [];