@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,241 @@
1
+ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
2
+
3
+ // 2026-06-05 Emoji :shortcode: autocomplete (GitHub / Slack / Discord style).
4
+ // Two behaviours, both client-side, no data bundled beyond a curated shortcode
5
+ // map:
6
+ // 1. Type a complete `:shortcode:` (closing colon) -> replaced with the emoji.
7
+ // 2. Type `:` + a partial query -> a popup of matches appears; Arrow keys move,
8
+ // Enter/Tab insert, Esc dismisses.
9
+ // Complements the existing emoji picker (insertemoji) with inline, keyboard-only
10
+ // entry. Extend or replace the map via config.emojiShortcodes.
11
+ RTE_DefaultConfig.plugin_emojiautocomplete = RTE_Plugin_EmojiAutocomplete;
12
+ RTE_DefaultConfig.emojiAutocompleteEnabled = (typeof RTE_DefaultConfig.emojiAutocompleteEnabled === "undefined")
13
+ ? true : RTE_DefaultConfig.emojiAutocompleteEnabled;
14
+ RTE_DefaultConfig.emojiShortcodes = RTE_DefaultConfig.emojiShortcodes || {
15
+ smile: "😄", smiley: "😃", grin: "😁", laughing: "😆", satisfied: "😆", joy: "😂", rofl: "🤣",
16
+ sweat_smile: "😅", wink: "😉", blush: "😊", innocent: "😇", heart_eyes: "😍", star_struck: "🤩",
17
+ kissing_heart: "😘", yum: "😋", stuck_out_tongue: "😛", zany: "🤪", thinking: "🤔", shush: "🤫",
18
+ neutral_face: "😐", expressionless: "😑", no_mouth: "😶", smirk: "😏", unamused: "😒", roll_eyes: "🙄",
19
+ grimacing: "😬", relieved: "😌", pensive: "😔", sleepy: "😪", sleeping: "😴", mask: "😷",
20
+ face_with_thermometer: "🤒", nauseated_face: "🤢", sneezing_face: "🤧", dizzy_face: "😵",
21
+ exploding_head: "🤯", cowboy: "🤠", sunglasses: "😎", nerd: "🤓", monocle: "🧐", confused: "😕",
22
+ worried: "😟", slightly_frowning_face: "🙁", open_mouth: "😮", astonished: "😲", flushed: "😳",
23
+ fearful: "😨", cold_sweat: "😰", cry: "😢", sob: "😭", scream: "😱", confounded: "😖", persevere: "😣",
24
+ disappointed: "😞", sweat: "😓", weary: "😩", tired_face: "😫", triumph: "😤", rage: "😡", angry: "😠",
25
+ cursing_face: "🤬", smiling_imp: "😈", imp: "👿", skull: "💀", poop: "💩", hankey: "💩", clown: "🤡",
26
+ ghost: "👻", alien: "👽", robot: "🤖", heart_eyes_cat: "😻", see_no_evil: "🙈", hear_no_evil: "🙉",
27
+ speak_no_evil: "🙊",
28
+ heart: "❤️", orange_heart: "🧡", yellow_heart: "💛", green_heart: "💚", blue_heart: "💙",
29
+ purple_heart: "💜", black_heart: "🖤", broken_heart: "💔", two_hearts: "💕", sparkling_heart: "💖",
30
+ heartpulse: "💗", cupid: "💘", gift_heart: "💝", revolving_hearts: "💞", kiss: "💋",
31
+ "+1": "👍", thumbsup: "👍", "-1": "👎", thumbsdown: "👎", ok_hand: "👌", punch: "👊", fist: "✊",
32
+ facepunch: "👊", wave: "👋", raised_hand: "✋", raised_hands: "🙌", pray: "🙏", clap: "👏",
33
+ muscle: "💪", point_up: "☝️", point_down: "👇", point_left: "👈", point_right: "👉", v: "✌️",
34
+ crossed_fingers: "🤞", handshake: "🤝", writing_hand: "✍️", selfie: "🤳", nail_care: "💅", eyes: "👀",
35
+ fire: "🔥", star: "⭐", star2: "🌟", sparkles: "✨", zap: "⚡", boom: "💥", collision: "💥",
36
+ dizzy: "💫", sweat_drops: "💦", droplet: "💧", snowflake: "❄️", sunny: "☀️", cloud: "☁️",
37
+ rainbow: "🌈", umbrella: "☔", ocean: "🌊", sun_with_face: "🌞", full_moon: "🌕", crescent_moon: "🌙",
38
+ tada: "🎉", confetti_ball: "🎊", balloon: "🎈", gift: "🎁", trophy: "🏆", medal: "🏅", crown: "👑",
39
+ gem: "💎", rocket: "🚀", airplane: "✈️", car: "🚗", rotating_light: "🚨",
40
+ "100": "💯", white_check_mark: "✅", heavy_check_mark: "✔️", ballot_box_with_check: "☑️", x: "❌",
41
+ warning: "⚠️", no_entry: "⛔", question: "❓", exclamation: "❗", bulb: "💡", lock: "🔒", key: "🔑",
42
+ mag: "🔍", bell: "🔔", recycle: "♻️", heavy_plus_sign: "➕", heavy_minus_sign: "➖",
43
+ coffee: "☕", tea: "🍵", beer: "🍺", beers: "🍻", wine_glass: "🍷", cocktail: "🍸", pizza: "🍕",
44
+ hamburger: "🍔", fries: "🍟", taco: "🌮", cake: "🎂", birthday: "🎂", cookie: "🍪", doughnut: "🍩",
45
+ apple: "🍎", banana: "🍌", strawberry: "🍓", avocado: "🥑", bread: "🍞", cheese: "🧀",
46
+ dog: "🐶", cat: "🐱", mouse: "🐭", rabbit: "🐰", bear: "🐻", panda_face: "🐼", koala: "🐨",
47
+ tiger: "🐯", lion: "🦁", cow: "🐮", pig: "🐷", frog: "🐸", monkey_face: "🐵", chicken: "🐔",
48
+ penguin: "🐧", bird: "🐦", unicorn: "🦄", bee: "🐝", bug: "🐛", butterfly: "🦋", snail: "🐌",
49
+ book: "📖", books: "📚", memo: "📝", pencil: "📝", email: "✉️", envelope: "✉️", calendar: "📅",
50
+ chart: "📊", bar_chart: "📊", chart_with_upwards_trend: "📈", clipboard: "📋", pushpin: "📌",
51
+ paperclip: "📎", scissors: "✂️", hammer: "🔨", wrench: "🔧", gear: "⚙️", nut_and_bolt: "🔩",
52
+ bomb: "💣", hourglass: "⌛", watch: "⌚", alarm_clock: "⏰", moneybag: "💰", dollar: "💵",
53
+ credit_card: "💳", chart_increasing: "📈", link: "🔗", mag_right: "🔎", telescope: "🔭",
54
+ microscope: "🔬", syringe: "💉", pill: "💊", phone: "📞", telephone: "📞", iphone: "📱",
55
+ computer: "💻", keyboard: "⌨️", printer: "🖨️", camera: "📷", movie_camera: "🎥", tv: "📺",
56
+ seedling: "🌱", evergreen_tree: "🌲", deciduous_tree: "🌳", palm_tree: "🌴", cactus: "🌵",
57
+ four_leaf_clover: "🍀", rose: "🌹", sunflower: "🌻", tulip: "🌷", blossom: "🌼", herb: "🌿",
58
+ musical_note: "🎵", notes: "🎶", microphone: "🎤", headphones: "🎧", guitar: "🎸", trumpet: "🎺",
59
+ soccer: "⚽", basketball: "🏀", football: "🏈", baseball: "⚾", tennis: "🎾", "8ball": "🎱",
60
+ dart: "🎯", video_game: "🎮", game_die: "🎲", trophy2: "🏆",
61
+ sos: "🆘", new: "🆕", ok: "🆗", up: "🆙", cool: "🆒", free: "🆓",
62
+ arrow_up: "⬆️", arrow_down: "⬇️", arrow_left: "⬅️", arrow_right: "➡️", arrow_forward: "▶️",
63
+ bangbang: "‼️", interrobang: "⁉️", grey_question: "❔", grey_exclamation: "❕"
64
+ };
65
+
66
+ function RTE_Plugin_EmojiAutocomplete() {
67
+ var obj = this;
68
+ var config, editor, editable, editdoc;
69
+ var pop = null, items = [], sel = 0, queryStart = -1, queryNode = null;
70
+
71
+ obj.PluginName = "EmojiAutocomplete";
72
+
73
+ obj.InitConfig = function (argconfig) { config = argconfig; };
74
+
75
+ obj.InitEditor = function (argeditor) {
76
+ editor = argeditor;
77
+ editable = editor.getEditable();
78
+ editdoc = editor.getDocument();
79
+ if (!editable || !editdoc) return;
80
+ editdoc.addEventListener("input", onInput, false);
81
+ // keydown in capture so we intercept nav keys before the editor does.
82
+ editdoc.addEventListener("keydown", onKeyDown, true);
83
+ editdoc.addEventListener("blur", function () { setTimeout(closePopup, 150); }, true);
84
+ };
85
+
86
+ function map() { return config.emojiShortcodes || {}; }
87
+
88
+ function getCaret() {
89
+ var s = editor.getSelection();
90
+ if (!s || s.rangeCount === 0 || !s.isCollapsed) return null;
91
+ var r = s.getRangeAt(0);
92
+ if (r.startContainer.nodeType !== 3) return null;
93
+ // never inside code / pre
94
+ var p = r.startContainer.parentNode;
95
+ while (p && p !== editable) { if (p.nodeName === "CODE" || p.nodeName === "PRE") return null; p = p.parentNode; }
96
+ return r;
97
+ }
98
+
99
+ function onInput() {
100
+ if (!config.emojiAutocompleteEnabled) return;
101
+ var r = getCaret();
102
+ if (!r) { closePopup(); return; }
103
+ var node = r.startContainer, off = r.startOffset;
104
+ var before = node.data.slice(0, off);
105
+
106
+ // 1. complete :shortcode: -> immediate replace
107
+ var full = /(^|[^a-z0-9_])(:[a-z0-9_+\-]{1,30}:)$/i.exec(before);
108
+ if (full) {
109
+ var code = full[2].slice(1, -1).toLowerCase();
110
+ var emo = map()[code];
111
+ if (emo) {
112
+ replaceRange(node, off - full[2].length, off, emo);
113
+ closePopup();
114
+ return;
115
+ }
116
+ }
117
+ // 2. partial :query -> popup
118
+ var part = /(^|[^a-z0-9_])(:)([a-z0-9_+\-]{1,30})$/i.exec(before);
119
+ if (part) {
120
+ var q = part[3].toLowerCase();
121
+ queryNode = node; queryStart = off - q.length - 1; // include the ":"
122
+ showMatches(q, r);
123
+ } else {
124
+ closePopup();
125
+ }
126
+ }
127
+
128
+ function showMatches(q, range) {
129
+ var m = map(), keys = Object.keys(m), starts = [], contains = [];
130
+ for (var i = 0; i < keys.length; i++) {
131
+ var k = keys[i];
132
+ if (k.indexOf(q) === 0) starts.push(k);
133
+ else if (k.indexOf(q) !== -1) contains.push(k);
134
+ }
135
+ // de-dupe emoji glyphs while keeping the first (best) shortcode
136
+ var ordered = starts.concat(contains), seen = {}, list = [];
137
+ for (var j = 0; j < ordered.length && list.length < 8; j++) {
138
+ var emo = m[ordered[j]];
139
+ if (seen[emo]) continue;
140
+ seen[emo] = 1; list.push({ code: ordered[j], emo: emo });
141
+ }
142
+ if (!list.length) { closePopup(); return; }
143
+ items = list; sel = 0;
144
+ renderPopup(range);
145
+ }
146
+
147
+ function renderPopup(range) {
148
+ var hostDoc = document;
149
+ if (!pop) {
150
+ pop = hostDoc.createElement("div");
151
+ pop.className = "rte-emoji-pop";
152
+ pop.setAttribute("role", "listbox");
153
+ pop.setAttribute("aria-label", "Emoji suggestions");
154
+ pop.style.cssText = "position:fixed;z-index:2147483600;";
155
+ hostDoc.body.appendChild(pop);
156
+ }
157
+ pop.innerHTML = "";
158
+ items.forEach(function (it, i) {
159
+ var row = hostDoc.createElement("div");
160
+ row.className = "rte-emoji-pop-row" + (i === sel ? " is-active" : "");
161
+ row.setAttribute("role", "option");
162
+ row.setAttribute("aria-selected", i === sel ? "true" : "false");
163
+ var glyph = hostDoc.createElement("span");
164
+ glyph.className = "rte-emoji-pop-glyph";
165
+ glyph.textContent = it.emo;
166
+ var code = hostDoc.createElement("span");
167
+ code.className = "rte-emoji-pop-code";
168
+ code.textContent = ":" + it.code + ":";
169
+ row.appendChild(glyph);
170
+ row.appendChild(code);
171
+ row.onmousedown = function (e) { e.preventDefault(); sel = i; commit(); };
172
+ row.onmouseenter = function () { sel = i; highlightRows(); };
173
+ pop.appendChild(row);
174
+ });
175
+ // position near the caret (iframe rect + caret rect)
176
+ try {
177
+ var cr = range.getBoundingClientRect();
178
+ var fr = (editdoc.defaultView && editdoc.defaultView.frameElement) ? editdoc.defaultView.frameElement.getBoundingClientRect() : { left: 0, top: 0 };
179
+ var x = fr.left + cr.left, y = fr.top + cr.bottom + 4;
180
+ var vw = hostDoc.documentElement.clientWidth, vh = hostDoc.documentElement.clientHeight;
181
+ pop.style.left = Math.max(4, Math.min(x, vw - 290)) + "px";
182
+ pop.style.top = Math.max(4, Math.min(y, vh - 20)) + "px";
183
+ pop.style.display = "block";
184
+ } catch (e) { /* ignore */ }
185
+ }
186
+
187
+ function highlightRows() {
188
+ if (!pop) return;
189
+ var rows = pop.childNodes;
190
+ for (var i = 0; i < rows.length; i++) {
191
+ rows[i].classList.toggle("is-active", i === sel);
192
+ rows[i].setAttribute("aria-selected", i === sel ? "true" : "false");
193
+ }
194
+ }
195
+
196
+ function onKeyDown(e) {
197
+ if (!pop || pop.style.display === "none" || !items.length) return;
198
+ switch (e.key) {
199
+ case "ArrowDown": e.preventDefault(); e.stopPropagation(); sel = (sel + 1) % items.length; highlightRows(); break;
200
+ case "ArrowUp": e.preventDefault(); e.stopPropagation(); sel = (sel - 1 + items.length) % items.length; highlightRows(); break;
201
+ case "Enter":
202
+ case "Tab": e.preventDefault(); e.stopPropagation(); commit(); break;
203
+ case "Escape": e.preventDefault(); e.stopPropagation(); closePopup(); break;
204
+ }
205
+ }
206
+
207
+ function commit() {
208
+ if (!items.length || !queryNode || queryStart < 0) { closePopup(); return; }
209
+ var it = items[sel] || items[0];
210
+ var r = getCaret();
211
+ if (!r || r.startContainer !== queryNode) { closePopup(); return; }
212
+ replaceRange(queryNode, queryStart, r.startOffset, it.emo);
213
+ closePopup();
214
+ }
215
+
216
+ function replaceRange(node, start, end, text) {
217
+ try {
218
+ var data = node.data;
219
+ var head = data.slice(0, start), tail = data.slice(end);
220
+ var parent = node.parentNode; if (!parent) return;
221
+ var frag = editdoc.createDocumentFragment();
222
+ if (head) frag.appendChild(editdoc.createTextNode(head));
223
+ var emoNode = editdoc.createTextNode(text);
224
+ frag.appendChild(emoNode);
225
+ var tailNode = tail ? editdoc.createTextNode(tail) : null;
226
+ if (tailNode) frag.appendChild(tailNode);
227
+ parent.replaceChild(frag, node);
228
+ var rr = editdoc.createRange();
229
+ if (tailNode) rr.setStart(tailNode, 0); else rr.setStartAfter(emoNode);
230
+ rr.collapse(true);
231
+ var s = editor.getSelection();
232
+ if (s) { s.removeAllRanges(); s.addRange(rr); }
233
+ if (typeof editor.fireChange === "function") { try { editor.fireChange(); } catch (e) {} }
234
+ } catch (e) { /* ignore */ }
235
+ }
236
+
237
+ function closePopup() {
238
+ if (pop) pop.style.display = "none";
239
+ items = []; sel = 0; queryStart = -1; queryNode = null;
240
+ }
241
+ }
@@ -0,0 +1,181 @@
1
+ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
2
+
3
+ // 2026-06-06 Foldable headings. Obsidian / Notion / Word-style section folding:
4
+ // click the gutter chevron next to a heading (or call the API) to collapse every
5
+ // block beneath it, up to the next heading of the same or higher level. Great for
6
+ // navigating long structured documents.
7
+ //
8
+ // Serialization-safe by design: the fold state lives ONLY in runtime CSS classes
9
+ // (rte-fold-collapsed on the heading, rte-fold-hidden on the blocks it hides) and
10
+ // the chevron is a CSS ::before pseudo-element &mdash; no DOM is injected into the
11
+ // content. getHTMLCode / getJSON are wrapped to strip those classes around the
12
+ // call, so saved markup is always clean and the folded content is never lost.
13
+ RTE_DefaultConfig.plugin_foldheadings = RTE_Plugin_FoldHeadings;
14
+ RTE_DefaultConfig.foldHeadingsEnabled = (typeof RTE_DefaultConfig.foldHeadingsEnabled === "undefined")
15
+ ? true : RTE_DefaultConfig.foldHeadingsEnabled;
16
+
17
+ function RTE_Plugin_FoldHeadings() {
18
+ var obj = this;
19
+ var config, editor;
20
+ var wrapped = false, clickDoc = null;
21
+
22
+ obj.PluginName = "FoldHeadings";
23
+
24
+ obj.InitConfig = function (argconfig) { config = argconfig; };
25
+
26
+ obj.InitEditor = function (argeditor) {
27
+ editor = argeditor;
28
+ setup();
29
+ try { editor.attachEvent("ready", setup); } catch (e) { /* ignore */ }
30
+ try { editor.attachEvent("aftersethtml", setup); } catch (e) { /* ignore */ }
31
+ setTimeout(setup, 0); setTimeout(setup, 200);
32
+ // Public API.
33
+ editor.toggleHeadingFold = function (h) { if (isHeading(h)) toggleFold(h); };
34
+ editor.unfoldAll = function () { var ed = editor.getEditable(); if (!ed) return; rm(ed.querySelectorAll(".rte-fold-collapsed,.rte-fold-hidden")); };
35
+ editor.foldAtCaret = function () { var h = headingAtCaret(); if (h) toggleFold(h); return !!h; };
36
+ };
37
+
38
+ // Re-runnable setup (the editor can recreate the document/body after init).
39
+ function setup() {
40
+ var doc = editor.getDocument();
41
+ if (doc) {
42
+ injectStyles(doc);
43
+ if (doc !== clickDoc) { doc.addEventListener("click", onClick, true); clickDoc = doc; }
44
+ }
45
+ wrapSerializers();
46
+ }
47
+
48
+ function isHeading(n) { return n && n.nodeType === 1 && /^H[1-6]$/.test(n.nodeName); }
49
+ function level(n) { return isHeading(n) ? +n.nodeName.charAt(1) : 0; }
50
+ function rm(list) { for (var i = 0; i < list.length; i++) { list[i].classList.remove("rte-fold-collapsed"); list[i].classList.remove("rte-fold-hidden"); } }
51
+
52
+ // Blocks from after `heading` up to the next heading of <= its level.
53
+ function blocksUnder(heading) {
54
+ var lv = level(heading), out = [], n = heading.nextElementSibling;
55
+ while (n) {
56
+ if (isHeading(n) && level(n) <= lv) break;
57
+ out.push(n);
58
+ n = n.nextElementSibling;
59
+ }
60
+ return out;
61
+ }
62
+
63
+ // The heading of the section the caret is in: the caret's own heading, else
64
+ // the nearest preceding top-level heading sibling.
65
+ function headingAtCaret() {
66
+ try {
67
+ var s = editor.getSelection(); if (!s || s.rangeCount === 0) return null;
68
+ var editable = editor.getEditable();
69
+ var n = s.getRangeAt(0).startContainer;
70
+ while (n && n !== editable) { if (isHeading(n)) return n; n = n.parentNode; }
71
+ // walk up to the top-level block, then back to the nearest heading
72
+ var block = s.getRangeAt(0).startContainer;
73
+ while (block && block.parentNode !== editable && block !== editable) block = block.parentNode;
74
+ while (block && block !== editable) { if (isHeading(block)) return block; block = block.previousElementSibling; }
75
+ } catch (e) {}
76
+ return null;
77
+ }
78
+
79
+ function toggleFold(heading) {
80
+ if (heading.classList.contains("rte-fold-collapsed")) unfold(heading);
81
+ else fold(heading);
82
+ }
83
+ function fold(heading) {
84
+ var blocks = blocksUnder(heading);
85
+ if (!blocks.length) return;
86
+ // Caret safety: if the caret sits in a block we're about to hide, move it
87
+ // to the end of the heading so it never ends up stuck in hidden content.
88
+ try {
89
+ var s = editor.getSelection();
90
+ if (s && s.rangeCount) {
91
+ var c = s.getRangeAt(0).startContainer, host = (c.nodeType === 1 ? c : c.parentNode);
92
+ for (var k = 0; k < blocks.length; k++) {
93
+ if (blocks[k] === host || blocks[k].contains(host)) {
94
+ var doc = editor.getDocument(), r = doc.createRange();
95
+ r.selectNodeContents(heading); r.collapse(false);
96
+ s.removeAllRanges(); s.addRange(r);
97
+ break;
98
+ }
99
+ }
100
+ }
101
+ } catch (e) {}
102
+ heading.classList.add("rte-fold-collapsed");
103
+ for (var i = 0; i < blocks.length; i++) blocks[i].classList.add("rte-fold-hidden");
104
+ fireChange();
105
+ }
106
+ function unfold(heading) {
107
+ heading.classList.remove("rte-fold-collapsed");
108
+ var blocks = blocksUnder(heading);
109
+ for (var i = 0; i < blocks.length; i++) blocks[i].classList.remove("rte-fold-hidden");
110
+ fireChange();
111
+ }
112
+
113
+ function onClick(e) {
114
+ if (!config.foldHeadingsEnabled) return;
115
+ var t = e.target;
116
+ if (!isHeading(t)) return;
117
+ // Only the left gutter chevron (rendered at left:-0.95em, just OUTSIDE
118
+ // the heading box, so its click offset is negative) toggles. Clicking
119
+ // anywhere in the heading text (offset >= 0) places the caret normally.
120
+ var rect = t.getBoundingClientRect();
121
+ var dx = e.clientX - rect.left;
122
+ if (dx > 2 || dx < -40) return;
123
+ e.preventDefault();
124
+ toggleFold(t);
125
+ }
126
+
127
+ function fireChange() { try { if (typeof editor.fireChange === "function") editor.fireChange(); } catch (e) {} }
128
+
129
+ function injectStyles(doc) {
130
+ if (!doc || doc.getElementById("rte-foldheadings-styles")) return;
131
+ var css =
132
+ ".rte-fold-hidden{display:none !important;}" +
133
+ "h1,h2,h3,h4,h5,h6{position:relative;}" +
134
+ "h1::before,h2::before,h3::before,h4::before,h5::before,h6::before{" +
135
+ "content:'\\25BE';position:absolute;left:-0.95em;top:0;width:0.8em;text-align:center;" +
136
+ "color:#cbd5e1;cursor:pointer;opacity:0;transition:opacity .12s,transform .12s;font-size:.9em;}" +
137
+ "h1:hover::before,h2:hover::before,h3:hover::before,h4:hover::before,h5:hover::before,h6:hover::before{opacity:1;}" +
138
+ "h1.rte-fold-collapsed::before,h2.rte-fold-collapsed::before,h3.rte-fold-collapsed::before," +
139
+ "h4.rte-fold-collapsed::before,h5.rte-fold-collapsed::before,h6.rte-fold-collapsed::before{" +
140
+ "content:'\\25B8';opacity:1;color:#1d67ba;}" +
141
+ "@media (prefers-reduced-motion: reduce){h1::before,h2::before,h3::before,h4::before,h5::before,h6::before{transition:none !important;}}" +
142
+ // Touch / no-hover devices can't reveal a hover-only chevron — keep it
143
+ // visible so headings remain foldable by tap.
144
+ "@media (hover: none),(pointer: coarse){h1::before,h2::before,h3::before,h4::before,h5::before,h6::before{opacity:.5;}}";
145
+ var st = doc.createElement("style");
146
+ st.id = "rte-foldheadings-styles";
147
+ st.appendChild(doc.createTextNode(css));
148
+ (doc.head || doc.getElementsByTagName("head")[0] || doc.documentElement).appendChild(st);
149
+ }
150
+
151
+ // Strip fold classes for the duration of a serialization call so saved markup
152
+ // is clean and folded content is never lost.
153
+ function stripFor() {
154
+ var ed = editor.getEditable(); if (!ed) return function () {};
155
+ var collapsed = ed.querySelectorAll(".rte-fold-collapsed");
156
+ var hidden = ed.querySelectorAll(".rte-fold-hidden");
157
+ var i;
158
+ for (i = 0; i < collapsed.length; i++) collapsed[i].classList.remove("rte-fold-collapsed");
159
+ for (i = 0; i < hidden.length; i++) hidden[i].classList.remove("rte-fold-hidden");
160
+ return function restore() {
161
+ for (var j = 0; j < collapsed.length; j++) collapsed[j].classList.add("rte-fold-collapsed");
162
+ for (var k = 0; k < hidden.length; k++) hidden[k].classList.add("rte-fold-hidden");
163
+ };
164
+ }
165
+ function wrapSerializers() {
166
+ if (wrapped) return;
167
+ var names = ["getHTMLCode", "getJSON", "getHTMLContent"];
168
+ var did = false;
169
+ for (var i = 0; i < names.length; i++) {
170
+ (function (name) {
171
+ var orig = editor[name];
172
+ if (typeof orig !== "function" || orig.__rteFoldWrapped) return;
173
+ var w = function () { var restore = stripFor(); try { return orig.apply(editor, arguments); } finally { restore(); } };
174
+ w.__rteFoldWrapped = true;
175
+ editor[name] = w;
176
+ did = true;
177
+ })(names[i]);
178
+ }
179
+ if (did) wrapped = true;
180
+ }
181
+ }