@richscripts/richtexteditor 2.4.0

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 (184) hide show
  1. package/README.md +360 -0
  2. package/admin/index.d.ts +100 -0
  3. package/admin/index.js +94 -0
  4. package/angular/component.ts +284 -0
  5. package/angular/index.d.ts +80 -0
  6. package/angular/index.ts +24 -0
  7. package/headless/index.d.ts +65 -0
  8. package/headless/index.js +272 -0
  9. package/index.d.ts +1412 -0
  10. package/integrations/shared.d.ts +138 -0
  11. package/integrations/shared.js +5182 -0
  12. package/package.json +130 -0
  13. package/react/index.d.ts +104 -0
  14. package/react/index.js +310 -0
  15. package/richtexteditor/CHANGELOG.md +1876 -0
  16. package/richtexteditor/all_plugins.js +31125 -0
  17. package/richtexteditor/all_plugins.min.js +78 -0
  18. package/richtexteditor/lang/rte-lang-af.js +193 -0
  19. package/richtexteditor/lang/rte-lang-ar.js +193 -0
  20. package/richtexteditor/lang/rte-lang-bg.js +193 -0
  21. package/richtexteditor/lang/rte-lang-bn.js +193 -0
  22. package/richtexteditor/lang/rte-lang-bs.js +193 -0
  23. package/richtexteditor/lang/rte-lang-ca.js +193 -0
  24. package/richtexteditor/lang/rte-lang-cs.js +193 -0
  25. package/richtexteditor/lang/rte-lang-cy.js +193 -0
  26. package/richtexteditor/lang/rte-lang-da.js +193 -0
  27. package/richtexteditor/lang/rte-lang-de.js +193 -0
  28. package/richtexteditor/lang/rte-lang-el.js +193 -0
  29. package/richtexteditor/lang/rte-lang-en.js +6 -0
  30. package/richtexteditor/lang/rte-lang-es.js +193 -0
  31. package/richtexteditor/lang/rte-lang-et.js +193 -0
  32. package/richtexteditor/lang/rte-lang-fa.js +193 -0
  33. package/richtexteditor/lang/rte-lang-fi.js +193 -0
  34. package/richtexteditor/lang/rte-lang-fil.js +193 -0
  35. package/richtexteditor/lang/rte-lang-fj.js +193 -0
  36. package/richtexteditor/lang/rte-lang-fr.js +193 -0
  37. package/richtexteditor/lang/rte-lang-ga.js +193 -0
  38. package/richtexteditor/lang/rte-lang-gu.js +193 -0
  39. package/richtexteditor/lang/rte-lang-he.js +193 -0
  40. package/richtexteditor/lang/rte-lang-hi.js +193 -0
  41. package/richtexteditor/lang/rte-lang-hr.js +193 -0
  42. package/richtexteditor/lang/rte-lang-ht.js +193 -0
  43. package/richtexteditor/lang/rte-lang-hu.js +193 -0
  44. package/richtexteditor/lang/rte-lang-id.js +193 -0
  45. package/richtexteditor/lang/rte-lang-is.js +193 -0
  46. package/richtexteditor/lang/rte-lang-it.js +193 -0
  47. package/richtexteditor/lang/rte-lang-ja.js +193 -0
  48. package/richtexteditor/lang/rte-lang-kn.js +193 -0
  49. package/richtexteditor/lang/rte-lang-ko.js +193 -0
  50. package/richtexteditor/lang/rte-lang-lt.js +193 -0
  51. package/richtexteditor/lang/rte-lang-lv.js +193 -0
  52. package/richtexteditor/lang/rte-lang-mg.js +193 -0
  53. package/richtexteditor/lang/rte-lang-mi.js +193 -0
  54. package/richtexteditor/lang/rte-lang-ml.js +193 -0
  55. package/richtexteditor/lang/rte-lang-mr.js +193 -0
  56. package/richtexteditor/lang/rte-lang-ms.js +193 -0
  57. package/richtexteditor/lang/rte-lang-mt.js +193 -0
  58. package/richtexteditor/lang/rte-lang-mww.js +193 -0
  59. package/richtexteditor/lang/rte-lang-nb.js +193 -0
  60. package/richtexteditor/lang/rte-lang-nl.js +193 -0
  61. package/richtexteditor/lang/rte-lang-otq.js +193 -0
  62. package/richtexteditor/lang/rte-lang-pa.js +193 -0
  63. package/richtexteditor/lang/rte-lang-pl.js +193 -0
  64. package/richtexteditor/lang/rte-lang-pt-pt.js +193 -0
  65. package/richtexteditor/lang/rte-lang-pt.js +193 -0
  66. package/richtexteditor/lang/rte-lang-ro.js +193 -0
  67. package/richtexteditor/lang/rte-lang-ru.js +193 -0
  68. package/richtexteditor/lang/rte-lang-sk.js +193 -0
  69. package/richtexteditor/lang/rte-lang-sl.js +193 -0
  70. package/richtexteditor/lang/rte-lang-sm.js +193 -0
  71. package/richtexteditor/lang/rte-lang-sr-Cyrl.js +193 -0
  72. package/richtexteditor/lang/rte-lang-sr-Latn.js +193 -0
  73. package/richtexteditor/lang/rte-lang-sv.js +193 -0
  74. package/richtexteditor/lang/rte-lang-sw.js +193 -0
  75. package/richtexteditor/lang/rte-lang-ta.js +193 -0
  76. package/richtexteditor/lang/rte-lang-te.js +193 -0
  77. package/richtexteditor/lang/rte-lang-th.js +193 -0
  78. package/richtexteditor/lang/rte-lang-tlh-Latn.js +193 -0
  79. package/richtexteditor/lang/rte-lang-tlh-Piqd.js +193 -0
  80. package/richtexteditor/lang/rte-lang-to.js +193 -0
  81. package/richtexteditor/lang/rte-lang-tr.js +193 -0
  82. package/richtexteditor/lang/rte-lang-ty.js +193 -0
  83. package/richtexteditor/lang/rte-lang-uk.js +193 -0
  84. package/richtexteditor/lang/rte-lang-ur.js +193 -0
  85. package/richtexteditor/lang/rte-lang-vi.js +193 -0
  86. package/richtexteditor/lang/rte-lang-yua.js +193 -0
  87. package/richtexteditor/lang/rte-lang-zh-Hans.js +193 -0
  88. package/richtexteditor/lang/rte-lang-zh-Hant.js +193 -0
  89. package/richtexteditor/lang/rte-lang.js +1 -0
  90. package/richtexteditor/plugins/-combine-plugin.txt +2 -0
  91. package/richtexteditor/plugins/-combine-plugins.bat +20 -0
  92. package/richtexteditor/plugins/a11yenhance.js +90 -0
  93. package/richtexteditor/plugins/accessibilitychecker.js +579 -0
  94. package/richtexteditor/plugins/aitoolkit.css +15830 -0
  95. package/richtexteditor/plugins/aitoolkit.js +19171 -0
  96. package/richtexteditor/plugins/all_plugins.js +31125 -0
  97. package/richtexteditor/plugins/all_plugins.min.js +78 -0
  98. package/richtexteditor/plugins/autoembed.js +174 -0
  99. package/richtexteditor/plugins/blocktypes.js +175 -0
  100. package/richtexteditor/plugins/bookmarkcard.js +226 -0
  101. package/richtexteditor/plugins/charlimit.js +195 -0
  102. package/richtexteditor/plugins/comments.js +672 -0
  103. package/richtexteditor/plugins/contentminimap.js +369 -0
  104. package/richtexteditor/plugins/crdt-engine.js +8905 -0
  105. package/richtexteditor/plugins/crdt-engine.min.js +5 -0
  106. package/richtexteditor/plugins/dialoga11y.js +174 -0
  107. package/richtexteditor/plugins/dictation.js +268 -0
  108. package/richtexteditor/plugins/documentimport.js +369 -0
  109. package/richtexteditor/plugins/documentoutline.js +341 -0
  110. package/richtexteditor/plugins/emailtoolkit.js +165 -0
  111. package/richtexteditor/plugins/emojiautocomplete.js +241 -0
  112. package/richtexteditor/plugins/foldheadings.js +181 -0
  113. package/richtexteditor/plugins/ghostcomplete.js +297 -0
  114. package/richtexteditor/plugins/html2pdf.js +158 -0
  115. package/richtexteditor/plugins/imageeditor.js +159 -0
  116. package/richtexteditor/plugins/insertcode.js +319 -0
  117. package/richtexteditor/plugins/insertemoji.js +229 -0
  118. package/richtexteditor/plugins/insertgallery.js +362 -0
  119. package/richtexteditor/plugins/inserttemplate.js +261 -0
  120. package/richtexteditor/plugins/localdraft.js +213 -0
  121. package/richtexteditor/plugins/markdownexport.js +110 -0
  122. package/richtexteditor/plugins/mathdialog.js +153 -0
  123. package/richtexteditor/plugins/mentionplugin.js +534 -0
  124. package/richtexteditor/plugins/mermaiddiagram.js +294 -0
  125. package/richtexteditor/plugins/readabilitystats.js +148 -0
  126. package/richtexteditor/plugins/readaloud.js +243 -0
  127. package/richtexteditor/plugins/restrictedediting.js +208 -0
  128. package/richtexteditor/plugins/revisionhistory.js +670 -0
  129. package/richtexteditor/plugins/samples/insertcode_v1.js +76 -0
  130. package/richtexteditor/plugins/slashcommand.js +705 -0
  131. package/richtexteditor/plugins/smartchips.js +250 -0
  132. package/richtexteditor/plugins/spellcheck.js +181 -0
  133. package/richtexteditor/plugins/todolist.js +137 -0
  134. package/richtexteditor/plugins/trackedchanges.js +658 -0
  135. package/richtexteditor/plugins/tui.image-editor/fabric.min.js +8 -0
  136. package/richtexteditor/plugins/tui.image-editor/note.txt +5 -0
  137. package/richtexteditor/plugins/tui.image-editor/svg/icon-a.svg +235 -0
  138. package/richtexteditor/plugins/tui.image-editor/svg/icon-b.svg +224 -0
  139. package/richtexteditor/plugins/tui.image-editor/svg/icon-c.svg +224 -0
  140. package/richtexteditor/plugins/tui.image-editor/svg/icon-d.svg +224 -0
  141. package/richtexteditor/plugins/tui.image-editor/tui-code-snippet.min.js +7 -0
  142. package/richtexteditor/plugins/tui.image-editor/tui-color-picker.min.css +6 -0
  143. package/richtexteditor/plugins/tui.image-editor/tui-color-picker.min.js +7 -0
  144. package/richtexteditor/plugins/tui.image-editor/tui-image-editor.min.css +7 -0
  145. package/richtexteditor/plugins/tui.image-editor/tui-image-editor.min.js +15 -0
  146. package/richtexteditor/plugins/typewriter.js +203 -0
  147. package/richtexteditor/plugins/wordexport.js +0 -0
  148. package/richtexteditor/plugins/yjscollab.js +693 -0
  149. package/richtexteditor/plugins/zz_richtextbox_dialog_style.js +335 -0
  150. package/richtexteditor/rte-config.js +933 -0
  151. package/richtexteditor/rte.js +943 -0
  152. package/richtexteditor/rte_mobile.css +234 -0
  153. package/richtexteditor/rte_theme_default.css +4819 -0
  154. package/richtexteditor/runtime/colorpicker_more_ns.htm +2613 -0
  155. package/richtexteditor/runtime/help.htm +162 -0
  156. package/richtexteditor/runtime/images/1x1.gif +0 -0
  157. package/richtexteditor/runtime/images/cpie_Color.cur +0 -0
  158. package/richtexteditor/runtime/images/cpie_GradientPositionDark.gif +0 -0
  159. package/richtexteditor/runtime/images/cpie_GradientPositionLight.gif +0 -0
  160. package/richtexteditor/runtime/images/cpie_VerticalPosition.gif +0 -0
  161. package/richtexteditor/runtime/images/cpie_WebSafe.gif +0 -0
  162. package/richtexteditor/runtime/images/cpie_gradients.png +0 -0
  163. package/richtexteditor/runtime/images/cpns_Color.cur +0 -0
  164. package/richtexteditor/runtime/images/cpns_ColorSpace1.png +0 -0
  165. package/richtexteditor/runtime/images/cpns_ColorSpace2.png +0 -0
  166. package/richtexteditor/runtime/images/cpns_GradientPositionDark.gif +0 -0
  167. package/richtexteditor/runtime/images/cpns_GradientPositionLight.gif +0 -0
  168. package/richtexteditor/runtime/images/cpns_Vertical1.png +0 -0
  169. package/richtexteditor/runtime/images/cpns_Vertical2.png +0 -0
  170. package/richtexteditor/runtime/images/cpns_VerticalPosition.gif +0 -0
  171. package/richtexteditor/runtime/images/cpns_WebSafe.gif +0 -0
  172. package/richtexteditor/runtime/images/cpns_gradients.png +0 -0
  173. package/richtexteditor/runtime/images/cpns_hue2.png +0 -0
  174. package/richtexteditor/runtime/images/formbn.gif +0 -0
  175. package/richtexteditor/runtime/images/multiclavier.gif +0 -0
  176. package/richtexteditor/runtime/richtexteditor_content.css +362 -0
  177. package/richtexteditor/runtime/richtexteditor_preview.css +302 -0
  178. package/richtexteditor/runtime/richtexteditor_preview.js +1 -0
  179. package/server/index.d.ts +21 -0
  180. package/server/index.js +23 -0
  181. package/svelte/index.d.ts +19 -0
  182. package/svelte/index.js +59 -0
  183. package/vue/index.d.ts +94 -0
  184. package/vue/index.js +291 -0
