@bobfrankston/rmfmail 1.1.203 → 1.1.205

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.
@@ -1900,6 +1900,26 @@ async function createTinyMceEditor(container2, opts = {}) {
1900
1900
  target.id = `rmf-tiny-${Math.random().toString(36).slice(2, 10)}`;
1901
1901
  target.style.cssText = "width:100%;height:100%;";
1902
1902
  container2.appendChild(target);
1903
+ const CODE_LANGS = [
1904
+ { text: "Text", value: "text" },
1905
+ { text: "HTML/XML", value: "markup" },
1906
+ { text: "JavaScript", value: "javascript" },
1907
+ { text: "TypeScript", value: "typescript" },
1908
+ { text: "CSS", value: "css" },
1909
+ { text: "JSON", value: "json" },
1910
+ { text: "Python", value: "python" },
1911
+ { text: "Java", value: "java" },
1912
+ { text: "C", value: "c" },
1913
+ { text: "C++", value: "cpp" },
1914
+ { text: "C#", value: "csharp" },
1915
+ { text: "Go", value: "go" },
1916
+ { text: "Rust", value: "rust" },
1917
+ { text: "Ruby", value: "ruby" },
1918
+ { text: "PHP", value: "php" },
1919
+ { text: "Shell", value: "bash" },
1920
+ { text: "PowerShell", value: "powershell" },
1921
+ { text: "SQL", value: "sql" }
1922
+ ];
1903
1923
  const editor2 = await new Promise((resolve) => {
1904
1924
  tinymce.init({
1905
1925
  target,
@@ -1912,7 +1932,7 @@ async function createTinyMceEditor(container2, opts = {}) {
1912
1932
  plugins: "paste lists advlist link table code codesample image searchreplace autolink wordcount emoticons charmap insertdatetime quickbars nonbreaking directionality help",
1913
1933
  toolbar: [
1914
1934
  "undo redo | bold italic underline strikethrough | forecolor backcolor",
1915
- "bullist numlist outdent indent | link table image code codesample | emoticons charmap | help"
1935
+ "bullist numlist outdent indent | link table image code rmfcode | emoticons charmap | help"
1916
1936
  ].join(" | "),
1917
1937
  // Include "tools" so wordcount and searchreplace are reachable.
1918
1938
  menubar: "file edit view insert format tools",
@@ -1939,25 +1959,7 @@ async function createTinyMceEditor(container2, opts = {}) {
1939
1959
  // which mangles unrelated paste content (Bob 2026-05-24).
1940
1960
  // Adding Text first so it's the default; rest are the modern
1941
1961
  // languages we actually paste.
1942
- codesample_languages: [
1943
- { text: "Text", value: "text" },
1944
- { text: "HTML/XML", value: "markup" },
1945
- { text: "JavaScript", value: "javascript" },
1946
- { text: "TypeScript", value: "typescript" },
1947
- { text: "CSS", value: "css" },
1948
- { text: "JSON", value: "json" },
1949
- { text: "Python", value: "python" },
1950
- { text: "Java", value: "java" },
1951
- { text: "C", value: "c" },
1952
- { text: "C++", value: "cpp" },
1953
- { text: "C#", value: "csharp" },
1954
- { text: "Go", value: "go" },
1955
- { text: "Rust", value: "rust" },
1956
- { text: "Ruby", value: "ruby" },
1957
- { text: "PHP", value: "php" },
1958
- { text: "Shell", value: "bash" },
1959
- { text: "SQL", value: "sql" }
1960
- ],
1962
+ codesample_languages: CODE_LANGS,
1961
1963
  // WebView's native spell-check (red underlines, right-click
1962
1964
  // "Add to dictionary"). Free; same UX as Quill's spellcheck=true.
1963
1965
  // Premium tinymcespellchecker plugin would replace this with a
@@ -1975,6 +1977,17 @@ async function createTinyMceEditor(container2, opts = {}) {
1975
1977
  statusbar: false,
1976
1978
  branding: false,
1977
1979
  license_key: "gpl",
1980
+ // Keep URLs verbatim. TinyMCE defaults to convert_urls:true +
1981
+ // relative_urls:true, which rewrites an absolute href
1982
+ // (https://github.com/…) to be RELATIVE to the editor's base URL
1983
+ // (the msger custom-protocol page). A link that worked in the
1984
+ // original message arrives in the reply quote rewritten/dead
1985
+ // (Bob 2026-05-31: "URL works in the original but not in the
1986
+ // reply"). false on both = hrefs travel into the sent message
1987
+ // exactly as the sender wrote them.
1988
+ convert_urls: false,
1989
+ relative_urls: false,
1990
+ remove_script_host: false,
1978
1991
  paste_data_images: true,
1979
1992
  // Permissive valid_elements — preserve as much of the source
1980
1993
  // formatting as possible. The default is more aggressive about
@@ -2063,6 +2076,60 @@ async function createTinyMceEditor(container2, opts = {}) {
2063
2076
  applyZoom();
2064
2077
  saveZoom();
2065
2078
  };
2079
+ const BORDER_STYLES = { "border": "1px solid #888", "border-radius": "4px", "padding": "10px" };
2080
+ const openCodeDialog = () => {
2081
+ const cs = ed.plugins.codesample;
2082
+ const preEl = ed.dom.getParent(ed.selection.getNode(), "pre");
2083
+ let curLang = "text";
2084
+ let curBorder = false;
2085
+ if (preEl) {
2086
+ const m = (preEl.className || "").match(/language-([\w-]+)/);
2087
+ if (m)
2088
+ curLang = m[1];
2089
+ curBorder = !!(preEl.style && preEl.style.border && preEl.style.border !== "none");
2090
+ }
2091
+ const curCode = cs && cs.getCurrentCode ? cs.getCurrentCode(ed) || "" : "";
2092
+ ed.windowManager.open({
2093
+ title: "Insert code",
2094
+ size: "large",
2095
+ body: {
2096
+ type: "panel",
2097
+ items: [
2098
+ { type: "listbox", name: "language", label: "Language", items: CODE_LANGS },
2099
+ { type: "textarea", name: "code", label: "Code" },
2100
+ { type: "checkbox", name: "border", label: "Box it in (border around the code)" }
2101
+ ]
2102
+ },
2103
+ initialData: { language: curLang, code: curCode, border: curBorder },
2104
+ buttons: [
2105
+ { type: "cancel", text: "Cancel" },
2106
+ { type: "submit", text: "Save", primary: true }
2107
+ ],
2108
+ onSubmit: (api) => {
2109
+ const data = api.getData();
2110
+ if (cs && cs.insertCodeSample)
2111
+ cs.insertCodeSample(data.language, data.code);
2112
+ const pre = ed.dom.getParent(ed.selection.getNode(), "pre");
2113
+ if (pre) {
2114
+ if (data.border)
2115
+ ed.dom.setStyles(pre, BORDER_STYLES);
2116
+ else
2117
+ ed.dom.setStyles(pre, { "border": "", "border-radius": "", "padding": "" });
2118
+ }
2119
+ api.close();
2120
+ }
2121
+ });
2122
+ };
2123
+ ed.ui.registry.addButton("rmfcode", {
2124
+ icon: "code-sample",
2125
+ tooltip: "Insert/edit code block",
2126
+ onAction: openCodeDialog
2127
+ });
2128
+ ed.ui.registry.addMenuItem("rmfcode", {
2129
+ icon: "code-sample",
2130
+ text: "Code block\u2026",
2131
+ onAction: openCodeDialog
2132
+ });
2066
2133
  ed.ui.registry.addMenuItem("zoomIn", {
2067
2134
  text: "Zoom in",
2068
2135
  shortcut: "Ctrl+=",
@@ -2114,11 +2181,19 @@ async function createTinyMceEditor(container2, opts = {}) {
2114
2181
  }
2115
2182
  if (tail.toString().length > 0)
2116
2183
  return;
2117
- const after = doc.createRange();
2118
- after.setStartAfter(link);
2119
- after.collapse(true);
2120
- sel.removeAllRanges();
2121
- sel.addRange(after);
2184
+ const parent2 = link.parentNode;
2185
+ const idx = parent2 ? Array.prototype.indexOf.call(parent2.childNodes, link) + 1 : -1;
2186
+ if (parent2 && idx >= 0) {
2187
+ try {
2188
+ ed.selection.setCursorLocation(parent2, idx);
2189
+ } catch {
2190
+ const after = doc.createRange();
2191
+ after.setStartAfter(link);
2192
+ after.collapse(true);
2193
+ sel.removeAllRanges();
2194
+ sel.addRange(after);
2195
+ }
2196
+ }
2122
2197
  }, true);
2123
2198
  };
2124
2199
  ed.on("init", installLinkEscape);