@@ -0,0 +1,174 @@
1
+ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
2
+
3
+ // 2026-06-05 Auto-embed on paste. Paste a bare YouTube or Vimeo URL on its own
4
+ // line and it becomes a responsive 16:9 embed — the Notion / Medium
5
+ // behaviour. Complements the command-driven Insert Video dialog and the
6
+ // bookmark-card plugin (which makes a link-preview card from any URL).
7
+ //
8
+ // Approach: the editor's own paste pipeline already inserts the URL, so rather
9
+ // than fight it we let the paste land, then (next tick) replace the block that
10
+ // now holds nothing but the pasted URL with the embed. Mid-paragraph pastes are
11
+ // left as-is (they should be links, not block embeds).
12
+ //
13
+ // Safety: the iframe src is NEVER the raw pasted string. We extract the video ID
14
+ // with a strict whitelist regex, constrain it to [A-Za-z0-9_-]/digits, and build
15
+ // a trusted provider embed URL from a template. Add providers via
16
+ // config.autoEmbedResolver (must return an https:// URL).
17
+ RTE_DefaultConfig.plugin_autoembed = RTE_Plugin_AutoEmbed;
18
+ RTE_DefaultConfig.autoEmbedEnabled = (typeof RTE_DefaultConfig.autoEmbedEnabled === "undefined")
19
+ ? true : RTE_DefaultConfig.autoEmbedEnabled;
20
+ RTE_DefaultConfig.autoEmbedResolver = RTE_DefaultConfig.autoEmbedResolver || null;
21
+
22
+ function RTE_Plugin_AutoEmbed() {
23
+ var obj = this;
24
+ var config, editor, editable;
25
+
26
+ obj.PluginName = "AutoEmbed";
27
+
28
+ obj.InitConfig = function (argconfig) { config = argconfig; };
29
+
30
+ var pasteDoc = null;
31
+ // Attach the paste listener to the iframe DOCUMENT (paste bubbles to it), and
32
+ // RE-ATTACH whenever the editor swaps in a new document/body. The editor can
33
+ // recreate both after init, so a one-shot attach at InitEditor is orphaned.
34
+ function ensurePasteListener() {
35
+ try {
36
+ var doc = editor.getDocument();
37
+ if (!doc || doc === pasteDoc) return;
38
+ doc.addEventListener("paste", onPaste, true);
39
+ pasteDoc = doc;
40
+ } catch (e) { /* ignore */ }
41
+ }
42
+
43
+ obj.InitEditor = function (argeditor) {
44
+ editor = argeditor;
45
+ ensurePasteListener();
46
+ try { editor.attachEvent("ready", ensurePasteListener); } catch (e) { /* ignore */ }
47
+ try { editor.attachEvent("aftersethtml", ensurePasteListener); } catch (e) { /* ignore */ }
48
+ // Belt-and-braces: the document can be created slightly after init.
49
+ setTimeout(ensurePasteListener, 0);
50
+ setTimeout(ensurePasteListener, 200);
51
+ // Public API: programmatically embed a media URL at the caret. Returns
52
+ // true if the URL resolved to a supported provider.
53
+ editor.embedUrl = function (url) {
54
+ var e = resolve(String(url || "").trim());
55
+ if (!e) return false;
56
+ insertEmbed(e);
57
+ return true;
58
+ };
59
+ };
60
+
61
+ // ---- provider resolution (id is whitelist-constrained before interpolation) ----
62
+ function resolveBuiltin(url) {
63
+ var m;
64
+ m = /^https?:\/\/(?:www\.)?youtube\.com\/watch\?(?:[^#]*&)?v=([A-Za-z0-9_-]{6,20})/i.exec(url);
65
+ if (m) return "https://www.youtube.com/embed/" + m[1];
66
+ m = /^https?:\/\/youtu\.be\/([A-Za-z0-9_-]{6,20})/i.exec(url);
67
+ if (m) return "https://www.youtube.com/embed/" + m[1];
68
+ m = /^https?:\/\/(?:www\.)?youtube\.com\/shorts\/([A-Za-z0-9_-]{6,20})/i.exec(url);
69
+ if (m) return "https://www.youtube.com/embed/" + m[1];
70
+ m = /^https?:\/\/(?:www\.)?vimeo\.com\/(\d{6,12})(?:[/?#]|$)/i.exec(url);
71
+ if (m) return "https://player.vimeo.com/video/" + m[1];
72
+ m = /^https?:\/\/player\.vimeo\.com\/video\/(\d{6,12})/i.exec(url);
73
+ if (m) return "https://player.vimeo.com/video/" + m[1];
74
+ return null;
75
+ }
76
+ function resolve(url) {
77
+ var built = resolveBuiltin(url);
78
+ if (built) return built;
79
+ if (typeof config.autoEmbedResolver === "function") {
80
+ try {
81
+ var r = config.autoEmbedResolver(url);
82
+ if (typeof r === "string" && /^https:\/\//i.test(r)) return r;
83
+ } catch (e) { /* ignore */ }
84
+ }
85
+ return null;
86
+ }
87
+
88
+ function isCollapsed() {
89
+ try { var s = editor.getSelection(); return s && s.rangeCount > 0 && s.isCollapsed; } catch (e) { return false; }
90
+ }
91
+
92
+ // ---- paste -> (next tick) replace the bare-URL block with an embed ----
93
+ function onPaste(e) {
94
+ if (!config.autoEmbedEnabled || !isCollapsed()) return;
95
+ var cd = e.clipboardData || window.clipboardData;
96
+ if (!cd) return;
97
+ var text;
98
+ try { text = cd.getData("text/plain"); } catch (er) { return; }
99
+ if (!text) return;
100
+ text = text.trim();
101
+ if (/\s/.test(text) || !/^https?:\/\/\S+$/i.test(text)) return;
102
+ var embedUrl = resolve(text);
103
+ if (!embedUrl) return;
104
+ // The editor inserts the pasted URL asynchronously, so poll briefly until
105
+ // the bare-URL block appears, then swap it for the embed.
106
+ var tries = 0;
107
+ (function attempt() {
108
+ if (replacePastedUrl(text, embedUrl)) return;
109
+ if (++tries < 12) setTimeout(attempt, 25);
110
+ })();
111
+ }
112
+
113
+ // Replace the top-level block whose only text is the pasted URL with an embed.
114
+ function replacePastedUrl(urlText, embedUrl) {
115
+ try {
116
+ var editable = editor.getEditable();
117
+ if (!editable) return false;
118
+ var blocks = editable.querySelectorAll("p,div,li,h1,h2,h3,h4,h5,h6");
119
+ for (var i = blocks.length - 1; i >= 0; i--) {
120
+ var b = blocks[i];
121
+ if (b.classList && b.classList.contains("rte-embed")) continue;
122
+ if (b.querySelector && (b.querySelector(".rte-embed") || b.querySelector("ul,ol,table"))) continue;
123
+ if ((b.textContent || "").trim() !== urlText) continue;
124
+ var doc = editor.getDocument();
125
+ injectStyles(doc);
126
+ var holder = doc.createElement("div");
127
+ holder.innerHTML = buildEmbedHtml(embedUrl) + '<p><br></p>';
128
+ var parent = b.parentNode; if (!parent) return;
129
+ var firstInserted = holder.firstChild, lastP = holder.lastChild;
130
+ while (holder.firstChild) parent.insertBefore(holder.firstChild, b);
131
+ parent.removeChild(b);
132
+ // caret into the trailing paragraph
133
+ try {
134
+ var r = doc.createRange(); r.setStart(lastP, 0); r.collapse(true);
135
+ var s = editor.getSelection(); if (s) { s.removeAllRanges(); s.addRange(r); }
136
+ } catch (e2) { /* ignore */ }
137
+ try { if (typeof editor.fireChange === "function") editor.fireChange(); } catch (e3) { /* ignore */ }
138
+ return true;
139
+ }
140
+ } catch (e) { /* ignore */ }
141
+ return false;
142
+ }
143
+
144
+ function buildEmbedHtml(embedUrl) {
145
+ var safe = String(embedUrl).replace(/"/g, "&quot;");
146
+ return '<div class="rte-embed" contenteditable="false" data-embed="' + safe + '" ' +
147
+ 'style="display:block;margin:12px 0;max-width:720px;">' +
148
+ '<div class="rte-embed-frame" style="position:relative;width:100%;height:0;padding-bottom:56.25%;' +
149
+ 'overflow:hidden;border-radius:10px;background:#000;">' +
150
+ '<iframe src="' + safe + '" loading="lazy" allowfullscreen ' +
151
+ 'referrerpolicy="strict-origin-when-cross-origin" ' +
152
+ 'allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" ' +
153
+ 'style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;"></iframe>' +
154
+ '</div></div>';
155
+ }
156
+
157
+ function injectStyles(doc) {
158
+ if (!doc || doc.getElementById("rte-autoembed-styles")) return;
159
+ var css = ".rte-embed{display:block;margin:12px 0;max-width:720px;}" +
160
+ ".rte-embed .rte-embed-frame{position:relative;width:100%;height:0;padding-bottom:56.25%;overflow:hidden;border-radius:10px;background:#000;}" +
161
+ ".rte-embed .rte-embed-frame iframe{position:absolute;top:0;left:0;width:100%;height:100%;border:0;}";
162
+ var st = doc.createElement("style");
163
+ st.id = "rte-autoembed-styles";
164
+ st.appendChild(doc.createTextNode(css));
165
+ (doc.head || doc.getElementsByTagName("head")[0] || doc.documentElement).appendChild(st);
166
+ }
167
+
168
+ // Programmatic insert at caret (public editor.embedUrl API).
169
+ function insertEmbed(embedUrl) {
170
+ injectStyles(editor.getDocument());
171
+ try { editor.insertHTML(buildEmbedHtml(embedUrl) + '<p><br></p>'); } catch (e) { /* ignore */ }
172
+ try { if (typeof editor.fireChange === "function") editor.fireChange(); } catch (e) { /* ignore */ }
173
+ }
174
+ }
@@ -0,0 +1,175 @@
1
+ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
2
+
3
+ // 2026-06-04 Modern block types: callout / multi-column / toggle.
4
+ // Closes the Tier-1 competitive gaps vs Notion / Google Docs / CKEditor:
5
+ // admonition callouts, multi-column layouts, and collapsible toggle blocks.
6
+ // Each block is editor-native HTML (no server), round-trips through
7
+ // getHTMLCode/getJSON, and is insertable via toolbar command + slash menu.
8
+ RTE_DefaultConfig.plugin_blocktypes = RTE_Plugin_BlockTypes;
9
+
10
+ // Callout variants: id -> { label, icon }. Customizable via config.calloutTypes.
11
+ RTE_DefaultConfig.calloutTypes = RTE_DefaultConfig.calloutTypes || [
12
+ { id: "info", label: "Info", icon: "ℹ️" }, // ℹ️
13
+ { id: "success", label: "Success", icon: "✅" }, // ✅
14
+ { id: "warning", label: "Warning", icon: "⚠️" }, // ⚠️
15
+ { id: "danger", label: "Danger", icon: "⛔" }, // ⛔
16
+ { id: "note", label: "Note", icon: "📝" } // 📝
17
+ ];
18
+ RTE_DefaultConfig.calloutDefaultType = RTE_DefaultConfig.calloutDefaultType || "info";
19
+ RTE_DefaultConfig.columnsDefaultCount = RTE_DefaultConfig.columnsDefaultCount || 2;
20
+
21
+ function RTE_Plugin_BlockTypes() {
22
+ var obj = this;
23
+ var config;
24
+ var editor;
25
+
26
+ obj.PluginName = "BlockTypes";
27
+
28
+ obj.InitConfig = function (argconfig) {
29
+ config = argconfig;
30
+ };
31
+
32
+ obj.InitEditor = function (argeditor) {
33
+ editor = argeditor;
34
+
35
+ editor.attachEvent("exec_command_insertcallout", function (state, cmd, value) {
36
+ state.returnValue = true;
37
+ obj.InsertCallout(value);
38
+ });
39
+ editor.attachEvent("exec_command_insertcolumns", function (state, cmd, value) {
40
+ state.returnValue = true;
41
+ obj.InsertColumns(value);
42
+ });
43
+ editor.attachEvent("exec_command_inserttoggle", function (state) {
44
+ state.returnValue = true;
45
+ obj.InsertToggle();
46
+ });
47
+
48
+ injectStyles();
49
+ };
50
+
51
+ function esc(s) {
52
+ return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
53
+ }
54
+
55
+ function calloutTypeById(id) {
56
+ var types = config.calloutTypes || [];
57
+ for (var i = 0; i < types.length; i++) {
58
+ if (types[i].id === id) return types[i];
59
+ }
60
+ return types[0] || { id: "info", label: "Info", icon: "ℹ️" };
61
+ }
62
+
63
+ // --- Insertion -------------------------------------------------------
64
+
65
+ obj.InsertCallout = function (typeId) {
66
+ var t = calloutTypeById(typeId || config.calloutDefaultType);
67
+ var bodyText = editor.getLangText ? (editor.getLangText("calloutplaceholder") || "Callout text") : "Callout text";
68
+ var html =
69
+ '<div class="rte-callout rte-callout-' + esc(t.id) + '" data-rte-block="callout" data-rte-callout-type="' + esc(t.id) + '">' +
70
+ '<div class="rte-callout-icon" contenteditable="false" data-rte-callout-icon="1">' + t.icon + '</div>' +
71
+ '<div class="rte-callout-body"><p>' + esc(bodyText) + '</p></div>' +
72
+ '</div><p><br></p>';
73
+ insertBlockHtml(html, "rte-callout-body");
74
+ };
75
+
76
+ obj.InsertColumns = function (count) {
77
+ count = parseInt(count, 10) || config.columnsDefaultCount || 2;
78
+ if (count < 2) count = 2;
79
+ if (count > 4) count = 4;
80
+ var cols = "";
81
+ var label = editor.getLangText ? (editor.getLangText("columnplaceholder") || "Column") : "Column";
82
+ for (var i = 0; i < count; i++) {
83
+ cols += '<div class="rte-column"><p>' + esc(label) + " " + (i + 1) + "</p></div>";
84
+ }
85
+ var html =
86
+ '<div class="rte-columns rte-columns-' + count + '" data-rte-block="columns" data-rte-columns="' + count + '">' +
87
+ cols + "</div><p><br></p>";
88
+ insertBlockHtml(html, "rte-column");
89
+ };
90
+
91
+ obj.InsertToggle = function () {
92
+ var summary = editor.getLangText ? (editor.getLangText("toggleplaceholder") || "Toggle title") : "Toggle title";
93
+ var body = editor.getLangText ? (editor.getLangText("togglebodyplaceholder") || "Hidden content") : "Hidden content";
94
+ var html =
95
+ '<details class="rte-toggle" data-rte-block="toggle" open>' +
96
+ '<summary class="rte-toggle-summary">' + esc(summary) + "</summary>" +
97
+ '<div class="rte-toggle-body"><p>' + esc(body) + "</p></div>" +
98
+ "</details><p><br></p>";
99
+ insertBlockHtml(html, "rte-toggle-summary");
100
+ };
101
+
102
+ // Insert block HTML, then try to move the caret into the first editable
103
+ // region (matched by class) so the user can type immediately.
104
+ function insertBlockHtml(html, focusClass) {
105
+ // 2026-06-04 Ensure styles are present BEFORE the block becomes visible.
106
+ // The InitEditor-time injection can no-op if the iframe document's
107
+ // <head> isn't ready yet at plugin-init; injecting here (idempotent,
108
+ // guarded by the data-attr check) guarantees the block is styled the
109
+ // moment it's inserted. Caught by visual verification — without this
110
+ // the blocks rendered unstyled (transparent callouts, block-display
111
+ // columns instead of flex).
112
+ injectStyles();
113
+ editor.insertHTML(html);
114
+ try {
115
+ var editdoc = editor.getDocument();
116
+ var editable = editor.getEditable();
117
+ var targets = editable.getElementsByClassName(focusClass);
118
+ if (targets && targets.length) {
119
+ var target = targets[targets.length - 1]; // the one we just inserted
120
+ var caretNode = target.querySelector("p") || target;
121
+ var range = editdoc.createRange();
122
+ range.selectNodeContents(caretNode);
123
+ range.collapse(true);
124
+ var sel = editor.getSelection();
125
+ sel.removeAllRanges();
126
+ sel.addRange(range);
127
+ }
128
+ } catch (e) { /* caret best-effort */ }
129
+ editor.focus();
130
+ }
131
+
132
+ // --- Styles ----------------------------------------------------------
133
+
134
+ function injectStyles() {
135
+ var css = [
136
+ // Callouts
137
+ ".rte-callout{display:flex;gap:10px;align-items:flex-start;margin:12px 0;padding:12px 14px;border-radius:8px;border:1px solid rgba(15,23,42,.12);border-left-width:4px;background:#f8fafc}",
138
+ ".rte-callout-icon{flex:0 0 auto;font-size:18px;line-height:1.4;user-select:none}",
139
+ ".rte-callout-body{flex:1;min-width:0}",
140
+ ".rte-callout-body > :first-child{margin-top:0}",
141
+ ".rte-callout-body > :last-child{margin-bottom:0}",
142
+ ".rte-callout-info{border-left-color:#2563eb;background:#eff6ff}",
143
+ ".rte-callout-success{border-left-color:#16a34a;background:#f0fdf4}",
144
+ ".rte-callout-warning{border-left-color:#d97706;background:#fffbeb}",
145
+ ".rte-callout-danger{border-left-color:#dc2626;background:#fef2f2}",
146
+ ".rte-callout-note{border-left-color:#7c3aed;background:#faf5ff}",
147
+ // Columns
148
+ ".rte-columns{display:flex;gap:16px;margin:12px 0;align-items:stretch}",
149
+ ".rte-column{flex:1 1 0;min-width:0;padding:8px 10px;border:1px dashed rgba(15,23,42,.15);border-radius:6px}",
150
+ ".rte-column > :first-child{margin-top:0}",
151
+ ".rte-column > :last-child{margin-bottom:0}",
152
+ // Toggle
153
+ ".rte-toggle{margin:12px 0;border:1px solid rgba(15,23,42,.12);border-radius:8px;padding:6px 12px;background:#fafbff}",
154
+ ".rte-toggle > summary{cursor:pointer;font-weight:600;outline:none;list-style:revert;padding:4px 0}",
155
+ ".rte-toggle-body{padding:4px 0 4px 16px}",
156
+ ".rte-toggle-body > :first-child{margin-top:0}",
157
+ ".rte-toggle-body > :last-child{margin-bottom:0}",
158
+ // Responsive: stack columns on narrow editors
159
+ "@media (max-width:640px){.rte-columns{flex-direction:column}}"
160
+ ].join("\n");
161
+
162
+ try {
163
+ var editdoc = editor.getDocument();
164
+ if (!editdoc) return;
165
+ // <head> may not exist yet at plugin-init; fall back to documentElement.
166
+ var head = editdoc.head || (editdoc.getElementsByTagName && editdoc.getElementsByTagName("head")[0]) || editdoc.documentElement;
167
+ if (head && !editdoc.querySelector("style[data-rte-blocktypes]")) {
168
+ var st = editdoc.createElement("style");
169
+ st.setAttribute("data-rte-blocktypes", "1");
170
+ st.textContent = css;
171
+ head.appendChild(st);
172
+ }
173
+ } catch (e) { /* ignore */ }
174
+ }
175
+ }
@@ -0,0 +1,226 @@
1
+ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
2
+
3
+ // 2026-06-04 Web-bookmark preview cards. Closes the Notion / Google Docs
4
+ // "bookmark with preview card" gap. Insert a URL as a rich link card
5
+ // (favicon + title + description + domain).
6
+ //
7
+ // Zero-infra by default: with no resolver configured it renders a card from
8
+ // the URL alone + the page favicon (via the public Google s2 favicon
9
+ // endpoint, an <img> the browser loads — no server of our own). For rich
10
+ // previews (title/description/og:image), wire a resolver — same BYOK shape as
11
+ // the AI toolkit:
12
+ // config.bookmarkResolver = function (url) {
13
+ // return fetch('/my/unfurl?u=' + encodeURIComponent(url))
14
+ // .then(function (r) { return r.json(); }); // { title, description, image, favicon }
15
+ // };
16
+ RTE_DefaultConfig.plugin_bookmarkcard = RTE_Plugin_BookmarkCard;
17
+ RTE_DefaultConfig.bookmarkResolver = RTE_DefaultConfig.bookmarkResolver || null;
18
+ RTE_DefaultConfig.bookmarkFaviconService = (typeof RTE_DefaultConfig.bookmarkFaviconService === "undefined")
19
+ ? "https://www.google.com/s2/favicons?sz=64&domain=" // set to "" to disable favicon fetch
20
+ : RTE_DefaultConfig.bookmarkFaviconService;
21
+
22
+ function RTE_Plugin_BookmarkCard() {
23
+ var obj = this;
24
+ var config, editor;
25
+
26
+ obj.PluginName = "BookmarkCard";
27
+
28
+ obj.InitConfig = function (argconfig) { config = argconfig; };
29
+
30
+ obj.InitEditor = function (argeditor) {
31
+ editor = argeditor;
32
+ editor.attachEvent("exec_command_insertbookmark", function (state) {
33
+ state.returnValue = true;
34
+ obj.OpenBookmarkDialog();
35
+ });
36
+ try {
37
+ var ed = editor.getEditable();
38
+ if (ed) {
39
+ ed.addEventListener("click", onEditableClick, true);
40
+ ed.addEventListener("keydown", onEditableKeyDown, true);
41
+ }
42
+ } catch (e) { /* ignore */ }
43
+ injectStyles();
44
+ };
45
+
46
+ function append(parent, tag, cssText, className) {
47
+ var el = parent.ownerDocument.createElement(tag);
48
+ if (cssText) el.style.cssText = cssText;
49
+ if (className) el.className = className;
50
+ parent.appendChild(el);
51
+ return el;
52
+ }
53
+
54
+ function esc(s) {
55
+ return String(s == null ? "" : s)
56
+ .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
57
+ .replace(/"/g, "&quot;");
58
+ }
59
+
60
+ // Accept only http/https; reject javascript:, data:, vbscript:, etc.
61
+ // Prepend https:// to bare domains so "example.com" works.
62
+ function normalizeUrl(raw) {
63
+ var u = String(raw || "").trim();
64
+ if (!u) return null;
65
+ if (/^(javascript|data|vbscript|file):/i.test(u)) return null;
66
+ if (!/^https?:\/\//i.test(u)) {
67
+ if (/^[a-z0-9.-]+\.[a-z]{2,}([\/?#].*)?$/i.test(u)) u = "https://" + u;
68
+ else return null;
69
+ }
70
+ try {
71
+ // Validate via URL when available.
72
+ if (typeof URL === "function") { var parsed = new URL(u); if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null; return parsed.href; }
73
+ } catch (e) { return null; }
74
+ return u;
75
+ }
76
+
77
+ function domainOf(url) {
78
+ try { return (new URL(url)).hostname.replace(/^www\./, ""); }
79
+ catch (e) { return url.replace(/^https?:\/\//i, "").split("/")[0].replace(/^www\./, ""); }
80
+ }
81
+ function closestBookmark(node, editable) {
82
+ while (node && node !== editable) {
83
+ if (node.nodeType === 1 && node.classList && node.classList.contains("rte-bookmark-card")) return node;
84
+ node = node.parentNode;
85
+ }
86
+ return null;
87
+ }
88
+ function removeBookmark(card) {
89
+ if (!card || !card.parentNode) return;
90
+ card.parentNode.removeChild(card);
91
+ try { if (typeof editor.fireChange === "function") editor.fireChange(); } catch (e) { /* ignore */ }
92
+ try { editor.focus(); } catch (e2) { /* ignore */ }
93
+ }
94
+ function onEditableClick(e) {
95
+ var target = e.target;
96
+ var editable = editor.getEditable();
97
+ while (target && target !== editable) {
98
+ if (target.nodeType === 1 && target.getAttribute && target.getAttribute("data-rte-bookmark-remove") === "1") {
99
+ e.preventDefault();
100
+ e.stopPropagation();
101
+ removeBookmark(closestBookmark(target, editable));
102
+ return;
103
+ }
104
+ target = target.parentNode;
105
+ }
106
+ }
107
+ function onEditableKeyDown(e) {
108
+ if ((e.key === "Enter" || e.key === " ") && e.target && e.target.getAttribute && e.target.getAttribute("data-rte-bookmark-remove") === "1") {
109
+ e.preventDefault();
110
+ e.stopPropagation();
111
+ removeBookmark(closestBookmark(e.target, editor.getEditable()));
112
+ return;
113
+ }
114
+ if (e.key !== "Delete" && e.key !== "Backspace") return;
115
+ var card = null;
116
+ try {
117
+ var sel = editor.getSelection();
118
+ if (sel && sel.rangeCount) card = closestBookmark(sel.getRangeAt(0).startContainer, editor.getEditable());
119
+ } catch (ex) { card = null; }
120
+ if (!card) return;
121
+ e.preventDefault();
122
+ removeBookmark(card);
123
+ }
124
+
125
+ function buildCardHtml(meta) {
126
+ var url = meta.url;
127
+ var domain = domainOf(url);
128
+ var title = meta.title || domain;
129
+ var desc = meta.description || "";
130
+ var favicon = meta.favicon || (config.bookmarkFaviconService ? (config.bookmarkFaviconService + encodeURIComponent(domain)) : "");
131
+ var image = meta.image || "";
132
+
133
+ var html = '<a class="rte-bookmark-card" data-rte-block="bookmark" href="' + esc(url) + '" target="_blank" rel="noopener noreferrer" contenteditable="false">';
134
+ html += '<span class="rte-bookmark-remove" data-rte-bookmark-remove="1" role="button" tabindex="0" aria-label="Remove bookmark" title="Remove bookmark">x</span>';
135
+ html += '<span class="rte-bookmark-main">';
136
+ html += '<span class="rte-bookmark-title">' + esc(title) + "</span>";
137
+ if (desc) html += '<span class="rte-bookmark-desc">' + esc(desc) + "</span>";
138
+ html += '<span class="rte-bookmark-url">';
139
+ if (favicon) html += '<img class="rte-bookmark-favicon" src="' + esc(favicon) + '" alt="" width="16" height="16">';
140
+ html += "<span>" + esc(domain) + "</span></span>";
141
+ html += "</span>";
142
+ if (image) html += '<span class="rte-bookmark-thumb" style="background-image:url(' + "'" + esc(image).replace(/'/g, "%27") + "'" + ')"></span>';
143
+ html += "</a><p><br></p>";
144
+ return html;
145
+ }
146
+
147
+ function insertCard(meta) {
148
+ injectStyles();
149
+ editor.insertHTML(buildCardHtml(meta));
150
+ editor.focus();
151
+ }
152
+
153
+ obj.OpenBookmarkDialog = function () {
154
+ var dlg = editor.createDialog(
155
+ (editor.getLangText && editor.getLangText("insertbookmarktitle")) || "Insert bookmark",
156
+ "rte-dialog-bookmark"
157
+ );
158
+ var close = typeof dlg.close === "function" ? function () { dlg.close(); } : function () { editor.closeCurrentPopup(); };
159
+ var wrap = append(dlg, "div", "padding:14px;min-width:420px;font:13px -apple-system,Segoe UI,sans-serif");
160
+
161
+ append(wrap, "div", "font-weight:600;margin-bottom:4px").innerText =
162
+ (editor.getLangText && editor.getLangText("insertbookmarklabel")) || "Link URL";
163
+ var input = append(wrap, "input", "width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid #cbd5e1;border-radius:8px;font:13px ui-monospace,monospace");
164
+ input.type = "url"; input.placeholder = "https://example.com/article";
165
+
166
+ var status = append(wrap, "div", "font-size:11px;color:#94a3b8;margin-top:6px;min-height:14px");
167
+ if (!config.bookmarkResolver) status.innerText = "No metadata resolver configured — a basic card (favicon + domain) will be inserted. Wire config.bookmarkResolver for rich previews.";
168
+
169
+ var footer = append(wrap, "div", "display:flex;justify-content:flex-end;gap:8px;margin-top:14px");
170
+ var cancel = append(footer, "button", "padding:6px 14px;border:1px solid #cbd5e1;border-radius:8px;background:#fff;cursor:pointer");
171
+ cancel.type = "button"; cancel.textContent = "Cancel"; cancel.onclick = close;
172
+ var ok = append(footer, "button", "padding:6px 14px;border:1px solid #1d67ba;border-radius:8px;background:#1d67ba;color:#fff;cursor:pointer");
173
+ ok.type = "button"; ok.textContent = "Insert";
174
+
175
+ setTimeout(function () { input.focus(); }, 0);
176
+ input.onkeydown = function (e) { if (e.key === "Enter") { e.preventDefault(); ok.click(); } };
177
+
178
+ ok.onclick = function () {
179
+ var url = normalizeUrl(input.value);
180
+ if (!url) { status.style.color = "#dc2626"; status.innerText = "Enter a valid http(s) URL."; return; }
181
+ if (config.bookmarkResolver && typeof config.bookmarkResolver === "function") {
182
+ ok.disabled = true; status.style.color = "#94a3b8"; status.innerText = "Fetching preview…";
183
+ var done = false;
184
+ function finish(meta) { if (done) return; done = true; meta = meta || {}; meta.url = url; close(); insertCard(meta); }
185
+ try {
186
+ var p = config.bookmarkResolver(url);
187
+ if (p && typeof p.then === "function") {
188
+ p.then(function (m) { finish(m || {}); }, function () { finish({}); });
189
+ setTimeout(function () { finish({}); }, 8000); // safety timeout → fallback card
190
+ } else { finish(p || {}); }
191
+ } catch (e) { finish({}); }
192
+ } else {
193
+ close();
194
+ insertCard({ url: url });
195
+ }
196
+ };
197
+ };
198
+
199
+ function injectStyles() {
200
+ var css = [
201
+ ".rte-bookmark-card{position:relative;display:flex;align-items:stretch;justify-content:space-between;gap:12px;margin:12px 0;border:1px solid rgba(15,23,42,.14);border-radius:10px;overflow:hidden;text-decoration:none;color:inherit;background:#fff;max-width:640px;transition:box-shadow 160ms,border-color 160ms}",
202
+ ".rte-bookmark-card:hover{border-color:rgba(37,99,235,.5);box-shadow:0 6px 18px rgba(15,23,42,.10)}",
203
+ ".rte-bookmark-remove{position:absolute;top:7px;right:7px;width:22px;height:22px;border:1px solid rgba(15,23,42,.14);border-radius:999px;background:rgba(255,255,255,.96);color:#334155;display:flex;align-items:center;justify-content:center;font:700 13px/1 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;box-shadow:0 4px 12px rgba(15,23,42,.12);opacity:0;transition:opacity 120ms,border-color 120ms,color 120ms;z-index:2}",
204
+ ".rte-bookmark-card:hover .rte-bookmark-remove,.rte-bookmark-card:focus .rte-bookmark-remove{opacity:1}",
205
+ ".rte-bookmark-remove:hover{border-color:rgba(220,38,38,.38);color:#b91c1c}",
206
+ ".rte-bookmark-main{flex:1;min-width:0;padding:12px 14px;display:flex;flex-direction:column;gap:4px}",
207
+ ".rte-bookmark-title{font-weight:600;font-size:14px;line-height:1.3;color:#0f172a;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}",
208
+ ".rte-bookmark-desc{font-size:12px;color:#64748b;line-height:1.4;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}",
209
+ ".rte-bookmark-url{display:flex;align-items:center;gap:6px;font-size:11px;color:#94a3b8;margin-top:2px}",
210
+ ".rte-bookmark-favicon{width:16px;height:16px;border-radius:3px;flex:0 0 16px}",
211
+ ".rte-bookmark-thumb{flex:0 0 120px;background-size:cover;background-position:center;background-color:#f1f5f9}",
212
+ "@media (max-width:480px){.rte-bookmark-thumb{display:none}}"
213
+ ].join("\n");
214
+ try {
215
+ var editdoc = editor.getDocument();
216
+ if (!editdoc) return;
217
+ var head = editdoc.head || (editdoc.getElementsByTagName && editdoc.getElementsByTagName("head")[0]) || editdoc.documentElement;
218
+ if (head && !editdoc.querySelector("style[data-rte-bookmarkcard]")) {
219
+ var st = editdoc.createElement("style");
220
+ st.setAttribute("data-rte-bookmarkcard", "1");
221
+ st.textContent = css;
222
+ head.appendChild(st);
223
+ }
224
+ } catch (e) { /* ignore */ }
225
+ }
226
+ }