@remyxjs/core 1.0.0-beta → 1.0.4

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 (113) hide show
  1. package/README.md +902 -578
  2. package/dist/convertCsv-DRxJY6iq.js +2 -0
  3. package/dist/{convertCsv-CKzZjzLJ.js.map → convertCsv-DRxJY6iq.js.map} +1 -1
  4. package/dist/{convertCsv-B8RVtdcs.cjs → convertCsv-_-qbSNir.cjs} +2 -2
  5. package/dist/{convertCsv-B8RVtdcs.cjs.map → convertCsv-_-qbSNir.cjs.map} +1 -1
  6. package/dist/convertDocx-D-V0dfFd.js +2 -0
  7. package/dist/{convertDocx-Dmx88twM.js.map → convertDocx-D-V0dfFd.js.map} +1 -1
  8. package/dist/{convertDocx-4q89XLLv.cjs → convertDocx-Dpd5BG-G.cjs} +2 -2
  9. package/dist/{convertDocx-4q89XLLv.cjs.map → convertDocx-Dpd5BG-G.cjs.map} +1 -1
  10. package/dist/{convertHtml-DbHrdrD3.cjs → convertHtml-DmfodLsz.cjs} +2 -2
  11. package/dist/{convertHtml-DbHrdrD3.cjs.map → convertHtml-DmfodLsz.cjs.map} +1 -1
  12. package/dist/convertHtml-gycStuZn.js +2 -0
  13. package/dist/{convertHtml-CtYVhiTh.js.map → convertHtml-gycStuZn.js.map} +1 -1
  14. package/dist/convertMarkdown-1BmsjWXP.js +2 -0
  15. package/dist/{convertMarkdown-Di239Gtn.js.map → convertMarkdown-1BmsjWXP.js.map} +1 -1
  16. package/dist/{convertMarkdown-eJ9Nkoid.cjs → convertMarkdown-CelFJJT9.cjs} +2 -2
  17. package/dist/{convertMarkdown-eJ9Nkoid.cjs.map → convertMarkdown-CelFJJT9.cjs.map} +1 -1
  18. package/dist/convertPdf-D4SNUyBk.js +2 -0
  19. package/dist/{convertPdf-CFA1eNNH.js.map → convertPdf-D4SNUyBk.js.map} +1 -1
  20. package/dist/{convertPdf-CSLmTrB8.cjs → convertPdf-DSZy--TD.cjs} +2 -2
  21. package/dist/{convertPdf-CSLmTrB8.cjs.map → convertPdf-DSZy--TD.cjs.map} +1 -1
  22. package/dist/{convertRtf-BfiBLMig.cjs → convertRtf-DK-4c2fe.cjs} +2 -2
  23. package/dist/{convertRtf-BfiBLMig.cjs.map → convertRtf-DK-4c2fe.cjs.map} +1 -1
  24. package/dist/convertRtf-JWu514-h.js +2 -0
  25. package/dist/{convertRtf-08CoScGD.js.map → convertRtf-JWu514-h.js.map} +1 -1
  26. package/dist/{convertText-BpgzHRuh.cjs → convertText-BHUD1tLm.cjs} +2 -2
  27. package/dist/{convertText-BpgzHRuh.cjs.map → convertText-BHUD1tLm.cjs.map} +1 -1
  28. package/dist/convertText-DCGItleK.js +2 -0
  29. package/dist/{convertText-sa7PxKTe.js.map → convertText-DCGItleK.js.map} +1 -1
  30. package/dist/index-C3_cH6Zy.cjs +2 -0
  31. package/dist/index-C3_cH6Zy.cjs.map +1 -0
  32. package/dist/index-Cv62E14a.js +2 -0
  33. package/dist/index-Cv62E14a.js.map +1 -0
  34. package/dist/remyx-core.cjs +1 -1
  35. package/dist/remyx-core.css +1 -1
  36. package/dist/remyx-core.js +1 -1
  37. package/package.json +1 -1
  38. package/dist/convertCsv-CKzZjzLJ.js +0 -2
  39. package/dist/convertDocx-Dmx88twM.js +0 -2
  40. package/dist/convertHtml-CtYVhiTh.js +0 -2
  41. package/dist/convertMarkdown-Di239Gtn.js +0 -2
  42. package/dist/convertPdf-CFA1eNNH.js +0 -2
  43. package/dist/convertRtf-08CoScGD.js +0 -2
  44. package/dist/convertText-sa7PxKTe.js +0 -2
  45. package/dist/index-4syk9eEO.js +0 -2
  46. package/dist/index-4syk9eEO.js.map +0 -1
  47. package/dist/index-B25zSs0W.js +0 -2
  48. package/dist/index-B25zSs0W.js.map +0 -1
  49. package/dist/index-B7VT6ZLa.cjs +0 -2
  50. package/dist/index-B7VT6ZLa.cjs.map +0 -1
  51. package/dist/index-BCpytFKJ.js +0 -2
  52. package/dist/index-BCpytFKJ.js.map +0 -1
  53. package/dist/index-BNKANY5i.cjs +0 -2
  54. package/dist/index-BNKANY5i.cjs.map +0 -1
  55. package/dist/index-B_g_579T.cjs +0 -2
  56. package/dist/index-B_g_579T.cjs.map +0 -1
  57. package/dist/index-BvwyeoMb.js +0 -3
  58. package/dist/index-BvwyeoMb.js.map +0 -1
  59. package/dist/index-Bw7mlUQo.js +0 -2
  60. package/dist/index-Bw7mlUQo.js.map +0 -1
  61. package/dist/index-Byatzd-A.js +0 -2
  62. package/dist/index-Byatzd-A.js.map +0 -1
  63. package/dist/index-C0z9eZLm.cjs +0 -2
  64. package/dist/index-C0z9eZLm.cjs.map +0 -1
  65. package/dist/index-C88XPqjX.js +0 -2
  66. package/dist/index-C88XPqjX.js.map +0 -1
  67. package/dist/index-CI6FPF49.cjs +0 -2
  68. package/dist/index-CI6FPF49.cjs.map +0 -1
  69. package/dist/index-CLZF5_GB.cjs +0 -2
  70. package/dist/index-CLZF5_GB.cjs.map +0 -1
  71. package/dist/index-CXSwYlG4.cjs +0 -2
  72. package/dist/index-CXSwYlG4.cjs.map +0 -1
  73. package/dist/index-Ch9gotLk.js +0 -2
  74. package/dist/index-Ch9gotLk.js.map +0 -1
  75. package/dist/index-CifDpN1Y.js +0 -2
  76. package/dist/index-CifDpN1Y.js.map +0 -1
  77. package/dist/index-D5o8VpWJ.cjs +0 -2
  78. package/dist/index-D5o8VpWJ.cjs.map +0 -1
  79. package/dist/index-DKT1bABL.js +0 -2
  80. package/dist/index-DKT1bABL.js.map +0 -1
  81. package/dist/index-DWcn72PW.js +0 -2
  82. package/dist/index-DWcn72PW.js.map +0 -1
  83. package/dist/index-DjCGzPEv.cjs +0 -2
  84. package/dist/index-DjCGzPEv.cjs.map +0 -1
  85. package/dist/index-Dq0Jr1Ae.js +0 -2
  86. package/dist/index-Dq0Jr1Ae.js.map +0 -1
  87. package/dist/index-Dw0MVypb.cjs +0 -2
  88. package/dist/index-Dw0MVypb.cjs.map +0 -1
  89. package/dist/index-FEo3LShh.cjs +0 -2
  90. package/dist/index-FEo3LShh.cjs.map +0 -1
  91. package/dist/index-O1hzAUzi.cjs +0 -2
  92. package/dist/index-O1hzAUzi.cjs.map +0 -1
  93. package/dist/index-T1ZyLzeF.cjs +0 -2
  94. package/dist/index-T1ZyLzeF.cjs.map +0 -1
  95. package/dist/index-iRikoCdK.cjs +0 -2
  96. package/dist/index-iRikoCdK.cjs.map +0 -1
  97. package/dist/index-l6Yddj6x.js +0 -2
  98. package/dist/index-l6Yddj6x.js.map +0 -1
  99. package/dist/index-rD8LZENp.js +0 -2
  100. package/dist/index-rD8LZENp.js.map +0 -1
  101. package/dist/themes/callouts.css +0 -79
  102. package/dist/themes/collaboration.css +0 -117
  103. package/dist/themes/comments.css +0 -198
  104. package/dist/themes/dark.css +0 -109
  105. package/dist/themes/forest.css +0 -109
  106. package/dist/themes/light.css +0 -4
  107. package/dist/themes/links.css +0 -115
  108. package/dist/themes/math-toc-analytics.css +0 -129
  109. package/dist/themes/ocean.css +0 -109
  110. package/dist/themes/rose.css +0 -109
  111. package/dist/themes/spellcheck.css +0 -173
  112. package/dist/themes/sunset.css +0 -109
  113. package/dist/themes/templates.css +0 -87
package/README.md CHANGED
@@ -4,79 +4,99 @@
4
4
 
5
5
  Framework-agnostic core engine for the Remyx Editor. Provides the editor engine, commands, plugin system, utilities, and CSS themes — with zero framework dependencies.
6
6
 
7
- Use this package to build Remyx Editor integrations for any framework (Vue, Svelte, Angular, vanilla JS) or for server-side processing. For React projects, use [`@remyxjs/react`](../remyx-react/), which includes this package plus React components and hooks.
7
+ Use this package to build Remyx Editor integrations for any framework (Vue, Svelte, Angular, vanilla JS) or for server-side processing. For React projects, use [`@remyxjs/react`](https://www.npmjs.com/package/@remyxjs/react), which includes this package plus React components and hooks.
8
8
 
9
9
  ## Table of Contents
10
10
 
11
- - [Installation](#installation)
12
- - [Quick Start](#quick-start)
13
- - [Architecture](#architecture)
14
- - [EditorEngine](#editorengine)
15
- - [Constructor Options](#constructor-options)
16
- - [Methods](#methods)
17
- - [Events](#events)
18
- - [Commands](#commands)
19
- - [Formatting](#formatting)
20
- - [Headings](#headings)
21
- - [Lists](#lists)
22
- - [Alignment](#alignment)
23
- - [Links](#links)
24
- - [Images](#images)
25
- - [Tables](#tables)
26
- - [Blocks](#blocks)
27
- - [Fonts](#fonts)
28
- - [Media Embeds](#media-embeds)
29
- - [Find & Replace](#find--replace)
30
- - [Source Mode](#source-mode)
31
- - [Fullscreen](#fullscreen)
32
- - [Distraction-Free Mode](#distraction-free-mode)
33
- - [Split View](#split-view)
34
- - [Color Presets](#color-presets)
35
- - [Typography Controls](#typography-controls)
36
- - [Sticky Toolbar](#sticky-toolbar)
37
- - [Markdown Toggle](#markdown-toggle)
38
- - [Attachments](#attachments)
39
- - [Document Import](#document-import)
40
- - [Plugin System](#plugin-system)
41
- - [Creating Plugins](#creating-plugins)
42
- - [Plugin API (Restricted)](#plugin-api-restricted)
43
- - [Built-in Plugins](#built-in-plugins)
44
- - [Syntax Highlighting](#syntax-highlighting)
45
- - [Autosave](#autosave)
46
- - [Storage Providers](#storage-providers)
47
- - [AutosaveManager API](#autosavemanager-api)
48
- - [Autosave Events](#autosave-events)
49
- - [Selection API](#selection-api)
50
- - [History (Undo/Redo)](#history-undoredo)
51
- - [Keyboard Shortcuts](#keyboard-shortcuts)
52
- - [Sanitizer](#sanitizer)
53
- - [Utilities](#utilities)
54
- - [Markdown Conversion](#markdown-conversion)
55
- - [Document Conversion](#document-conversion)
56
- - [Export](#export)
57
- - [Paste Cleaning](#paste-cleaning)
58
- - [Font Management](#font-management)
59
- - [DOM Utilities](#dom-utilities)
60
- - [HTML Formatting](#html-formatting)
61
- - [Platform Detection](#platform-detection)
62
- - [Theming](#theming)
63
- - [Theme Variables](#theme-variables)
64
- - [Theme Presets](#theme-presets)
65
- - [Custom Themes](#custom-themes)
66
- - [Toolbar Configuration](#toolbar-configuration)
67
- - [Toolbar Presets](#toolbar-presets)
68
- - [Custom Toolbars](#custom-toolbars)
69
- - [Toolbar Item Theming](#toolbar-item-theming)
70
- - [Configuration](#configuration)
71
- - [defineConfig](#defineconfig)
72
- - [Multi-Editor Support](#multi-editor-support)
73
- - [EditorBus](#editorbus)
74
- - [SharedResources](#sharedresources)
75
- - [Constants](#constants)
76
- - [Tree-Shaking](#tree-shaking)
77
- - [CSS](#css)
78
- - [Building Framework Wrappers](#building-framework-wrappers)
79
- - [License](#license)
11
+ - [@remyxjs/core](#remyxjscore)
12
+ - [Table of Contents](#table-of-contents)
13
+ - [Installation](#installation)
14
+ - [Quick Start](#quick-start)
15
+ - [Architecture](#architecture)
16
+ - [EditorEngine](#editorengine)
17
+ - [Constructor Options](#constructor-options)
18
+ - [Methods](#methods)
19
+ - [Events](#events)
20
+ - [Commands](#commands)
21
+ - [Formatting](#formatting)
22
+ - [Headings](#headings)
23
+ - [Lists](#lists)
24
+ - [Alignment](#alignment)
25
+ - [Links](#links)
26
+ - [Images](#images)
27
+ - [Tables](#tables)
28
+ - [Table command reference](#table-command-reference)
29
+ - [Sort data types](#sort-data-types)
30
+ - [Formulas](#formulas)
31
+ - [Cell formatting](#cell-formatting)
32
+ - [Clipboard interop](#clipboard-interop)
33
+ - [Blocks](#blocks)
34
+ - [Fonts](#fonts)
35
+ - [Media Embeds](#media-embeds)
36
+ - [Find \& Replace](#find--replace)
37
+ - [Source Mode](#source-mode)
38
+ - [Fullscreen](#fullscreen)
39
+ - [Distraction-Free Mode](#distraction-free-mode)
40
+ - [Split View](#split-view)
41
+ - [Color Presets](#color-presets)
42
+ - [Typography Controls](#typography-controls)
43
+ - [Sticky Toolbar](#sticky-toolbar)
44
+ - [Markdown Toggle](#markdown-toggle)
45
+ - [Attachments](#attachments)
46
+ - [Document Import](#document-import)
47
+ - [Command Palette](#command-palette)
48
+ - [Recently-used commands](#recently-used-commands)
49
+ - [Custom command items](#custom-command-items)
50
+ - [Autosave](#autosave)
51
+ - [Storage Providers](#storage-providers)
52
+ - [AutosaveManager API](#autosavemanager-api)
53
+ - [Autosave Events](#autosave-events)
54
+ - [Plugin System](#plugin-system)
55
+ - [Creating Plugins](#creating-plugins)
56
+ - [Lifecycle Hooks](#lifecycle-hooks)
57
+ - [Plugin Dependencies](#plugin-dependencies)
58
+ - [Scoped Plugin Settings](#scoped-plugin-settings)
59
+ - [Plugin Registry](#plugin-registry)
60
+ - [Plugin Metadata](#plugin-metadata)
61
+ - [Plugin API (Restricted)](#plugin-api-restricted)
62
+ - [Built-in Plugins](#built-in-plugins)
63
+ - [Line numbers](#line-numbers)
64
+ - [Copy-to-clipboard](#copy-to-clipboard)
65
+ - [Inline code highlighting](#inline-code-highlighting)
66
+ - [Custom language registration](#custom-language-registration)
67
+ - [Selection API](#selection-api)
68
+ - [History (Undo/Redo)](#history-undoredo)
69
+ - [Keyboard Shortcuts](#keyboard-shortcuts)
70
+ - [Sanitizer](#sanitizer)
71
+ - [Utilities](#utilities)
72
+ - [HTML Helpers](#html-helpers)
73
+ - [Markdown Conversion](#markdown-conversion)
74
+ - [Document Conversion](#document-conversion)
75
+ - [Export](#export)
76
+ - [Paste Cleaning](#paste-cleaning)
77
+ - [Font Management](#font-management)
78
+ - [DOM Utilities](#dom-utilities)
79
+ - [HTML Formatting](#html-formatting)
80
+ - [Platform Detection](#platform-detection)
81
+ - [Theming](#theming)
82
+ - [Theme Variables](#theme-variables)
83
+ - [Built-in Themes](#built-in-themes)
84
+ - [Custom Themes](#custom-themes)
85
+ - [Toolbar Configuration](#toolbar-configuration)
86
+ - [Toolbar Presets](#toolbar-presets)
87
+ - [Custom Toolbars](#custom-toolbars)
88
+ - [Toolbar Item Theming](#toolbar-item-theming)
89
+ - [Configuration](#configuration)
90
+ - [defineConfig](#defineconfig)
91
+ - [loadConfig](#loadconfig)
92
+ - [Multi-Editor Support](#multi-editor-support)
93
+ - [EditorBus](#editorbus)
94
+ - [SharedResources](#sharedresources)
95
+ - [Constants](#constants)
96
+ - [Tree-Shaking](#tree-shaking)
97
+ - [CSS](#css)
98
+ - [Building Framework Wrappers](#building-framework-wrappers)
99
+ - [License](#license)
80
100
 
81
101
  ## Installation
82
102
 
@@ -176,51 +196,51 @@ const engine = new EditorEngine(element, {
176
196
 
177
197
  ### Methods
178
198
 
179
- | Method | Returns | Description |
180
- | --- | --- | --- |
181
- | `init()` | `void` | Initialize the editor — binds event listeners, starts subsystems |
182
- | `destroy()` | `void` | Clean up all listeners, disconnect observers, destroy plugins |
183
- | `getHTML()` | `string` | Get sanitized HTML content |
184
- | `setHTML(html)` | `void` | Set content (sanitized before insertion) |
185
- | `getText()` | `string` | Get plain text content |
186
- | `isEmpty()` | `boolean` | `true` when editor has no meaningful content |
187
- | `focus()` | `void` | Focus the editor element |
188
- | `blur()` | `void` | Blur the editor element |
189
- | `executeCommand(name, ...args)` | `any` | Execute a registered command by name |
190
- | `on(event, handler)` | `Function` | Subscribe to an event; returns an unsubscribe function |
191
- | `off(event, handler)` | `void` | Unsubscribe from an event |
192
- | `getWordCount()` | `number` | Current word count |
193
- | `getCharCount()` | `number` | Current character count |
199
+ | Method | Returns | Description |
200
+ | ------------------------------- | ---------- | ---------------------------------------------------------------- |
201
+ | `init()` | `void` | Initialize the editor — binds event listeners, starts subsystems |
202
+ | `destroy()` | `void` | Clean up all listeners, disconnect observers, destroy plugins |
203
+ | `getHTML()` | `string` | Get sanitized HTML content |
204
+ | `setHTML(html)` | `void` | Set content (sanitized before insertion) |
205
+ | `getText()` | `string` | Get plain text content |
206
+ | `isEmpty()` | `boolean` | `true` when editor has no meaningful content |
207
+ | `focus()` | `void` | Focus the editor element |
208
+ | `blur()` | `void` | Blur the editor element |
209
+ | `executeCommand(name, ...args)` | `any` | Execute a registered command by name |
210
+ | `on(event, handler)` | `Function` | Subscribe to an event; returns an unsubscribe function |
211
+ | `off(event, handler)` | `void` | Unsubscribe from an event |
212
+ | `getWordCount()` | `number` | Current word count |
213
+ | `getCharCount()` | `number` | Current character count |
194
214
 
195
215
  ### Events
196
216
 
197
- | Event | Data | Description |
198
- | --- | --- | --- |
199
- | `content:change` | — | Content was modified |
200
- | `selection:change` | `ActiveFormats` | Selection or formatting state changed |
201
- | `focus` | — | Editor received focus |
202
- | `blur` | — | Editor lost focus |
203
- | `command:executed` | `{ name, args, result }` | A command was executed |
204
- | `paste` | `{ html, text }` | Paste occurred |
205
- | `drop` | `{ files, html }` | Drop occurred |
206
- | `upload:error` | `{ file, error }` | Upload handler rejected |
207
- | `file:too-large` | `{ file, maxSize }` | Dropped/pasted file exceeded size limit |
208
- | `editor:error` | `{ phase, error }` | Initialization error |
209
- | `mode:change` | `{ sourceMode }` | Source mode toggled |
210
- | `mode:change:markdown` | `{ markdownMode }` | Markdown mode toggled |
211
- | `fullscreen:toggle` | `{ fullscreen }` | Fullscreen toggled |
212
- | `find:results` | `{ total, current }` | Find/replace results updated |
213
- | `history:undo` | — | Undo performed |
214
- | `history:redo` | — | Redo performed |
215
- | `plugin:registered` | `{ name }` | Plugin was registered |
216
- | `plugin:error` | `{ name, error }` | Plugin init/destroy error |
217
- | `codeblock:created` | `{ element, language }` | Code block was created |
218
- | `codeblock:language-change` | `{ language, element }` | Code block language was changed |
219
- | `wordcount:update` | `{ wordCount, charCount }` | Word/char count changed |
220
- | `autosave:saving` | — | Autosave started |
221
- | `autosave:saved` | `{ timestamp }` | Autosave succeeded |
222
- | `autosave:error` | `{ error }` | Autosave failed |
223
- | `autosave:recovered` | `{ recoveredContent, timestamp }` | Recovery data found on init |
217
+ | Event | Data | Description |
218
+ | --------------------------- | --------------------------------- | --------------------------------------- |
219
+ | `content:change` | — | Content was modified |
220
+ | `selection:change` | `ActiveFormats` | Selection or formatting state changed |
221
+ | `focus` | — | Editor received focus |
222
+ | `blur` | — | Editor lost focus |
223
+ | `command:executed` | `{ name, args, result }` | A command was executed |
224
+ | `paste` | `{ html, text }` | Paste occurred |
225
+ | `drop` | `{ files, html }` | Drop occurred |
226
+ | `upload:error` | `{ file, error }` | Upload handler rejected |
227
+ | `file:too-large` | `{ file, maxSize }` | Dropped/pasted file exceeded size limit |
228
+ | `editor:error` | `{ phase, error }` | Initialization error |
229
+ | `mode:change` | `{ sourceMode }` | Source mode toggled |
230
+ | `mode:change:markdown` | `{ markdownMode }` | Markdown mode toggled |
231
+ | `fullscreen:toggle` | `{ fullscreen }` | Fullscreen toggled |
232
+ | `find:results` | `{ total, current }` | Find/replace results updated |
233
+ | `history:undo` | — | Undo performed |
234
+ | `history:redo` | — | Redo performed |
235
+ | `plugin:registered` | `{ name }` | Plugin was registered |
236
+ | `plugin:error` | `{ name, error }` | Plugin init/destroy error |
237
+ | `codeblock:created` | `{ element, language }` | Code block was created |
238
+ | `codeblock:language-change` | `{ language, element }` | Code block language was changed |
239
+ | `wordcount:update` | `{ wordCount, charCount }` | Word/char count changed |
240
+ | `autosave:saving` | — | Autosave started |
241
+ | `autosave:saved` | `{ timestamp }` | Autosave succeeded |
242
+ | `autosave:error` | `{ error }` | Autosave failed |
243
+ | `autosave:recovered` | `{ recoveredContent, timestamp }` | Recovery data found on init |
224
244
 
225
245
  **Example — listening to events:**
226
246
 
@@ -247,40 +267,40 @@ unsub(); // stop listening
247
267
 
248
268
  Each register function adds commands to the engine. Call only the ones you need — unused commands are tree-shaken from the bundle.
249
269
 
250
- | Function | Commands Added |
251
- | --- | --- |
252
- | `registerFormattingCommands` | bold, italic, underline, strikethrough, subscript, superscript, removeFormat |
253
- | `registerHeadingCommands` | heading, h1–h6, paragraph |
254
- | `registerAlignmentCommands` | alignLeft, alignCenter, alignRight, alignJustify |
255
- | `registerListCommands` | orderedList, unorderedList, taskList, indent, outdent |
256
- | `registerLinkCommands` | insertLink, editLink, removeLink |
257
- | `registerImageCommands` | insertImage, resizeImage, alignImage, removeImage |
258
- | `registerTableCommands` | insertTable, addRowBefore, addRowAfter, addColBefore, addColAfter, deleteRow, deleteCol, deleteTable, mergeCells, splitCell, toggleHeaderRow, sortTable, filterTable, clearTableFilters, formatCell, evaluateFormulas |
259
- | `registerBlockCommands` | blockquote, codeBlock, horizontalRule |
260
- | `registerFontCommands` | fontFamily, fontSize, foreColor, backColor, lineHeight, letterSpacing, paragraphSpacing |
261
- | `registerMediaCommands` | embedMedia, removeEmbed |
262
- | `registerFindReplaceCommands` | find, findNext, findPrev, replace, replaceAll |
263
- | `registerSourceModeCommands` | sourceMode |
264
- | `registerFullscreenCommands` | fullscreen |
265
- | `registerDistractionFreeCommands` | distractionFree |
266
- | `registerSplitViewCommands` | toggleSplitView |
267
- | `registerColorPresetCommands` | saveColorPreset, loadColorPresets, deleteColorPreset |
268
- | `registerMarkdownToggleCommands` | toggleMarkdown |
269
- | `registerAttachmentCommands` | insertAttachment, removeAttachment |
270
- | `registerImportDocumentCommands` | importDocument |
270
+ | Function | Commands Added |
271
+ | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
272
+ | `registerFormattingCommands` | bold, italic, underline, strikethrough, subscript, superscript, removeFormat |
273
+ | `registerHeadingCommands` | heading, h1–h6, paragraph |
274
+ | `registerAlignmentCommands` | alignLeft, alignCenter, alignRight, alignJustify |
275
+ | `registerListCommands` | orderedList, unorderedList, taskList, indent, outdent |
276
+ | `registerLinkCommands` | insertLink, editLink, removeLink |
277
+ | `registerImageCommands` | insertImage, resizeImage, alignImage, removeImage |
278
+ | `registerTableCommands` | insertTable, addRowBefore, addRowAfter, addColBefore, addColAfter, deleteRow, deleteCol, deleteTable, mergeCells, splitCell, toggleHeaderRow, sortTable, filterTable, clearTableFilters, formatCell, evaluateFormulas |
279
+ | `registerBlockCommands` | blockquote, codeBlock, horizontalRule |
280
+ | `registerFontCommands` | fontFamily, fontSize, foreColor, backColor, lineHeight, letterSpacing, paragraphSpacing |
281
+ | `registerMediaCommands` | embedMedia, removeEmbed |
282
+ | `registerFindReplaceCommands` | find, findNext, findPrev, replace, replaceAll |
283
+ | `registerSourceModeCommands` | sourceMode |
284
+ | `registerFullscreenCommands` | fullscreen |
285
+ | `registerDistractionFreeCommands` | distractionFree |
286
+ | `registerSplitViewCommands` | toggleSplitView |
287
+ | `registerColorPresetCommands` | saveColorPreset, loadColorPresets, deleteColorPreset |
288
+ | `registerMarkdownToggleCommands` | toggleMarkdown |
289
+ | `registerAttachmentCommands` | insertAttachment, removeAttachment |
290
+ | `registerImportDocumentCommands` | importDocument |
271
291
 
272
292
  ### Formatting
273
293
 
274
294
  ```js
275
295
  registerFormattingCommands(engine);
276
296
 
277
- engine.executeCommand('bold'); // Toggle bold (Mod+B)
278
- engine.executeCommand('italic'); // Toggle italic (Mod+I)
279
- engine.executeCommand('underline'); // Toggle underline (Mod+U)
297
+ engine.executeCommand('bold'); // Toggle bold (Mod+B)
298
+ engine.executeCommand('italic'); // Toggle italic (Mod+I)
299
+ engine.executeCommand('underline'); // Toggle underline (Mod+U)
280
300
  engine.executeCommand('strikethrough'); // Toggle strikethrough (Mod+Shift+X)
281
- engine.executeCommand('subscript'); // Toggle subscript (Mod+,)
282
- engine.executeCommand('superscript'); // Toggle superscript (Mod+.)
283
- engine.executeCommand('removeFormat'); // Strip all inline formatting
301
+ engine.executeCommand('subscript'); // Toggle subscript (Mod+,)
302
+ engine.executeCommand('superscript'); // Toggle superscript (Mod+.)
303
+ engine.executeCommand('removeFormat'); // Strip all inline formatting
284
304
  ```
285
305
 
286
306
  ### Headings
@@ -288,11 +308,11 @@ engine.executeCommand('removeFormat'); // Strip all inline formatting
288
308
  ```js
289
309
  registerHeadingCommands(engine);
290
310
 
291
- engine.executeCommand('heading', 1); // Apply H1
292
- engine.executeCommand('heading', 3); // Apply H3
311
+ engine.executeCommand('heading', 1); // Apply H1
312
+ engine.executeCommand('heading', 3); // Apply H3
293
313
  engine.executeCommand('heading', 'p'); // Reset to paragraph
294
- engine.executeCommand('h2'); // Shorthand for heading level 2
295
- engine.executeCommand('paragraph'); // Shorthand for normal text
314
+ engine.executeCommand('h2'); // Shorthand for heading level 2
315
+ engine.executeCommand('paragraph'); // Shorthand for normal text
296
316
  ```
297
317
 
298
318
  If `baseHeadingLevel` is set in options, heading levels are offset. For example, with `baseHeadingLevel: 2`, `heading(1)` renders as `<h2>`.
@@ -302,11 +322,11 @@ If `baseHeadingLevel` is set in options, heading levels are offset. For example,
302
322
  ```js
303
323
  registerListCommands(engine);
304
324
 
305
- engine.executeCommand('orderedList'); // Toggle numbered list (Mod+Shift+7)
325
+ engine.executeCommand('orderedList'); // Toggle numbered list (Mod+Shift+7)
306
326
  engine.executeCommand('unorderedList'); // Toggle bullet list (Mod+Shift+8)
307
- engine.executeCommand('taskList'); // Toggle task list with checkboxes
308
- engine.executeCommand('indent'); // Increase indentation
309
- engine.executeCommand('outdent'); // Decrease indentation
327
+ engine.executeCommand('taskList'); // Toggle task list with checkboxes
328
+ engine.executeCommand('indent'); // Increase indentation
329
+ engine.executeCommand('outdent'); // Decrease indentation
310
330
  ```
311
331
 
312
332
  ### Alignment
@@ -328,15 +348,15 @@ registerLinkCommands(engine);
328
348
  // Insert a new link (Mod+K)
329
349
  engine.executeCommand('insertLink', {
330
350
  href: 'https://example.com',
331
- text: 'Example', // optional — uses selection if omitted
332
- target: '_blank', // optional
351
+ text: 'Example', // optional — uses selection if omitted
352
+ target: '_blank', // optional
333
353
  });
334
354
 
335
355
  // Edit an existing link
336
356
  engine.executeCommand('editLink', {
337
357
  href: 'https://new-url.com',
338
- text: 'New text', // optional
339
- target: '_self', // optional
358
+ text: 'New text', // optional
359
+ target: '_self', // optional
340
360
  });
341
361
 
342
362
  // Remove link, keep text
@@ -351,9 +371,9 @@ registerImageCommands(engine);
351
371
  // Insert image
352
372
  engine.executeCommand('insertImage', {
353
373
  src: 'https://example.com/photo.jpg',
354
- alt: 'A photo', // optional
355
- width: 400, // optional
356
- height: 300, // optional
374
+ alt: 'A photo', // optional
375
+ width: 400, // optional
376
+ height: 300, // optional
357
377
  });
358
378
 
359
379
  // Resize an existing image
@@ -395,8 +415,15 @@ engine.executeCommand('deleteCol');
395
415
  engine.executeCommand('toggleHeaderRow');
396
416
 
397
417
  // Sort by column (physically reorders rows, sets data-sort-dir on <th>)
398
- engine.executeCommand('sortTable', { columnIndex: 0, direction: 'asc' });
399
- engine.executeCommand('sortTable', { columnIndex: 0, direction: 'desc', dataType: 'numeric' });
418
+ engine.executeCommand('sortTable', {
419
+ columnIndex: 0,
420
+ direction: 'asc',
421
+ });
422
+ engine.executeCommand('sortTable', {
423
+ columnIndex: 0,
424
+ direction: 'desc',
425
+ dataType: 'numeric',
426
+ });
400
427
 
401
428
  // Multi-column sort
402
429
  engine.executeCommand('sortTable', {
@@ -407,14 +434,23 @@ engine.executeCommand('sortTable', {
407
434
  });
408
435
 
409
436
  // Filter rows (non-destructive, hides non-matching rows)
410
- engine.executeCommand('filterTable', { columnIndex: 0, filterValue: 'search term' });
437
+ engine.executeCommand('filterTable', {
438
+ columnIndex: 0,
439
+ filterValue: 'search term',
440
+ });
411
441
  engine.executeCommand('clearTableFilters');
412
442
 
413
443
  // Cell formatting (stores raw value in data-raw-value, displays formatted)
414
444
  engine.executeCommand('formatCell', { format: 'number' });
415
- engine.executeCommand('formatCell', { format: 'currency', options: { currency: 'EUR' } });
445
+ engine.executeCommand('formatCell', {
446
+ format: 'currency',
447
+ options: { currency: 'EUR' },
448
+ });
416
449
  engine.executeCommand('formatCell', { format: 'percentage' });
417
- engine.executeCommand('formatCell', { format: 'date', options: { dateStyle: 'long' } });
450
+ engine.executeCommand('formatCell', {
451
+ format: 'date',
452
+ options: { dateStyle: 'long' },
453
+ });
418
454
 
419
455
  // Formula evaluation (cells with data-formula attribute)
420
456
  engine.executeCommand('evaluateFormulas');
@@ -426,28 +462,29 @@ engine.executeCommand('splitCell');
426
462
 
427
463
  #### Table command reference
428
464
 
429
- | Command | Arguments | Description |
430
- | --- | --- | --- |
431
- | `insertTable` | `{ rows, cols }` | Insert a table with `<thead>` header row. Default 3x3. |
432
- | `addRowBefore` | — | Insert a row above the current cell |
433
- | `addRowAfter` | — | Insert a row below the current cell |
434
- | `addColBefore` | — | Insert a column to the left |
435
- | `addColAfter` | — | Insert a column to the right |
436
- | `deleteRow` | — | Delete the current row (removes table if last row) |
437
- | `deleteCol` | — | Delete the current column (removes table if last column) |
438
- | `deleteTable` | — | Delete the entire table |
439
- | `mergeCells` | `{ cells: [el, el, ...] }` | Merge an array of cell elements |
440
- | `splitCell` | — | Split a merged cell back into individual cells |
441
- | `toggleHeaderRow` | — | Convert first row to/from `<thead>` with `<th>` cells |
442
- | `sortTable` | `{ columnIndex, direction, dataType }` or `{ keys: [...] }` | Sort rows. Direction: `'asc'` or `'desc'`. DataType: `'alphabetical'`, `'numeric'`, or `'date'` (auto-detected if omitted). Use `keys` array for multi-column sort. |
443
- | `filterTable` | `{ columnIndex, filterValue }` | Hide rows where the cell at `columnIndex` doesn't contain `filterValue` (case-insensitive substring match). Pass empty string to clear a single column filter. |
444
- | `clearTableFilters` | — | Remove all column filters and show all rows |
445
- | `formatCell` | `{ format, options }` | Format the focused cell. Format: `'number'`, `'currency'`, `'percentage'`, `'date'`. Options: `{ decimals, currency, dateStyle }`. |
446
- | `evaluateFormulas` | — | Re-evaluate all formula cells in the focused table |
465
+ | Command | Arguments | Description |
466
+ | ------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
467
+ | `insertTable` | `{ rows, cols }` | Insert a table with `<thead>` header row. Default 3x3. |
468
+ | `addRowBefore` | — | Insert a row above the current cell |
469
+ | `addRowAfter` | — | Insert a row below the current cell |
470
+ | `addColBefore` | — | Insert a column to the left |
471
+ | `addColAfter` | — | Insert a column to the right |
472
+ | `deleteRow` | — | Delete the current row (removes table if last row) |
473
+ | `deleteCol` | — | Delete the current column (removes table if last column) |
474
+ | `deleteTable` | — | Delete the entire table |
475
+ | `mergeCells` | `{ cells: [el, el, ...] }` | Merge an array of cell elements |
476
+ | `splitCell` | — | Split a merged cell back into individual cells |
477
+ | `toggleHeaderRow` | — | Convert first row to/from `<thead>` with `<th>` cells |
478
+ | `sortTable` | `{ columnIndex, direction, dataType }` or `{ keys: [...] }` | Sort rows. Direction: `'asc'` or `'desc'`. DataType: `'alphabetical'`, `'numeric'`, or `'date'` (auto-detected if omitted). Use `keys` array for multi-column sort. |
479
+ | `filterTable` | `{ columnIndex, filterValue }` | Hide rows where the cell at `columnIndex` doesn't contain `filterValue` (case-insensitive substring match). Pass empty string to clear a single column filter. |
480
+ | `clearTableFilters` | — | Remove all column filters and show all rows |
481
+ | `formatCell` | `{ format, options }` | Format the focused cell. Format: `'number'`, `'currency'`, `'percentage'`, `'date'`. Options: `{ decimals, currency, dateStyle }`. |
482
+ | `evaluateFormulas` | — | Re-evaluate all formula cells in the focused table |
447
483
 
448
484
  #### Sort data types
449
485
 
450
486
  The sort command auto-detects the data type of a column by sampling its values:
487
+
451
488
  - **numeric** — if >70% of values parse as numbers
452
489
  - **date** — if >70% of values parse as valid dates
453
490
  - **alphabetical** — default, uses locale-aware `localeCompare`
@@ -455,7 +492,12 @@ The sort command auto-detects the data type of a column by sampling its values:
455
492
  You can override auto-detection by passing `dataType` explicitly, or provide a global custom comparator via `engine.options.tableSortComparator`:
456
493
 
457
494
  ```js
458
- engine.options.tableSortComparator = (a, b, dataType, columnIndex) => {
495
+ engine.options.tableSortComparator = (
496
+ a,
497
+ b,
498
+ dataType,
499
+ columnIndex,
500
+ ) => {
459
501
  // Custom comparison — return negative, zero, or positive
460
502
  return a.localeCompare(b, 'de'); // German locale sort
461
503
  };
@@ -467,15 +509,15 @@ Cells starting with `=` are treated as formulas when the `TablePlugin` is active
467
509
 
468
510
  **Supported functions:**
469
511
 
470
- | Function | Description | Example |
471
- | --- | --- | --- |
472
- | `SUM` | Sum all values in a range | `=SUM(A1:A10)` |
473
- | `AVERAGE` | Arithmetic mean of values | `=AVERAGE(B2:B8)` |
474
- | `COUNT` | Count non-empty cells | `=COUNT(A1:A20)` |
475
- | `MIN` | Smallest value in a range | `=MIN(C1:C5)` |
476
- | `MAX` | Largest value in a range | `=MAX(C1:C5)` |
477
- | `IF` | Conditional value | `=IF(A1>10, "high", "low")` |
478
- | `CONCAT` | Join values into a string | `=CONCAT(A1, " ", B1)` |
512
+ | Function | Description | Example |
513
+ | --------- | ------------------------- | --------------------------- |
514
+ | `SUM` | Sum all values in a range | `=SUM(A1:A10)` |
515
+ | `AVERAGE` | Arithmetic mean of values | `=AVERAGE(B2:B8)` |
516
+ | `COUNT` | Count non-empty cells | `=COUNT(A1:A20)` |
517
+ | `MIN` | Smallest value in a range | `=MIN(C1:C5)` |
518
+ | `MAX` | Largest value in a range | `=MAX(C1:C5)` |
519
+ | `IF` | Conditional value | `=IF(A1>10, "high", "low")` |
520
+ | `CONCAT` | Join values into a string | `=CONCAT(A1, " ", B1)` |
479
521
 
480
522
  **Cell references:** A1 notation (e.g., `A1`, `B3`, `AA1`), ranges (e.g., `A1:A5`, `B2:D4`)
481
523
 
@@ -510,19 +552,34 @@ The `formatCell` command uses the browser's built-in `Intl` APIs for locale-awar
510
552
 
511
553
  ```js
512
554
  // Number: "1,234.50"
513
- engine.executeCommand('formatCell', { format: 'number', options: { decimals: 2 } });
555
+ engine.executeCommand('formatCell', {
556
+ format: 'number',
557
+ options: { decimals: 2 },
558
+ });
514
559
 
515
560
  // Currency: "$1,234.50" (or locale equivalent)
516
- engine.executeCommand('formatCell', { format: 'currency', options: { currency: 'USD' } });
561
+ engine.executeCommand('formatCell', {
562
+ format: 'currency',
563
+ options: { currency: 'USD' },
564
+ });
517
565
 
518
566
  // Euro: "1.234,50 €"
519
- engine.executeCommand('formatCell', { format: 'currency', options: { currency: 'EUR' } });
567
+ engine.executeCommand('formatCell', {
568
+ format: 'currency',
569
+ options: { currency: 'EUR' },
570
+ });
520
571
 
521
572
  // Percentage: "75.0%" (raw value 0.75 × 100)
522
- engine.executeCommand('formatCell', { format: 'percentage', options: { decimals: 1 } });
573
+ engine.executeCommand('formatCell', {
574
+ format: 'percentage',
575
+ options: { decimals: 1 },
576
+ });
523
577
 
524
578
  // Date: locale-formatted date
525
- engine.executeCommand('formatCell', { format: 'date', options: { dateStyle: 'long' } });
579
+ engine.executeCommand('formatCell', {
580
+ format: 'date',
581
+ options: { dateStyle: 'long' },
582
+ });
526
583
  ```
527
584
 
528
585
  The raw value is preserved in the `data-raw-value` attribute so it can be used for sorting and formula calculations even after formatting.
@@ -530,10 +587,12 @@ The raw value is preserved in the `data-raw-value` attribute so it can be used f
530
587
  #### Clipboard interop
531
588
 
532
589
  When copying from a Remyx table, the clipboard contains both:
590
+
533
591
  - `text/html` — clean `<table>` markup
534
592
  - `text/plain` — TSV (tab-separated values) for pasting into spreadsheets
535
593
 
536
594
  When pasting into a table cell:
595
+
537
596
  - **TSV data** (from Excel, Sheets, or tab-separated text) is detected and inserted into the grid starting at the caret cell
538
597
  - **HTML tables** (from Excel or Sheets) are converted to TSV and inserted the same way
539
598
  - Rows and columns are automatically added if the pasted data exceeds the current table dimensions
@@ -545,8 +604,8 @@ Google Sheets `<google-sheets-html-origin>` tags and Excel `mso-*` styles are au
545
604
  ```js
546
605
  registerBlockCommands(engine);
547
606
 
548
- engine.executeCommand('blockquote'); // Toggle blockquote (Mod+Shift+9)
549
- engine.executeCommand('codeBlock'); // Toggle code block (Mod+Shift+C)
607
+ engine.executeCommand('blockquote'); // Toggle blockquote (Mod+Shift+9)
608
+ engine.executeCommand('codeBlock'); // Toggle code block (Mod+Shift+C)
550
609
  engine.executeCommand('codeBlock', { language: 'javascript' }); // Code block with language
551
610
  engine.executeCommand('horizontalRule'); // Insert <hr>
552
611
  ```
@@ -557,7 +616,7 @@ engine.executeCommand('horizontalRule'); // Insert <hr>
557
616
  registerFontCommands(engine);
558
617
 
559
618
  engine.executeCommand('fontFamily', 'Georgia');
560
- engine.executeCommand('fontSize', '18px'); // Accepts px, pt, em, rem, %
619
+ engine.executeCommand('fontSize', '18px'); // Accepts px, pt, em, rem, %
561
620
  engine.executeCommand('foreColor', '#ff0000');
562
621
  engine.executeCommand('backColor', '#ffff00');
563
622
  ```
@@ -584,7 +643,7 @@ registerFindReplaceCommands(engine);
584
643
  // Search (Mod+F)
585
644
  engine.executeCommand('find', {
586
645
  text: 'hello',
587
- caseSensitive: false, // optional, default false
646
+ caseSensitive: false, // optional, default false
588
647
  });
589
648
 
590
649
  engine.executeCommand('findNext');
@@ -632,29 +691,37 @@ engine.on('fullscreen:toggle', ({ fullscreen }) => {
632
691
  ```
633
692
 
634
693
  ### Distraction-Free Mode
694
+
635
695
  ```js
636
696
  registerDistractionFreeCommands(engine);
637
697
 
638
698
  // Toggle distraction-free mode (Mod+Shift+D)
639
699
  engine.executeCommand('distractionFree');
640
700
  ```
701
+
641
702
  Hides toolbar, status bar, and menu bar. Chrome reappears on mouse movement and auto-hides after 3 seconds of inactivity. Adds `.rmx-distraction-free` class to the editor root.
642
703
 
643
704
  ### Split View
705
+
644
706
  ```js
645
707
  registerSplitViewCommands(engine);
646
708
 
647
709
  // Toggle split view (Mod+Shift+V)
648
710
  engine.executeCommand('toggleSplitView');
649
711
  ```
712
+
650
713
  Opens a side-by-side preview pane showing rendered HTML or markdown output. Adds `.rmx-split-view` class to the editor root.
651
714
 
652
715
  ### Color Presets
716
+
653
717
  ```js
654
718
  registerColorPresetCommands(engine);
655
719
 
656
720
  // Save a named color preset (persisted in localStorage)
657
- engine.executeCommand('saveColorPreset', { name: 'Brand', colors: ['#e11d48', '#3b82f6', '#22c55e'] });
721
+ engine.executeCommand('saveColorPreset', {
722
+ name: 'Brand',
723
+ colors: ['#e11d48', '#3b82f6', '#22c55e'],
724
+ });
658
725
 
659
726
  // Load all saved presets
660
727
  const presets = engine.executeCommand('loadColorPresets');
@@ -664,6 +731,7 @@ engine.executeCommand('deleteColorPreset', 'Brand');
664
731
  ```
665
732
 
666
733
  ### Typography Controls
734
+
667
735
  ```js
668
736
  // Line height (applied as inline style to selected text)
669
737
  engine.executeCommand('lineHeight', '1.8');
@@ -674,9 +742,11 @@ engine.executeCommand('letterSpacing', '0.05em');
674
742
  // Paragraph spacing (margin-bottom on block elements)
675
743
  engine.executeCommand('paragraphSpacing', '1.5em');
676
744
  ```
745
+
677
746
  These commands are registered by `registerFontCommands(engine)`. A `typography` toolbar dropdown provides UI access to all three.
678
747
 
679
748
  ### Sticky Toolbar
749
+
680
750
  The toolbar uses `position: sticky; top: 0` by default, remaining visible when scrolling long documents. No configuration needed.
681
751
 
682
752
  ### Markdown Toggle
@@ -700,10 +770,12 @@ registerAttachmentCommands(engine);
700
770
  engine.executeCommand('insertAttachment', {
701
771
  url: 'https://example.com/report.pdf',
702
772
  filename: 'report.pdf',
703
- filesize: '2.4 MB', // optional, displayed in UI
773
+ filesize: '2.4 MB', // optional, displayed in UI
704
774
  });
705
775
 
706
- engine.executeCommand('removeAttachment', { element: attachmentElement });
776
+ engine.executeCommand('removeAttachment', {
777
+ element: attachmentElement,
778
+ });
707
779
  ```
708
780
 
709
781
  ### Document Import
@@ -740,7 +812,11 @@ Each item has the shape `{ id, label, description, icon, keywords, category, act
740
812
  The last 5 executed commands are tracked in `localStorage` and pinned to the top of the palette under a "Recent" category when no search query is active:
741
813
 
742
814
  ```js
743
- import { getRecentCommands, recordRecentCommand, clearRecentCommands } from '@remyxjs/core';
815
+ import {
816
+ getRecentCommands,
817
+ recordRecentCommand,
818
+ clearRecentCommands,
819
+ } from '@remyxjs/core';
744
820
 
745
821
  // Commands are recorded automatically when executed via the palette.
746
822
  // You can also record manually:
@@ -753,7 +829,7 @@ getRecentCommands(); // → ['heading1']
753
829
  clearRecentCommands();
754
830
 
755
831
  // filterSlashItems pins recent items by default (disable with { pinRecent: false })
756
- filterSlashItems(SLASH_COMMAND_ITEMS, ''); // recent items at top
832
+ filterSlashItems(SLASH_COMMAND_ITEMS, ''); // recent items at top
757
833
  filterSlashItems(SLASH_COMMAND_ITEMS, '', { pinRecent: false }); // no pinning
758
834
  ```
759
835
 
@@ -762,7 +838,11 @@ filterSlashItems(SLASH_COMMAND_ITEMS, '', { pinRecent: false }); // no pinning
762
838
  Register custom command items that appear alongside built-in commands in the palette:
763
839
 
764
840
  ```js
765
- import { registerCommandItems, unregisterCommandItem, getCustomCommandItems } from '@remyxjs/core';
841
+ import {
842
+ registerCommandItems,
843
+ unregisterCommandItem,
844
+ getCustomCommandItems,
845
+ } from '@remyxjs/core';
766
846
 
767
847
  // Register a single item
768
848
  registerCommandItems({
@@ -772,17 +852,37 @@ registerCommandItems({
772
852
  icon: '✍️',
773
853
  keywords: ['signature', 'sign', 'email'],
774
854
  category: 'Custom',
775
- action: (engine) => engine.executeCommand('insertHTML', '<p>— John Doe</p>'),
855
+ action: (engine) =>
856
+ engine.executeCommand('insertHTML', '<p>— John Doe</p>'),
776
857
  });
777
858
 
778
859
  // Register multiple items at once
779
860
  registerCommandItems([
780
- { id: 'draft', label: 'Save Draft', description: 'Save as draft', icon: '💾', keywords: ['save', 'draft'], category: 'Custom', action: (engine) => saveDraft(engine.getHTML()) },
781
- { id: 'publish', label: 'Publish', description: 'Publish document', icon: '🚀', keywords: ['publish', 'post'], category: 'Custom', action: (engine) => publish(engine.getHTML()) },
861
+ {
862
+ id: 'draft',
863
+ label: 'Save Draft',
864
+ description: 'Save as draft',
865
+ icon: '💾',
866
+ keywords: ['save', 'draft'],
867
+ category: 'Custom',
868
+ action: (engine) => saveDraft(engine.getHTML()),
869
+ },
870
+ {
871
+ id: 'publish',
872
+ label: 'Publish',
873
+ description: 'Publish document',
874
+ icon: '🚀',
875
+ keywords: ['publish', 'post'],
876
+ category: 'Custom',
877
+ action: (engine) => publish(engine.getHTML()),
878
+ },
782
879
  ]);
783
880
 
784
881
  // Re-registering the same id replaces the previous item
785
- registerCommandItems({ id: 'insertSignature', label: 'Insert Sig (updated)', /* ... */ });
882
+ registerCommandItems({
883
+ id: 'insertSignature',
884
+ label: 'Insert Sig (updated)' /* ... */,
885
+ });
786
886
 
787
887
  // Remove a custom item
788
888
  unregisterCommandItem('insertSignature');
@@ -810,26 +910,28 @@ import {
810
910
  } from '@remyxjs/core';
811
911
  ```
812
912
 
813
- | Provider | Use Case | Config Shorthand |
814
- | --- | --- | --- |
815
- | `LocalStorageProvider` | Browser apps (default) | `'localStorage'` or omit |
816
- | `SessionStorageProvider` | Tab-scoped saves | `'sessionStorage'` |
817
- | `FileSystemProvider` | Node / Electron / Tauri | `{ writeFn, readFn, deleteFn }` |
818
- | `CloudProvider` | AWS S3, GCP, any HTTP API | `{ endpoint, headers, ... }` |
819
- | `CustomProvider` | Full consumer control | `{ save, load, clear }` |
913
+ | Provider | Use Case | Config Shorthand |
914
+ | ------------------------ | ------------------------- | ------------------------------- |
915
+ | `LocalStorageProvider` | Browser apps (default) | `'localStorage'` or omit |
916
+ | `SessionStorageProvider` | Tab-scoped saves | `'sessionStorage'` |
917
+ | `FileSystemProvider` | Node / Electron / Tauri | `{ writeFn, readFn, deleteFn }` |
918
+ | `CloudProvider` | AWS S3, GCP, any HTTP API | `{ endpoint, headers, ... }` |
919
+ | `CustomProvider` | Full consumer control | `{ save, load, clear }` |
820
920
 
821
921
  Each provider implements `save(key, content)`, `load(key)`, and `clear(key)`. Content is wrapped in a JSON envelope with `{ content, timestamp, version }`.
822
922
 
823
923
  **Factory function** — `createStorageProvider(config)` resolves shorthand strings or objects into provider instances:
824
924
 
825
925
  ```js
826
- const local = createStorageProvider(); // LocalStorageProvider
827
- const session = createStorageProvider('sessionStorage'); // SessionStorageProvider
828
- const cloud = createStorageProvider({ // CloudProvider
926
+ const local = createStorageProvider(); // LocalStorageProvider
927
+ const session = createStorageProvider('sessionStorage'); // SessionStorageProvider
928
+ const cloud = createStorageProvider({
929
+ // CloudProvider
829
930
  endpoint: 'https://api.example.com/autosave',
830
931
  headers: { Authorization: 'Bearer token123' },
831
932
  });
832
- const fs = createStorageProvider({ // FileSystemProvider
933
+ const fs = createStorageProvider({
934
+ // FileSystemProvider
833
935
  writeFn: async (key, data) => writeFile(`/saves/${key}.json`, data),
834
936
  readFn: async (key) => readFile(`/saves/${key}.json`),
835
937
  deleteFn: async (key) => unlink(`/saves/${key}.json`),
@@ -841,7 +943,7 @@ const fs = createStorageProvider({ // FileSystemProvide
841
943
  ```js
842
944
  const s3Provider = new CloudProvider({
843
945
  endpoint: 'https://my-bucket.s3.amazonaws.com',
844
- buildUrl: (key) => getPresignedUploadUrl(key), // S3 presigned URL
946
+ buildUrl: (key) => getPresignedUploadUrl(key), // S3 presigned URL
845
947
  buildLoadUrl: (key) => getPresignedDownloadUrl(key),
846
948
  method: 'PUT',
847
949
  headers: { 'Content-Type': 'application/json' },
@@ -855,20 +957,20 @@ const s3Provider = new CloudProvider({
855
957
  import { AutosaveManager } from '@remyxjs/core';
856
958
 
857
959
  const manager = new AutosaveManager(engine, {
858
- provider: 'localStorage', // or any provider config
859
- key: 'doc-123', // storage key (default: 'rmx-default')
860
- interval: 30000, // periodic save interval in ms (default: 30s)
861
- debounce: 2000, // debounce delay after content change (default: 2s)
862
- enabled: true, // toggle on/off (default: true)
960
+ provider: 'localStorage', // or any provider config
961
+ key: 'doc-123', // storage key (default: 'rmx-default')
962
+ interval: 30000, // periodic save interval in ms (default: 30s)
963
+ debounce: 2000, // debounce delay after content change (default: 2s)
964
+ enabled: true, // toggle on/off (default: true)
863
965
  });
864
966
 
865
- manager.init(); // start listening to content:change
967
+ manager.init(); // start listening to content:change
866
968
 
867
- await manager.save(); // force an immediate save
868
- await manager.checkRecovery(engine.getHTML()); // check for recoverable content
869
- await manager.clearRecovery(); // clear stored recovery data
969
+ await manager.save(); // force an immediate save
970
+ await manager.checkRecovery(engine.getHTML()); // check for recoverable content
971
+ await manager.clearRecovery(); // clear stored recovery data
870
972
 
871
- manager.destroy(); // cleanup timers, listeners, final save
973
+ manager.destroy(); // cleanup timers, listeners, final save
872
974
  ```
873
975
 
874
976
  ### Autosave Events
@@ -886,11 +988,14 @@ engine.eventBus.on('autosave:error', ({ error }) => {
886
988
  console.error('Autosave failed:', error.message);
887
989
  });
888
990
 
889
- engine.eventBus.on('autosave:recovered', ({ recoveredContent, timestamp }) => {
890
- if (confirm('Unsaved changes found. Restore?')) {
891
- engine.setHTML(recoveredContent);
892
- }
893
- });
991
+ engine.eventBus.on(
992
+ 'autosave:recovered',
993
+ ({ recoveredContent, timestamp }) => {
994
+ if (confirm('Unsaved changes found. Restore?')) {
995
+ engine.setHTML(recoveredContent);
996
+ }
997
+ },
998
+ );
894
999
  ```
895
1000
 
896
1001
  ## Plugin System
@@ -922,7 +1027,9 @@ const HighlightPlugin = createPlugin({
922
1027
  document.execCommand('hiliteColor', false, 'yellow');
923
1028
  },
924
1029
  isActive(engine) {
925
- return engine.selection.getActiveFormats().backColor === 'yellow';
1030
+ return (
1031
+ engine.selection.getActiveFormats().backColor === 'yellow'
1032
+ );
926
1033
  },
927
1034
  shortcut: 'mod+shift+h',
928
1035
  },
@@ -943,7 +1050,8 @@ const HighlightPlugin = createPlugin({
943
1050
  statusBarItems: [
944
1051
  {
945
1052
  name: 'highlight-status',
946
- render: (engine) => engine.selection.getActiveFormats().backColor || 'none',
1053
+ render: (engine) =>
1054
+ engine.selection.getActiveFormats().backColor || 'none',
947
1055
  },
948
1056
  ],
949
1057
 
@@ -1003,12 +1111,19 @@ Each lifecycle callback is sandboxed — if it throws, the error is caught, logg
1003
1111
  Declare dependencies to control initialization order:
1004
1112
 
1005
1113
  ```js
1006
- const BasePlugin = createPlugin({ name: 'base', init() { /* ... */ } });
1114
+ const BasePlugin = createPlugin({
1115
+ name: 'base',
1116
+ init() {
1117
+ /* ... */
1118
+ },
1119
+ });
1007
1120
 
1008
1121
  const ExtensionPlugin = createPlugin({
1009
1122
  name: 'extension',
1010
1123
  dependencies: ['base'], // initialized after 'base'
1011
- init(api) { /* can safely use base's commands */ },
1124
+ init(api) {
1125
+ /* can safely use base's commands */
1126
+ },
1012
1127
  });
1013
1128
  ```
1014
1129
 
@@ -1022,14 +1137,35 @@ Plugins can define a settings schema with type validation:
1022
1137
  const ThemePlugin = createPlugin({
1023
1138
  name: 'custom-theme',
1024
1139
  settingsSchema: [
1025
- { key: 'fontSize', type: 'number', label: 'Font Size', defaultValue: 16, validate: (v) => v >= 8 && v <= 72 },
1026
- { key: 'fontFamily', type: 'string', label: 'Font Family', defaultValue: 'sans-serif' },
1027
- { key: 'mode', type: 'select', label: 'Mode', defaultValue: 'light', options: [
1028
- { label: 'Light', value: 'light' },
1029
- { label: 'Dark', value: 'dark' },
1030
- ]},
1140
+ {
1141
+ key: 'fontSize',
1142
+ type: 'number',
1143
+ label: 'Font Size',
1144
+ defaultValue: 16,
1145
+ validate: (v) => v >= 8 && v <= 72,
1146
+ },
1147
+ {
1148
+ key: 'fontFamily',
1149
+ type: 'string',
1150
+ label: 'Font Family',
1151
+ defaultValue: 'sans-serif',
1152
+ },
1153
+ {
1154
+ key: 'mode',
1155
+ type: 'select',
1156
+ label: 'Mode',
1157
+ defaultValue: 'light',
1158
+ options: [
1159
+ { label: 'Light', value: 'light' },
1160
+ { label: 'Dark', value: 'dark' },
1161
+ ],
1162
+ },
1031
1163
  ],
1032
- defaultSettings: { fontSize: 16, fontFamily: 'sans-serif', mode: 'light' },
1164
+ defaultSettings: {
1165
+ fontSize: 16,
1166
+ fontFamily: 'sans-serif',
1167
+ mode: 'light',
1168
+ },
1033
1169
  init(api) {
1034
1170
  const size = api.getSetting('fontSize');
1035
1171
  api.element.style.fontSize = `${size}px`;
@@ -1047,7 +1183,11 @@ engine.plugins.getPluginSettings('custom-theme'); // { fontSize: 18, fontFamily:
1047
1183
  A global registry for plugin discovery and marketplace concepts:
1048
1184
 
1049
1185
  ```js
1050
- import { registerPluginInRegistry, searchPluginRegistry, listRegisteredPlugins } from '@remyxjs/core';
1186
+ import {
1187
+ registerPluginInRegistry,
1188
+ searchPluginRegistry,
1189
+ listRegisteredPlugins,
1190
+ } from '@remyxjs/core';
1051
1191
 
1052
1192
  // Register a plugin for discovery
1053
1193
  registerPluginInRegistry({
@@ -1060,9 +1200,9 @@ registerPluginInRegistry({
1060
1200
  });
1061
1201
 
1062
1202
  // Search the registry
1063
- searchPluginRegistry('math'); // → [{ name: 'math-equations', ... }]
1203
+ searchPluginRegistry('math'); // → [{ name: 'math-equations', ... }]
1064
1204
  searchPluginRegistry('latex'); // → [{ name: 'math-equations', ... }] (matches tags)
1065
- listRegisteredPlugins(); // → all registered entries
1205
+ listRegisteredPlugins(); // → all registered entries
1066
1206
 
1067
1207
  // Install from registry
1068
1208
  const entry = searchPluginRegistry('math')[0];
@@ -1087,21 +1227,21 @@ const MyPlugin = createPlugin({
1087
1227
 
1088
1228
  Plugins without `requiresFullAccess` receive a sandboxed API:
1089
1229
 
1090
- | Property/Method | Description |
1091
- | --- | --- |
1092
- | `element` | Editor DOM element (read-only) |
1093
- | `options` | Engine options (read-only copy) |
1094
- | `executeCommand(name, ...args)` | Execute a command |
1095
- | `on(event, handler)` | Subscribe to events |
1096
- | `off(event, handler)` | Unsubscribe |
1097
- | `getSelection()` | Browser Selection object |
1098
- | `getRange()` | Current Range in editor |
1099
- | `getActiveFormats()` | Current formatting state |
1100
- | `getHTML()` | Get content as HTML |
1101
- | `getText()` | Get content as plain text |
1102
- | `isEmpty()` | Check if editor is empty |
1103
- | `getSetting(key)` | Get a plugin-scoped setting value |
1104
- | `setSetting(key, value)` | Set a plugin-scoped setting value (with validation) |
1230
+ | Property/Method | Description |
1231
+ | ------------------------------- | --------------------------------------------------- |
1232
+ | `element` | Editor DOM element (read-only) |
1233
+ | `options` | Engine options (read-only copy) |
1234
+ | `executeCommand(name, ...args)` | Execute a command |
1235
+ | `on(event, handler)` | Subscribe to events |
1236
+ | `off(event, handler)` | Unsubscribe |
1237
+ | `getSelection()` | Browser Selection object |
1238
+ | `getRange()` | Current Range in editor |
1239
+ | `getActiveFormats()` | Current formatting state |
1240
+ | `getHTML()` | Get content as HTML |
1241
+ | `getText()` | Get content as plain text |
1242
+ | `isEmpty()` | Check if editor is empty |
1243
+ | `getSetting(key)` | Get a plugin-scoped setting value |
1244
+ | `setSetting(key, value)` | Set a plugin-scoped setting value (with validation) |
1105
1245
 
1106
1246
  ### Built-in Plugins
1107
1247
 
@@ -1133,8 +1273,13 @@ engine.plugins.register(PlaceholderPlugin('Start writing...'));
1133
1273
 
1134
1274
  ```js
1135
1275
  import {
1136
- SyntaxHighlightPlugin, SUPPORTED_LANGUAGES, detectLanguage, tokenize,
1137
- registerLanguage, unregisterLanguage, runRules,
1276
+ SyntaxHighlightPlugin,
1277
+ SUPPORTED_LANGUAGES,
1278
+ detectLanguage,
1279
+ tokenize,
1280
+ registerLanguage,
1281
+ unregisterLanguage,
1282
+ runRules,
1138
1283
  } from '@remyxjs/core';
1139
1284
 
1140
1285
  // Register the plugin
@@ -1191,26 +1336,41 @@ The inline code element will be tokenized with the same `rmx-syn-*` classes used
1191
1336
  Register custom language tokenizers at runtime. The new language immediately becomes available for highlighting and appears in `SUPPORTED_LANGUAGES`.
1192
1337
 
1193
1338
  ```js
1194
- import { registerLanguage, unregisterLanguage, runRules } from '@remyxjs/core';
1339
+ import {
1340
+ registerLanguage,
1341
+ unregisterLanguage,
1342
+ runRules,
1343
+ } from '@remyxjs/core';
1195
1344
 
1196
1345
  // Define tokenizer rules (same format used by all built-in tokenizers)
1197
1346
  const RUBY_RULES = [
1198
1347
  [/#[^\n]*/g, 'rmx-syn-comment'],
1199
1348
  [/"(?:[^"\\]|\\.)*"/g, 'rmx-syn-string'],
1200
1349
  [/'(?:[^'\\]|\\.)*'/g, 'rmx-syn-string'],
1201
- [/\b(?:def|end|class|module|if|else|elsif|unless|do|while|for|return|yield|begin|rescue|ensure)\b/g, 'rmx-syn-keyword'],
1202
- [/\b(?:puts|print|require|include|attr_accessor|attr_reader)\b/g, 'rmx-syn-builtin'],
1350
+ [
1351
+ /\b(?:def|end|class|module|if|else|elsif|unless|do|while|for|return|yield|begin|rescue|ensure)\b/g,
1352
+ 'rmx-syn-keyword',
1353
+ ],
1354
+ [
1355
+ /\b(?:puts|print|require|include|attr_accessor|attr_reader)\b/g,
1356
+ 'rmx-syn-builtin',
1357
+ ],
1203
1358
  [/:\w+/g, 'rmx-syn-entity'],
1204
1359
  [/\b\d[\d_.]*\b/g, 'rmx-syn-number'],
1205
1360
  ];
1206
1361
 
1207
1362
  // Register with the built-in rule engine
1208
- registerLanguage('ruby', 'Ruby', (code) => runRules(code, RUBY_RULES), ['rb']);
1363
+ registerLanguage(
1364
+ 'ruby',
1365
+ 'Ruby',
1366
+ (code) => runRules(code, RUBY_RULES),
1367
+ ['rb'],
1368
+ );
1209
1369
 
1210
1370
  // Now works everywhere
1211
- tokenize('puts "hello"', 'ruby'); // tokenize API
1212
- tokenize('puts "hello"', 'rb'); // alias works too
1213
- engine.executeCommand('setCodeLanguage', { language: 'ruby' }); // in editor
1371
+ tokenize('puts "hello"', 'ruby'); // tokenize API
1372
+ tokenize('puts "hello"', 'rb'); // alias works too
1373
+ engine.executeCommand('setCodeLanguage', { language: 'ruby' }); // in editor
1214
1374
 
1215
1375
  // Remove later if needed
1216
1376
  unregisterLanguage('ruby', ['rb']);
@@ -1241,25 +1401,33 @@ evaluateTableFormulas(tableElement);
1241
1401
  import { CommentsPlugin, parseMentions } from '@remyxjs/core';
1242
1402
 
1243
1403
  // Register the plugin
1244
- engine.plugins.register(CommentsPlugin({
1245
- onComment: (thread) => saveToServer(thread),
1246
- onResolve: ({ thread, resolved }) => updateServer(thread),
1247
- onDelete: (thread) => deleteFromServer(thread),
1248
- onReply: ({ thread, reply }) => saveReply(thread.id, reply),
1249
- mentionUsers: ['alice', 'bob', 'charlie'],
1250
- commentOnly: false, // true = read-only editor with comment support
1251
- }));
1404
+ engine.plugins.register(
1405
+ CommentsPlugin({
1406
+ onComment: (thread) => saveToServer(thread),
1407
+ onResolve: ({ thread, resolved }) => updateServer(thread),
1408
+ onDelete: (thread) => deleteFromServer(thread),
1409
+ onReply: ({ thread, reply }) => saveReply(thread.id, reply),
1410
+ mentionUsers: ['alice', 'bob', 'charlie'],
1411
+ commentOnly: false, // true = read-only editor with comment support
1412
+ }),
1413
+ );
1252
1414
 
1253
1415
  // The plugin exposes engine._comments API:
1254
- engine._comments.addComment({ author: 'Alice', body: 'This needs clarification @bob' });
1416
+ engine._comments.addComment({
1417
+ author: 'Alice',
1418
+ body: 'This needs clarification @bob',
1419
+ });
1255
1420
  engine._comments.resolveComment(threadId, true);
1256
- engine._comments.replyToComment(threadId, { author: 'Bob', body: 'Fixed!' });
1421
+ engine._comments.replyToComment(threadId, {
1422
+ author: 'Bob',
1423
+ body: 'Fixed!',
1424
+ });
1257
1425
  engine._comments.deleteComment(threadId);
1258
1426
  engine._comments.navigateToComment(threadId); // scroll to + select
1259
- engine._comments.getAllThreads(); // all threads (newest first)
1427
+ engine._comments.getAllThreads(); // all threads (newest first)
1260
1428
  engine._comments.getUnresolvedThreads();
1261
1429
  engine._comments.getResolvedThreads();
1262
- engine._comments.exportThreads(); // JSON-serializable array
1430
+ engine._comments.exportThreads(); // JSON-serializable array
1263
1431
  engine._comments.importThreads(data); // load from server
1264
1432
 
1265
1433
  // Parse @mentions from text
@@ -1269,15 +1437,27 @@ parseMentions('Hello @alice and @bob'); // → ['alice', 'bob']
1269
1437
  **CalloutPlugin** — Styled callout/alert/admonition blocks with 7 built-in types, custom type registration, collapsible toggle, nested content, and GitHub-flavored alert syntax auto-conversion.
1270
1438
 
1271
1439
  ```js
1272
- import { CalloutPlugin, registerCalloutType, getCalloutTypes, parseGFMAlert } from '@remyxjs/core';
1440
+ import {
1441
+ CalloutPlugin,
1442
+ registerCalloutType,
1443
+ getCalloutTypes,
1444
+ parseGFMAlert,
1445
+ } from '@remyxjs/core';
1273
1446
 
1274
1447
  // Register the plugin
1275
1448
  engine.plugins.register(CalloutPlugin());
1276
1449
 
1277
1450
  // Insert a callout at the cursor
1278
1451
  engine.executeCommand('insertCallout', { type: 'warning' });
1279
- engine.executeCommand('insertCallout', { type: 'tip', collapsible: true, title: 'Pro tip' });
1280
- engine.executeCommand('insertCallout', { type: 'info', content: '<p>Custom HTML content</p>' });
1452
+ engine.executeCommand('insertCallout', {
1453
+ type: 'tip',
1454
+ collapsible: true,
1455
+ title: 'Pro tip',
1456
+ });
1457
+ engine.executeCommand('insertCallout', {
1458
+ type: 'info',
1459
+ content: '<p>Custom HTML content</p>',
1460
+ });
1281
1461
 
1282
1462
  // Change type of the focused callout
1283
1463
  engine.executeCommand('changeCalloutType', 'error');
@@ -1289,7 +1469,12 @@ engine.executeCommand('toggleCalloutCollapse');
1289
1469
  engine.executeCommand('removeCallout');
1290
1470
 
1291
1471
  // Register a custom callout type
1292
- registerCalloutType({ type: 'security', label: 'Security', icon: '🔒', color: '#dc2626' });
1472
+ registerCalloutType({
1473
+ type: 'security',
1474
+ label: 'Security',
1475
+ icon: '🔒',
1476
+ color: '#dc2626',
1477
+ });
1293
1478
 
1294
1479
  // List all types
1295
1480
  getCalloutTypes(); // → [{ type: 'info', ... }, { type: 'warning', ... }, ..., { type: 'security', ... }]
@@ -1303,24 +1488,31 @@ parseGFMAlert('[!WARNING]\nBe careful'); // → { type: 'warning', body: 'Be car
1303
1488
  ```js
1304
1489
  import { LinkPlugin, detectLinks, slugify } from '@remyxjs/core';
1305
1490
 
1306
- engine.plugins.register(LinkPlugin({
1307
- onLinkClick: ({ href, text, timestamp }) => trackClick(href),
1308
- onUnfurl: async (url) => {
1309
- const res = await fetch(`/api/unfurl?url=${encodeURIComponent(url)}`);
1310
- return res.json(); // { title, description, image }
1311
- },
1312
- validateLink: async (url) => {
1313
- const res = await fetch(url, { method: 'HEAD' });
1314
- return res.ok;
1315
- },
1316
- onBrokenLink: (url, el) => console.warn('Broken:', url),
1317
- autoLink: true, // auto-convert URLs/emails/phones on Space/Enter
1318
- showPreviews: true, // hover tooltips on links
1319
- scanInterval: 60000, // broken link scan interval (ms)
1320
- }));
1491
+ engine.plugins.register(
1492
+ LinkPlugin({
1493
+ onLinkClick: ({ href, text, timestamp }) => trackClick(href),
1494
+ onUnfurl: async (url) => {
1495
+ const res = await fetch(
1496
+ `/api/unfurl?url=${encodeURIComponent(url)}`,
1497
+ );
1498
+ return res.json(); // { title, description, image }
1499
+ },
1500
+ validateLink: async (url) => {
1501
+ const res = await fetch(url, { method: 'HEAD' });
1502
+ return res.ok;
1503
+ },
1504
+ onBrokenLink: (url, el) => console.warn('Broken:', url),
1505
+ autoLink: true, // auto-convert URLs/emails/phones on Space/Enter
1506
+ showPreviews: true, // hover tooltips on links
1507
+ scanInterval: 60000, // broken link scan interval (ms)
1508
+ }),
1509
+ );
1321
1510
 
1322
1511
  // Bookmark anchors for intra-document linking
1323
- engine.executeCommand('insertBookmark', { name: 'Introduction', id: 'intro' });
1512
+ engine.executeCommand('insertBookmark', {
1513
+ name: 'Introduction',
1514
+ id: 'intro',
1515
+ });
1324
1516
  engine.executeCommand('linkToBookmark', 'intro'); // link selected text to #intro
1325
1517
  engine.executeCommand('getBookmarks'); // → [{ id, name, element }]
1326
1518
  engine.executeCommand('scanBrokenLinks'); // manual scan
@@ -1334,7 +1526,12 @@ slugify('Section #1: Overview!'); // → 'section-1-overview'
1334
1526
  **TemplatePlugin** — Merge tags, conditional blocks, repeatable sections, live preview, and pre-built template library.
1335
1527
 
1336
1528
  ```js
1337
- import { TemplatePlugin, renderTemplate, extractTags, getTemplateLibrary } from '@remyxjs/core';
1529
+ import {
1530
+ TemplatePlugin,
1531
+ renderTemplate,
1532
+ extractTags,
1533
+ getTemplateLibrary,
1534
+ } from '@remyxjs/core';
1338
1535
 
1339
1536
  engine.plugins.register(TemplatePlugin());
1340
1537
 
@@ -1345,7 +1542,10 @@ engine.executeCommand('insertMergeTag', 'recipient_name');
1345
1542
  engine.executeCommand('loadTemplate', 'email');
1346
1543
 
1347
1544
  // Preview with sample data (read-only mode)
1348
- engine.executeCommand('previewTemplate', { recipient_name: 'Alice', body: 'Welcome!' });
1545
+ engine.executeCommand('previewTemplate', {
1546
+ recipient_name: 'Alice',
1547
+ body: 'Welcome!',
1548
+ });
1349
1549
  engine.executeCommand('exitPreview');
1350
1550
 
1351
1551
  // Export as JSON
@@ -1355,7 +1555,9 @@ const exported = engine.executeCommand('exportTemplate');
1355
1555
  // Render template string with data
1356
1556
  renderTemplate('Hello {{name}}!', { name: 'World' }); // → 'Hello World!'
1357
1557
  renderTemplate('{{#if show}}visible{{/if}}', { show: true }); // → 'visible'
1358
- renderTemplate('{{#each items}}{{name}} {{/each}}', { items: [{name:'A'},{name:'B'}] }); // → 'A B '
1558
+ renderTemplate('{{#each items}}{{name}} {{/each}}', {
1559
+ items: [{ name: 'A' }, { name: 'B' }],
1560
+ }); // → 'A B '
1359
1561
  ```
1360
1562
 
1361
1563
  **KeyboardPlugin** — Vim/Emacs modes, auto-pairing, multi-cursor, jump-to-heading.
@@ -1370,10 +1572,12 @@ engine.plugins.register(KeyboardPlugin({ mode: 'vim' }));
1370
1572
  engine.plugins.register(KeyboardPlugin({ mode: 'emacs' }));
1371
1573
 
1372
1574
  // Default with auto-pair and custom bindings
1373
- engine.plugins.register(KeyboardPlugin({
1374
- autoPair: true,
1375
- keyBindings: { 'ctrl+shift+l': 'insertLink' },
1376
- }));
1575
+ engine.plugins.register(
1576
+ KeyboardPlugin({
1577
+ autoPair: true,
1578
+ keyBindings: { 'ctrl+shift+l': 'insertLink' },
1579
+ }),
1580
+ );
1377
1581
 
1378
1582
  // Multi-cursor: Cmd+D selects next occurrence
1379
1583
  // Jump-to-heading: Cmd+Shift+G
@@ -1385,33 +1589,49 @@ engine.plugins.register(KeyboardPlugin({
1385
1589
  ```js
1386
1590
  import { DragDropPlugin } from '@remyxjs/core';
1387
1591
 
1388
- engine.plugins.register(DragDropPlugin({
1389
- onDrop: (event, data) => console.log('Dropped:', data.type),
1390
- onFileDrop: (files) => uploadFiles(files),
1391
- allowExternalDrop: true,
1392
- showDropZone: true,
1393
- enableReorder: true,
1394
- }));
1592
+ engine.plugins.register(
1593
+ DragDropPlugin({
1594
+ onDrop: (event, data) => console.log('Dropped:', data.type),
1595
+ onFileDrop: (files) => uploadFiles(files),
1596
+ allowExternalDrop: true,
1597
+ showDropZone: true,
1598
+ enableReorder: true,
1599
+ }),
1600
+ );
1395
1601
 
1396
1602
  // Keyboard shortcuts for block reorder
1397
- engine.executeCommand('moveBlockUp'); // Cmd+Shift+ArrowUp
1603
+ engine.executeCommand('moveBlockUp'); // Cmd+Shift+ArrowUp
1398
1604
  engine.executeCommand('moveBlockDown'); // Cmd+Shift+ArrowDown
1399
1605
  ```
1400
1606
 
1401
1607
  **MathPlugin** — LaTeX/KaTeX math rendering with inline and block equations, symbol palette, equation numbering, and MathML export.
1402
1608
 
1403
1609
  ```js
1404
- import { MathPlugin, getSymbolPalette, parseMathExpressions, latexToMathML } from '@remyxjs/core';
1610
+ import {
1611
+ MathPlugin,
1612
+ getSymbolPalette,
1613
+ parseMathExpressions,
1614
+ latexToMathML,
1615
+ } from '@remyxjs/core';
1405
1616
 
1406
- engine.plugins.register(MathPlugin({
1407
- renderMath: (latex, displayMode) => katex.renderToString(latex, { displayMode }), // plug in KaTeX
1408
- }));
1617
+ engine.plugins.register(
1618
+ MathPlugin({
1619
+ renderMath: (latex, displayMode) =>
1620
+ katex.renderToString(latex, { displayMode }), // plug in KaTeX
1621
+ }),
1622
+ );
1409
1623
 
1410
1624
  // Insert inline math
1411
- engine.executeCommand('insertMath', { latex: 'E = mc^2', displayMode: false });
1625
+ engine.executeCommand('insertMath', {
1626
+ latex: 'E = mc^2',
1627
+ displayMode: false,
1628
+ });
1412
1629
 
1413
1630
  // Insert block equation (auto-numbered)
1414
- engine.executeCommand('insertMath', { latex: '\\sum_{i=1}^{n} x_i', displayMode: true });
1631
+ engine.executeCommand('insertMath', {
1632
+ latex: '\\sum_{i=1}^{n} x_i',
1633
+ displayMode: true,
1634
+ });
1415
1635
 
1416
1636
  // Symbol palette for building UIs
1417
1637
  getSymbolPalette(); // → [{ category: 'Greek', symbols: [{ label: 'α', latex: '\\alpha' }, ...] }, ...]
@@ -1427,12 +1647,20 @@ latexToMathML('\\frac{a}{b}'); // → '<math ...><mfrac>...</mfrac></math>'
1427
1647
  **TocPlugin** — Auto-generated table of contents, document outline, heading validation, and click-to-scroll navigation.
1428
1648
 
1429
1649
  ```js
1430
- import { TocPlugin, buildOutline, flattenOutline, renderTocHTML, validateHeadingHierarchy } from '@remyxjs/core';
1650
+ import {
1651
+ TocPlugin,
1652
+ buildOutline,
1653
+ flattenOutline,
1654
+ renderTocHTML,
1655
+ validateHeadingHierarchy,
1656
+ } from '@remyxjs/core';
1431
1657
 
1432
- engine.plugins.register(TocPlugin({
1433
- numbering: true,
1434
- onOutlineChange: (outline) => updateSidebar(outline),
1435
- }));
1658
+ engine.plugins.register(
1659
+ TocPlugin({
1660
+ numbering: true,
1661
+ onOutlineChange: (outline) => updateSidebar(outline),
1662
+ }),
1663
+ );
1436
1664
 
1437
1665
  // Insert a rendered TOC into the document
1438
1666
  engine.executeCommand('insertToc');
@@ -1451,14 +1679,21 @@ engine.executeCommand('validateHeadings');
1451
1679
  **AnalyticsPlugin** — Readability scores, reading time, vocabulary level, sentence warnings, goal tracking, keyword density, and SEO hints.
1452
1680
 
1453
1681
  ```js
1454
- import { AnalyticsPlugin, analyzeContent, keywordDensity, seoAnalysis } from '@remyxjs/core';
1682
+ import {
1683
+ AnalyticsPlugin,
1684
+ analyzeContent,
1685
+ keywordDensity,
1686
+ seoAnalysis,
1687
+ } from '@remyxjs/core';
1455
1688
 
1456
- engine.plugins.register(AnalyticsPlugin({
1457
- wordsPerMinute: 200,
1458
- targetWordCount: 1000,
1459
- maxSentenceLength: 30,
1460
- onAnalytics: (stats) => updateDashboard(stats),
1461
- }));
1689
+ engine.plugins.register(
1690
+ AnalyticsPlugin({
1691
+ wordsPerMinute: 200,
1692
+ targetWordCount: 1000,
1693
+ maxSentenceLength: 30,
1694
+ onAnalytics: (stats) => updateDashboard(stats),
1695
+ }),
1696
+ );
1462
1697
 
1463
1698
  // Toggle analytics panel visibility (emits 'analytics:toggle' event)
1464
1699
  engine.executeCommand('toggleAnalytics');
@@ -1484,21 +1719,28 @@ engine.executeCommand('getKeywordDensity', 'editor');
1484
1719
  **SpellcheckPlugin** — Spelling & grammar checking with inline underlines, writing-style presets, custom service integration, and persistent dictionary.
1485
1720
 
1486
1721
  ```js
1487
- import { SpellcheckPlugin, analyzeGrammar, STYLE_PRESETS } from '@remyxjs/core';
1722
+ import {
1723
+ SpellcheckPlugin,
1724
+ analyzeGrammar,
1725
+ STYLE_PRESETS,
1726
+ } from '@remyxjs/core';
1488
1727
 
1489
- engine.plugins.register(SpellcheckPlugin({
1490
- language: 'en-US', // BCP 47 language tag
1491
- enabled: true, // enable on init
1492
- grammarRules: true, // enable built-in grammar checking
1493
- stylePreset: 'formal', // 'formal'|'casual'|'technical'|'academic'
1494
- customService: { // optional external service
1495
- check: async (text) => [...suggestions],
1496
- },
1497
- dictionary: ['Remyx', 'WYSIWYG'], // custom words to ignore
1498
- persistent: true, // persist dictionary in localStorage
1499
- onError: (errors) => {},
1500
- onCorrection: ({ original, replacement }) => {},
1501
- }));
1728
+ engine.plugins.register(
1729
+ SpellcheckPlugin({
1730
+ language: 'en-US', // BCP 47 language tag
1731
+ enabled: true, // enable on init
1732
+ grammarRules: true, // enable built-in grammar checking
1733
+ stylePreset: 'formal', // 'formal'|'casual'|'technical'|'academic'
1734
+ customService: {
1735
+ // optional external service
1736
+ check: async (text) => [...suggestions],
1737
+ },
1738
+ dictionary: ['Remyx', 'WYSIWYG'], // custom words to ignore
1739
+ persistent: true, // persist dictionary in localStorage
1740
+ onError: (errors) => {},
1741
+ onCorrection: ({ original, replacement }) => {},
1742
+ }),
1743
+ );
1502
1744
 
1503
1745
  // Toggle spellcheck on/off
1504
1746
  engine.executeCommand('toggleSpellcheck');
@@ -1533,20 +1775,22 @@ engine.executeCommand('getSpellcheckStats');
1533
1775
  ```js
1534
1776
  import { CollaborationPlugin } from '@remyxjs/core';
1535
1777
 
1536
- engine.plugins.register(CollaborationPlugin({
1537
- serverUrl: 'wss://signal.example.com',
1538
- roomId: 'my-document-123',
1539
- userName: 'Alice',
1540
- userColor: '#6366f1',
1541
- autoConnect: true, // connect on init (default: true)
1542
- transport: 'websocket', // 'websocket' | 'webrtc' | custom transport
1543
- offlineQueue: true, // queue changes while disconnected (default: true)
1544
- awarenessTimeout: 30000, // ms before marking a peer as inactive
1545
- onPeerJoined: (peer) => console.log(`${peer.name} joined`),
1546
- onPeerLeft: (peer) => console.log(`${peer.name} left`),
1547
- onSync: () => console.log('Document synced'),
1548
- onError: (err) => console.error('Collaboration error:', err),
1549
- }));
1778
+ engine.plugins.register(
1779
+ CollaborationPlugin({
1780
+ serverUrl: 'wss://signal.example.com',
1781
+ roomId: 'my-document-123',
1782
+ userName: 'Alice',
1783
+ userColor: '#6366f1',
1784
+ autoConnect: true, // connect on init (default: true)
1785
+ transport: 'websocket', // 'websocket' | 'webrtc' | custom transport
1786
+ offlineQueue: true, // queue changes while disconnected (default: true)
1787
+ awarenessTimeout: 30000, // ms before marking a peer as inactive
1788
+ onPeerJoined: (peer) => console.log(`${peer.name} joined`),
1789
+ onPeerLeft: (peer) => console.log(`${peer.name} left`),
1790
+ onSync: () => console.log('Document synced'),
1791
+ onError: (err) => console.error('Collaboration error:', err),
1792
+ }),
1793
+ );
1550
1794
 
1551
1795
  // Connect / disconnect manually (when autoConnect is false)
1552
1796
  engine.executeCommand('collaborationConnect', {
@@ -1566,14 +1810,14 @@ engine.executeCommand('collaborationGetPeers');
1566
1810
 
1567
1811
  **Events:**
1568
1812
 
1569
- | Event | Payload | When |
1570
- | --- | --- | --- |
1571
- | `collaboration:connected` | `{ roomId, peerId }` | Successfully connected to room |
1572
- | `collaboration:disconnected` | `{ reason }` | Disconnected from room |
1573
- | `collaboration:peer-joined` | `{ peer }` | A new peer joined the room |
1574
- | `collaboration:peer-left` | `{ peer }` | A peer left the room |
1575
- | `collaboration:sync` | `{ documentState }` | Document state synchronized |
1576
- | `collaboration:error` | `{ error, code }` | Connection or sync error |
1813
+ | Event | Payload | When |
1814
+ | ---------------------------- | -------------------- | ------------------------------ |
1815
+ | `collaboration:connected` | `{ roomId, peerId }` | Successfully connected to room |
1816
+ | `collaboration:disconnected` | `{ reason }` | Disconnected from room |
1817
+ | `collaboration:peer-joined` | `{ peer }` | A new peer joined the room |
1818
+ | `collaboration:peer-left` | `{ peer }` | A peer left the room |
1819
+ | `collaboration:sync` | `{ documentState }` | Document state synchronized |
1820
+ | `collaboration:error` | `{ error, code }` | Connection or sync error |
1577
1821
 
1578
1822
  **Custom transport interface:**
1579
1823
 
@@ -1584,15 +1828,24 @@ const myTransport = {
1584
1828
  // handlers.onConnect() — call when connected
1585
1829
  // handlers.onDisconnect(reason) — call when disconnected
1586
1830
  // Return a connection object with send(data) and close() methods
1587
- return { send(data) { /* ... */ }, close() { /* ... */ } };
1831
+ return {
1832
+ send(data) {
1833
+ /* ... */
1834
+ },
1835
+ close() {
1836
+ /* ... */
1837
+ },
1838
+ };
1588
1839
  },
1589
1840
  };
1590
1841
 
1591
- engine.plugins.register(CollaborationPlugin({
1592
- transport: myTransport,
1593
- roomId: 'room-1',
1594
- userName: 'Charlie',
1595
- }));
1842
+ engine.plugins.register(
1843
+ CollaborationPlugin({
1844
+ transport: myTransport,
1845
+ roomId: 'room-1',
1846
+ userName: 'Charlie',
1847
+ }),
1848
+ );
1596
1849
  ```
1597
1850
 
1598
1851
  ## Selection API
@@ -1603,10 +1856,10 @@ Access the selection subsystem via `engine.selection`:
1603
1856
  const sel = engine.selection;
1604
1857
 
1605
1858
  // Read selection state
1606
- sel.isCollapsed(); // true if cursor (no range selected)
1607
- sel.getSelectedText(); // selected plain text
1608
- sel.getSelectedHTML(); // selected HTML fragment
1609
- sel.getBoundingRect(); // DOMRect for positioning floating UI
1859
+ sel.isCollapsed(); // true if cursor (no range selected)
1860
+ sel.getSelectedText(); // selected plain text
1861
+ sel.getSelectedHTML(); // selected HTML fragment
1862
+ sel.getBoundingRect(); // DOMRect for positioning floating UI
1610
1863
 
1611
1864
  // Inspect formatting at cursor
1612
1865
  const formats = sel.getActiveFormats();
@@ -1623,9 +1876,9 @@ const formats = sel.getActiveFormats();
1623
1876
  // }
1624
1877
 
1625
1878
  // Navigate the DOM
1626
- sel.getParentElement(); // nearest element containing cursor
1627
- sel.getParentBlock(); // nearest block element (div, p, li, etc.)
1628
- sel.getClosestElement('a'); // find ancestor by tag name
1879
+ sel.getParentElement(); // nearest element containing cursor
1880
+ sel.getParentBlock(); // nearest block element (div, p, li, etc.)
1881
+ sel.getClosestElement('a'); // find ancestor by tag name
1629
1882
 
1630
1883
  // Manipulate content at cursor
1631
1884
  sel.insertHTML('<strong>injected</strong>');
@@ -1639,8 +1892,8 @@ const bookmark = sel.save();
1639
1892
  sel.restore(bookmark);
1640
1893
 
1641
1894
  // Collapse cursor
1642
- sel.collapse(); // collapse to start
1643
- sel.collapse(true); // collapse to end
1895
+ sel.collapse(); // collapse to start
1896
+ sel.collapse(true); // collapse to end
1644
1897
  ```
1645
1898
 
1646
1899
  ## History (Undo/Redo)
@@ -1648,12 +1901,12 @@ sel.collapse(true); // collapse to end
1648
1901
  Access the history subsystem via `engine.history`:
1649
1902
 
1650
1903
  ```js
1651
- engine.history.undo(); // Undo last change
1652
- engine.history.redo(); // Redo
1653
- engine.history.canUndo(); // true if undo stack is not empty
1654
- engine.history.canRedo(); // true if redo stack is not empty
1655
- engine.history.snapshot(); // Force an immediate snapshot
1656
- engine.history.clear(); // Clear all history
1904
+ engine.history.undo(); // Undo last change
1905
+ engine.history.redo(); // Redo
1906
+ engine.history.canUndo(); // true if undo stack is not empty
1907
+ engine.history.canRedo(); // true if redo stack is not empty
1908
+ engine.history.snapshot(); // Force an immediate snapshot
1909
+ engine.history.clear(); // Clear all history
1657
1910
 
1658
1911
  // Events
1659
1912
  engine.on('history:undo', () => updateUndoButton());
@@ -1665,8 +1918,8 @@ History is configured via engine options:
1665
1918
  ```js
1666
1919
  new EditorEngine(element, {
1667
1920
  history: {
1668
- maxSize: 200, // Keep up to 200 undo states (default: 100)
1669
- debounceMs: 500, // Wait 500ms of inactivity before snapshotting (default: 300)
1921
+ maxSize: 200, // Keep up to 200 undo states (default: 100)
1922
+ debounceMs: 500, // Wait 500ms of inactivity before snapshotting (default: 300)
1670
1923
  },
1671
1924
  });
1672
1925
  ```
@@ -1683,32 +1936,32 @@ engine.keyboard.register('mod+shift+h', 'highlight');
1683
1936
  engine.keyboard.unregister('mod+shift+h');
1684
1937
 
1685
1938
  // Look up shortcuts
1686
- engine.keyboard.getShortcutForCommand('bold'); // 'mod+b'
1687
- engine.keyboard.getShortcutLabel('mod+b'); // '⌘B' on Mac, 'Ctrl+B' on Windows
1939
+ engine.keyboard.getShortcutForCommand('bold'); // 'mod+b'
1940
+ engine.keyboard.getShortcutLabel('mod+b'); // '⌘B' on Mac, 'Ctrl+B' on Windows
1688
1941
  ```
1689
1942
 
1690
1943
  **Shortcut format:** Use `mod` for Ctrl (Windows/Linux) or Cmd (Mac). Combine with `shift`, `alt`, and a lowercase key: `'mod+shift+k'`.
1691
1944
 
1692
1945
  **Default shortcuts:**
1693
1946
 
1694
- | Shortcut | Command |
1695
- | --- | --- |
1696
- | Mod+B | bold |
1697
- | Mod+I | italic |
1698
- | Mod+U | underline |
1699
- | Mod+Shift+X | strikethrough |
1700
- | Mod+, | subscript |
1701
- | Mod+. | superscript |
1702
- | Mod+K | insertLink |
1703
- | Mod+Shift+7 | orderedList |
1704
- | Mod+Shift+8 | unorderedList |
1705
- | Mod+Shift+9 | blockquote |
1706
- | Mod+Shift+C | codeBlock |
1707
- | Mod+F | find |
1708
- | Mod+Shift+U | sourceMode |
1709
- | Mod+Shift+F | fullscreen |
1710
- | Mod+Z | undo |
1711
- | Mod+Shift+Z | redo |
1947
+ | Shortcut | Command |
1948
+ | ----------- | -------------- |
1949
+ | Mod+B | bold |
1950
+ | Mod+I | italic |
1951
+ | Mod+U | underline |
1952
+ | Mod+Shift+X | strikethrough |
1953
+ | Mod+, | subscript |
1954
+ | Mod+. | superscript |
1955
+ | Mod+K | insertLink |
1956
+ | Mod+Shift+7 | orderedList |
1957
+ | Mod+Shift+8 | unorderedList |
1958
+ | Mod+Shift+9 | blockquote |
1959
+ | Mod+Shift+C | codeBlock |
1960
+ | Mod+F | find |
1961
+ | Mod+Shift+U | sourceMode |
1962
+ | Mod+Shift+F | fullscreen |
1963
+ | Mod+Z | undo |
1964
+ | Mod+Shift+Z | redo |
1712
1965
  | Mod+Shift+P | commandPalette |
1713
1966
 
1714
1967
  ## Sanitizer
@@ -1724,23 +1977,36 @@ new EditorEngine(element, {
1724
1977
  sanitize: {
1725
1978
  allowedTags: {
1726
1979
  // tag name → array of allowed attributes
1727
- 'div': ['class', 'id', 'style'],
1728
- 'span': ['class', 'style'],
1729
- 'a': ['href', 'target', 'rel', 'class'],
1730
- 'img': ['src', 'alt', 'width', 'height', 'class'],
1731
- 'mark': ['class'],
1980
+ div: ['class', 'id', 'style'],
1981
+ span: ['class', 'style'],
1982
+ a: ['href', 'target', 'rel', 'class'],
1983
+ img: ['src', 'alt', 'width', 'height', 'class'],
1984
+ mark: ['class'],
1732
1985
  // ... see schema.js for full defaults
1733
1986
  },
1734
1987
  allowedStyles: [
1735
- 'color', 'background-color', 'font-size', 'font-family',
1736
- 'text-align', 'text-decoration', 'font-weight', 'font-style',
1737
- 'margin', 'padding', 'border', 'width', 'height',
1988
+ 'color',
1989
+ 'background-color',
1990
+ 'font-size',
1991
+ 'font-family',
1992
+ 'text-align',
1993
+ 'text-decoration',
1994
+ 'font-weight',
1995
+ 'font-style',
1996
+ 'margin',
1997
+ 'padding',
1998
+ 'border',
1999
+ 'width',
2000
+ 'height',
1738
2001
  // ... see schema.js for full defaults
1739
2002
  ],
1740
2003
  // Restrict which domains can be embedded via iframe
1741
2004
  iframeAllowedDomains: [
1742
- 'www.youtube.com', 'youtube.com', 'www.youtube-nocookie.com',
1743
- 'player.vimeo.com', 'www.dailymotion.com',
2005
+ 'www.youtube.com',
2006
+ 'youtube.com',
2007
+ 'www.youtube-nocookie.com',
2008
+ 'player.vimeo.com',
2009
+ 'www.dailymotion.com',
1744
2010
  // Add your own domains here
1745
2011
  ],
1746
2012
  },
@@ -1765,7 +2031,11 @@ new EditorEngine(element, {
1765
2031
  ### HTML Helpers
1766
2032
 
1767
2033
  ```js
1768
- import { escapeHTML, escapeHTMLAttr, insertPlainText } from '@remyxjs/core';
2034
+ import {
2035
+ escapeHTML,
2036
+ escapeHTMLAttr,
2037
+ insertPlainText,
2038
+ } from '@remyxjs/core';
1769
2039
 
1770
2040
  // Escape HTML entities for safe insertion
1771
2041
  escapeHTML('<script>alert("xss")</script>');
@@ -1797,7 +2067,11 @@ Supports GitHub Flavored Markdown (GFM): headings, bold, italic, links, images,
1797
2067
  ### Document Conversion
1798
2068
 
1799
2069
  ```js
1800
- import { convertDocument, isImportableFile, getSupportedExtensions } from '@remyxjs/core';
2070
+ import {
2071
+ convertDocument,
2072
+ isImportableFile,
2073
+ getSupportedExtensions,
2074
+ } from '@remyxjs/core';
1801
2075
 
1802
2076
  // Check if a file can be imported
1803
2077
  if (isImportableFile(file)) {
@@ -1817,7 +2091,11 @@ PDF and DOCX converters use dynamic imports — the heavy libraries are only loa
1817
2091
  ### Export
1818
2092
 
1819
2093
  ```js
1820
- import { exportAsMarkdown, exportAsPDF, exportAsDocx } from '@remyxjs/core';
2094
+ import {
2095
+ exportAsMarkdown,
2096
+ exportAsPDF,
2097
+ exportAsDocx,
2098
+ } from '@remyxjs/core';
1821
2099
 
1822
2100
  // Export as Markdown file download
1823
2101
  exportAsMarkdown(engine.getHTML(), 'my-document');
@@ -1848,7 +2126,11 @@ The paste cleaner auto-detects the source application and applies format-specifi
1848
2126
  ### Font Management
1849
2127
 
1850
2128
  ```js
1851
- import { loadGoogleFonts, addFonts, removeFonts } from '@remyxjs/core';
2129
+ import {
2130
+ loadGoogleFonts,
2131
+ addFonts,
2132
+ removeFonts,
2133
+ } from '@remyxjs/core';
1852
2134
 
1853
2135
  // Load Google Fonts (injects <link> into <head>)
1854
2136
  loadGoogleFonts(['Roboto', 'Open Sans', 'Merriweather']);
@@ -1856,7 +2138,7 @@ loadGoogleFonts(['Roboto', 'Open Sans', 'Merriweather']);
1856
2138
  // With Subresource Integrity (SRI) hash for security
1857
2139
  loadGoogleFonts(['Roboto'], {
1858
2140
  integrity: 'sha384-abc123...', // pre-computed hash of the stylesheet
1859
- crossOrigin: 'anonymous', // required for SRI (default)
2141
+ crossOrigin: 'anonymous', // required for SRI (default)
1860
2142
  });
1861
2143
 
1862
2144
  // Modify a font list
@@ -1871,14 +2153,21 @@ const trimmed = removeFonts(updated, ['Times New Roman']);
1871
2153
  ### DOM Utilities
1872
2154
 
1873
2155
  ```js
1874
- import { closestBlock, closestTag, wrapInTag, unwrapTag, generateId, isBlockEmpty } from '@remyxjs/core';
2156
+ import {
2157
+ closestBlock,
2158
+ closestTag,
2159
+ wrapInTag,
2160
+ unwrapTag,
2161
+ generateId,
2162
+ isBlockEmpty,
2163
+ } from '@remyxjs/core';
1875
2164
 
1876
- closestBlock(node, editorElement); // Nearest block ancestor
1877
- closestTag(node, 'a', editorElement); // Nearest <a> ancestor
2165
+ closestBlock(node, editorElement); // Nearest block ancestor
2166
+ closestTag(node, 'a', editorElement); // Nearest <a> ancestor
1878
2167
  wrapInTag(range, 'mark', { class: 'hi' }); // Wrap range in element
1879
- unwrapTag(markElement); // Unwrap, keep children
1880
- generateId(); // 'rmx-a1b2c3d4'
1881
- isBlockEmpty(paragraphElement); // true if no meaningful content
2168
+ unwrapTag(markElement); // Unwrap, keep children
2169
+ generateId(); // 'rmx-a1b2c3d4'
2170
+ isBlockEmpty(paragraphElement); // true if no meaningful content
1882
2171
  ```
1883
2172
 
1884
2173
  ### HTML Formatting
@@ -1898,8 +2187,8 @@ const pretty = formatHTML('<div><p>Hello</p><p>World</p></div>');
1898
2187
  ```js
1899
2188
  import { isMac, getModKey } from '@remyxjs/core';
1900
2189
 
1901
- isMac(); // true on macOS
1902
- getModKey(); // 'Cmd' on Mac, 'Ctrl' on Windows/Linux
2190
+ isMac(); // true on macOS
2191
+ getModKey(); // 'Cmd' on Mac, 'Ctrl' on Windows/Linux
1903
2192
  ```
1904
2193
 
1905
2194
  ## Theming
@@ -1908,23 +2197,23 @@ getModKey(); // 'Cmd' on Mac, 'Ctrl' on Windows/Linux
1908
2197
 
1909
2198
  All styles use CSS custom properties with the `--rmx-` prefix. Override them in your CSS or use `createTheme` to generate overrides programmatically.
1910
2199
 
1911
- | Variable | Default (Light) | Description |
1912
- | --- | --- | --- |
1913
- | `--rmx-bg` | `#ffffff` | Editor background |
1914
- | `--rmx-text` | `#1a1a1a` | Text color |
1915
- | `--rmx-border` | `#e0e0e0` | Border color |
1916
- | `--rmx-toolbar-bg` | `#f8f9fa` | Toolbar background |
1917
- | `--rmx-toolbar-text` | `#374151` | Toolbar text color |
1918
- | `--rmx-toolbar-hover` | `#e9ecef` | Toolbar button hover |
1919
- | `--rmx-toolbar-active` | `#dee2e6` | Toolbar button active |
1920
- | `--rmx-primary` | `#3b82f6` | Primary accent color |
1921
- | `--rmx-primary-hover` | `#2563eb` | Primary hover |
1922
- | `--rmx-primary-text` | `#ffffff` | Text on primary |
1923
- | `--rmx-shadow` | `0 1px 3px ...` | Box shadow |
1924
- | `--rmx-radius` | `6px` | Border radius |
1925
- | `--rmx-font-family` | system-ui | Editor font |
1926
- | `--rmx-font-size` | `16px` | Editor font size |
1927
- | `--rmx-line-height` | `1.6` | Line height |
2200
+ | Variable | Default (Light) | Description |
2201
+ | ---------------------- | --------------- | --------------------- |
2202
+ | `--rmx-bg` | `#ffffff` | Editor background |
2203
+ | `--rmx-text` | `#1a1a1a` | Text color |
2204
+ | `--rmx-border` | `#e0e0e0` | Border color |
2205
+ | `--rmx-toolbar-bg` | `#f8f9fa` | Toolbar background |
2206
+ | `--rmx-toolbar-text` | `#374151` | Toolbar text color |
2207
+ | `--rmx-toolbar-hover` | `#e9ecef` | Toolbar button hover |
2208
+ | `--rmx-toolbar-active` | `#dee2e6` | Toolbar button active |
2209
+ | `--rmx-primary` | `#3b82f6` | Primary accent color |
2210
+ | `--rmx-primary-hover` | `#2563eb` | Primary hover |
2211
+ | `--rmx-primary-text` | `#ffffff` | Text on primary |
2212
+ | `--rmx-shadow` | `0 1px 3px ...` | Box shadow |
2213
+ | `--rmx-radius` | `6px` | Border radius |
2214
+ | `--rmx-font-family` | system-ui | Editor font |
2215
+ | `--rmx-font-size` | `16px` | Editor font size |
2216
+ | `--rmx-line-height` | `1.6` | Line height |
1928
2217
 
1929
2218
  See `packages/remyx-core/src/themes/variables.css` for the full list of 40+ variables.
1930
2219
 
@@ -1932,19 +2221,21 @@ See `packages/remyx-core/src/themes/variables.css` for the full list of 40+ vari
1932
2221
 
1933
2222
  Six built-in themes are available via CSS classes (applied automatically by `@remyxjs/react`'s `theme` prop, or manually via `.rmx-theme-{name}`):
1934
2223
 
1935
- | Theme | Class | Description |
1936
- | --- | --- | --- |
1937
- | `light` | `.rmx-theme-light` | Clean white (default) |
1938
- | `dark` | `.rmx-theme-dark` | Neutral dark |
1939
- | `ocean` | `.rmx-theme-ocean` | Deep blue palette |
1940
- | `forest` | `.rmx-theme-forest` | Green earth-tone palette |
2224
+ | Theme | Class | Description |
2225
+ | -------- | ------------------- | ------------------------- |
2226
+ | `light` | `.rmx-theme-light` | Clean white (default) |
2227
+ | `dark` | `.rmx-theme-dark` | Neutral dark |
2228
+ | `ocean` | `.rmx-theme-ocean` | Deep blue palette |
2229
+ | `forest` | `.rmx-theme-forest` | Green earth-tone palette |
1941
2230
  | `sunset` | `.rmx-theme-sunset` | Warm orange/amber palette |
1942
- | `rose` | `.rmx-theme-rose` | Soft pink palette |
2231
+ | `rose` | `.rmx-theme-rose` | Soft pink palette |
1943
2232
 
1944
2233
  For vanilla JS usage, add the class to the editor wrapper:
1945
2234
 
1946
2235
  ```js
1947
- document.querySelector('.rmx-editor').classList.add('rmx-theme-ocean');
2236
+ document
2237
+ .querySelector('.rmx-editor')
2238
+ .classList.add('rmx-theme-ocean');
1948
2239
  ```
1949
2240
 
1950
2241
  The `THEME_PRESETS` export is still available for programmatic overrides via `customTheme`:
@@ -1952,7 +2243,10 @@ The `THEME_PRESETS` export is still available for programmatic overrides via `cu
1952
2243
  ```js
1953
2244
  import { THEME_PRESETS, createTheme } from '@remyxjs/core';
1954
2245
  // Override a single variable on top of ocean
1955
- const modified = { ...THEME_PRESETS.ocean, ...createTheme({ primary: '#ff6b6b' }) };
2246
+ const modified = {
2247
+ ...THEME_PRESETS.ocean,
2248
+ ...createTheme({ primary: '#ff6b6b' }),
2249
+ };
1956
2250
  ```
1957
2251
 
1958
2252
  ### Custom Themes
@@ -1994,38 +2288,47 @@ const theme = createTheme({
1994
2288
  import { TOOLBAR_PRESETS } from '@remyxjs/core';
1995
2289
 
1996
2290
  // Available presets:
1997
- TOOLBAR_PRESETS.full; // All available items including plugin commands
1998
- TOOLBAR_PRESETS.rich; // All features with plugin toolbar items (callout, math, toc, bookmark, merge tag)
1999
- TOOLBAR_PRESETS.standard; // Common editing features without plugins
2000
- TOOLBAR_PRESETS.minimal; // Basic text formatting
2001
- TOOLBAR_PRESETS.bare; // Bold, italic, underline only
2291
+ TOOLBAR_PRESETS.full; // All available items including plugin commands
2292
+ TOOLBAR_PRESETS.rich; // All features with plugin toolbar items (callout, math, toc, bookmark, merge tag)
2293
+ TOOLBAR_PRESETS.standard; // Common editing features without plugins
2294
+ TOOLBAR_PRESETS.minimal; // Basic text formatting
2295
+ TOOLBAR_PRESETS.bare; // Bold, italic, underline only
2002
2296
  ```
2003
2297
 
2004
2298
  Each preset is an array of arrays, where each inner array is a toolbar group (rendered with a separator between groups):
2005
2299
 
2006
2300
  ```js
2007
2301
  [
2008
- ['bold', 'italic', 'underline'], // Group 1
2302
+ ['bold', 'italic', 'underline'], // Group 1
2009
2303
  ['heading', 'orderedList', 'unorderedList'], // Group 2
2010
- ['link', 'image'], // Group 3
2011
- ]
2304
+ ['link', 'image'], // Group 3
2305
+ ];
2012
2306
  ```
2013
2307
 
2014
2308
  ### Custom Toolbars
2015
2309
 
2016
2310
  ```js
2017
- import { removeToolbarItems, addToolbarItems, createToolbar, TOOLBAR_PRESETS } from '@remyxjs/core';
2311
+ import {
2312
+ removeToolbarItems,
2313
+ addToolbarItems,
2314
+ createToolbar,
2315
+ TOOLBAR_PRESETS,
2316
+ } from '@remyxjs/core';
2018
2317
 
2019
2318
  // Start from a preset and customize
2020
2319
  let toolbar = TOOLBAR_PRESETS.standard;
2021
2320
 
2022
2321
  // Remove items
2023
- toolbar = removeToolbarItems(toolbar, ['strikethrough', 'subscript', 'superscript']);
2322
+ toolbar = removeToolbarItems(toolbar, [
2323
+ 'strikethrough',
2324
+ 'subscript',
2325
+ 'superscript',
2326
+ ]);
2024
2327
 
2025
2328
  // Add items to a specific group or position
2026
2329
  toolbar = addToolbarItems(toolbar, ['codeBlock', 'blockquote'], {
2027
- position: 'after', // 'before' | 'after' | 'start' | 'end'
2028
- relativeTo: 'link', // existing item to position relative to
2330
+ position: 'after', // 'before' | 'after' | 'start' | 'end'
2331
+ relativeTo: 'link', // existing item to position relative to
2029
2332
  });
2030
2333
 
2031
2334
  // Or build from scratch
@@ -2043,7 +2346,10 @@ const myToolbar = createToolbar([
2043
2346
  Style individual toolbar items differently:
2044
2347
 
2045
2348
  ```js
2046
- import { createToolbarItemTheme, resolveToolbarItemStyle } from '@remyxjs/core';
2349
+ import {
2350
+ createToolbarItemTheme,
2351
+ resolveToolbarItemStyle,
2352
+ } from '@remyxjs/core';
2047
2353
 
2048
2354
  const itemTheme = createToolbarItemTheme({
2049
2355
  bold: {
@@ -2094,13 +2400,17 @@ Load editor configuration from an external JSON or YAML URL:
2094
2400
  import { loadConfig } from '@remyxjs/core';
2095
2401
 
2096
2402
  // JSON config
2097
- const config = await loadConfig('https://cdn.example.com/editor-config.json');
2403
+ const config = await loadConfig(
2404
+ 'https://cdn.example.com/editor-config.json',
2405
+ );
2098
2406
 
2099
2407
  // YAML config (detected by .yml/.yaml extension)
2100
2408
  const config = await loadConfig('/configs/editor.yaml');
2101
2409
 
2102
2410
  // Environment-based merging
2103
- const config = await loadConfig('/config.json', { env: 'production' });
2411
+ const config = await loadConfig('/config.json', {
2412
+ env: 'production',
2413
+ });
2104
2414
 
2105
2415
  // Custom headers (e.g., authenticated endpoints)
2106
2416
  const config = await loadConfig('/api/editor-config', {
@@ -2109,7 +2419,9 @@ const config = await loadConfig('/api/editor-config', {
2109
2419
 
2110
2420
  // Cancellation
2111
2421
  const controller = new AbortController();
2112
- const config = await loadConfig('/config.json', { signal: controller.signal });
2422
+ const config = await loadConfig('/config.json', {
2423
+ signal: controller.signal,
2424
+ });
2113
2425
  ```
2114
2426
 
2115
2427
  **Config file format with environment overrides:**
@@ -2148,12 +2460,12 @@ autosave:
2148
2460
 
2149
2461
  The built-in YAML parser handles simple key-value pairs, nested objects, inline arrays, booleans, numbers, null, and quoted strings. For complex YAML with anchors, multi-line blocks, or flow mappings, use the `js-yaml` library and pass the result to `defineConfig()`.
2150
2462
 
2151
- | Parameter | Type | Description |
2152
- | --- | --- | --- |
2153
- | `url` | `string` | URL or path to JSON/YAML config file |
2154
- | `options.env` | `string` | Environment name for config merging |
2155
- | `options.headers` | `Record<string, string>` | Custom fetch headers |
2156
- | `options.signal` | `AbortSignal` | Cancellation signal |
2463
+ | Parameter | Type | Description |
2464
+ | ----------------- | ------------------------ | ------------------------------------ |
2465
+ | `url` | `string` | URL or path to JSON/YAML config file |
2466
+ | `options.env` | `string` | Environment name for config merging |
2467
+ | `options.headers` | `Record<string, string>` | Custom fetch headers |
2468
+ | `options.signal` | `AbortSignal` | Cancellation signal |
2157
2469
 
2158
2470
  ## Multi-Editor Support
2159
2471
 
@@ -2201,19 +2513,19 @@ console.log(EditorBus.getEditorIds()); // ['source', 'preview']
2201
2513
  EditorBus.unregister('source');
2202
2514
  ```
2203
2515
 
2204
- | Method | Description |
2205
- | --- | --- |
2206
- | `register(id, engine)` | Register an engine by ID |
2207
- | `unregister(id)` | Remove a registered engine |
2208
- | `getEditor(id)` | Get an engine by ID |
2209
- | `getEditorIds()` | List all registered IDs |
2210
- | `editorCount` | Number of registered editors |
2211
- | `on(event, handler)` | Subscribe to a global event |
2212
- | `off(event, handler)` | Unsubscribe |
2213
- | `once(event, handler)` | Subscribe once |
2214
- | `emit(event, data)` | Emit to global subscribers |
2215
- | `broadcast(event, data, opts)` | Emit into each editor's local `eventBus` |
2216
- | `reset()` | Clear all listeners and registry (for tests) |
2516
+ | Method | Description |
2517
+ | ------------------------------ | -------------------------------------------- |
2518
+ | `register(id, engine)` | Register an engine by ID |
2519
+ | `unregister(id)` | Remove a registered engine |
2520
+ | `getEditor(id)` | Get an engine by ID |
2521
+ | `getEditorIds()` | List all registered IDs |
2522
+ | `editorCount` | Number of registered editors |
2523
+ | `on(event, handler)` | Subscribe to a global event |
2524
+ | `off(event, handler)` | Unsubscribe |
2525
+ | `once(event, handler)` | Subscribe once |
2526
+ | `emit(event, data)` | Emit to global subscribers |
2527
+ | `broadcast(event, data, opts)` | Emit into each editor's local `eventBus` |
2528
+ | `reset()` | Clear all listeners and registry (for tests) |
2217
2529
 
2218
2530
  ### SharedResources
2219
2531
 
@@ -2223,7 +2535,8 @@ A lazily-initialized singleton that provides deeply-frozen copies of large, immu
2223
2535
  import { SharedResources } from '@remyxjs/core';
2224
2536
 
2225
2537
  // Shared, frozen sanitizer schema
2226
- const { allowedTags, allowedStyles } = SharedResources.sanitizerSchema;
2538
+ const { allowedTags, allowedStyles } =
2539
+ SharedResources.sanitizerSchema;
2227
2540
 
2228
2541
  // Shared toolbar presets
2229
2542
  const fullToolbar = SharedResources.toolbarPresets.full;
@@ -2238,9 +2551,12 @@ const keybindings = SharedResources.keybindings;
2238
2551
  const tooltips = SharedResources.commands.tooltips;
2239
2552
 
2240
2553
  // Register a custom icon once, available to all editors
2241
- SharedResources.registerIcon('myAction', '<svg viewBox="0 0 24 24">...</svg>');
2242
- SharedResources.getIcon('myAction'); // '<svg ...>'
2243
- SharedResources.getIconNames(); // ['myAction']
2554
+ SharedResources.registerIcon(
2555
+ 'myAction',
2556
+ '<svg viewBox="0 0 24 24">...</svg>',
2557
+ );
2558
+ SharedResources.getIcon('myAction'); // '<svg ...>'
2559
+ SharedResources.getIconNames(); // ['myAction']
2244
2560
  SharedResources.unregisterIcon('myAction');
2245
2561
 
2246
2562
  // Stats
@@ -2252,120 +2568,120 @@ SharedResources.stats; // { registeredIcons: 0, frozenSchemas: true }
2252
2568
  ```js
2253
2569
  import {
2254
2570
  // Toolbar & menu defaults
2255
- DEFAULT_TOOLBAR, // Full toolbar configuration
2256
- DEFAULT_MENU_BAR, // Menu bar structure
2571
+ DEFAULT_TOOLBAR, // Full toolbar configuration
2572
+ DEFAULT_MENU_BAR, // Menu bar structure
2257
2573
 
2258
2574
  // Font & color defaults
2259
- DEFAULT_FONTS, // Array of font family names
2260
- DEFAULT_FONT_SIZES, // Array of size strings
2261
- DEFAULT_COLORS, // Color palette array
2575
+ DEFAULT_FONTS, // Array of font family names
2576
+ DEFAULT_FONT_SIZES, // Array of size strings
2577
+ DEFAULT_COLORS, // Color palette array
2262
2578
 
2263
2579
  // Keyboard
2264
- DEFAULT_KEYBINDINGS, // Map: command name → shortcut string
2580
+ DEFAULT_KEYBINDINGS, // Map: command name → shortcut string
2265
2581
 
2266
2582
  // Heading options
2267
- HEADING_OPTIONS, // H1-H6 + paragraph
2583
+ HEADING_OPTIONS, // H1-H6 + paragraph
2268
2584
 
2269
2585
  // Security schema
2270
- ALLOWED_TAGS, // Tag → allowed attributes map
2271
- ALLOWED_STYLES, // Allowed CSS property names
2586
+ ALLOWED_TAGS, // Tag → allowed attributes map
2587
+ ALLOWED_STYLES, // Allowed CSS property names
2272
2588
 
2273
2589
  // Command metadata
2274
- BUTTON_COMMANDS, // Set of commands rendered as buttons
2275
- TOOLTIP_MAP, // Command → tooltip text
2276
- SHORTCUT_MAP, // Command → display shortcut
2277
- MODAL_COMMANDS, // Commands that open modals
2278
- getShortcutLabel, // (command) => platform-aware label string
2590
+ BUTTON_COMMANDS, // Set of commands rendered as buttons
2591
+ TOOLTIP_MAP, // Command → tooltip text
2592
+ SHORTCUT_MAP, // Command → display shortcut
2593
+ MODAL_COMMANDS, // Commands that open modals
2594
+ getShortcutLabel, // (command) => platform-aware label string
2279
2595
  getCommandActiveState, // (command, selectionState, engine) => boolean
2280
2596
 
2281
2597
  // Command palette
2282
- SLASH_COMMAND_ITEMS, // Default catalog of command palette items
2283
- filterSlashItems, // (items, query, options?) => filtered items (pinRecent: true by default)
2284
- getRecentCommands, // () => string[] — last 5 executed command IDs
2285
- recordRecentCommand, // (id) => void — record a command execution
2286
- clearRecentCommands, // () => void — clear recent history
2598
+ SLASH_COMMAND_ITEMS, // Default catalog of command palette items
2599
+ filterSlashItems, // (items, query, options?) => filtered items (pinRecent: true by default)
2600
+ getRecentCommands, // () => string[] — last 5 executed command IDs
2601
+ recordRecentCommand, // (id) => void — record a command execution
2602
+ clearRecentCommands, // () => void — clear recent history
2287
2603
  registerCommandItems, // (items) => void — add custom items to the palette
2288
2604
  unregisterCommandItem, // (id) => boolean — remove a custom item
2289
2605
  getCustomCommandItems, // () => SlashCommandItem[] — get all custom items
2290
2606
 
2291
2607
  // Comments & Annotations
2292
- CommentsPlugin, // Inline comment threads plugin
2293
- parseMentions, // (text) => string[] — extract @mentions from text
2608
+ CommentsPlugin, // Inline comment threads plugin
2609
+ parseMentions, // (text) => string[] — extract @mentions from text
2294
2610
 
2295
2611
  // Callouts & Alerts
2296
- CalloutPlugin, // Styled callout blocks plugin
2297
- registerCalloutType, // (typeDef) => void — register custom callout type
2298
- unregisterCalloutType, // (type) => boolean — remove a callout type
2299
- getCalloutTypes, // () => CalloutType[] — all registered types
2300
- getCalloutType, // (type) => CalloutType — get a type definition
2301
- parseGFMAlert, // (text) => { type, body } — parse GFM alert syntax
2612
+ CalloutPlugin, // Styled callout blocks plugin
2613
+ registerCalloutType, // (typeDef) => void — register custom callout type
2614
+ unregisterCalloutType, // (type) => boolean — remove a callout type
2615
+ getCalloutTypes, // () => CalloutType[] — all registered types
2616
+ getCalloutType, // (type) => CalloutType — get a type definition
2617
+ parseGFMAlert, // (text) => { type, body } — parse GFM alert syntax
2302
2618
 
2303
2619
  // Advanced Link Management
2304
- LinkPlugin, // Link previews, broken links, auto-link, bookmarks
2305
- detectLinks, // (text) => Array<{ type, value, index }>
2306
- slugify, // (text) => URL-safe slug string
2620
+ LinkPlugin, // Link previews, broken links, auto-link, bookmarks
2621
+ detectLinks, // (text) => Array<{ type, value, index }>
2622
+ slugify, // (text) => URL-safe slug string
2307
2623
 
2308
2624
  // Template System
2309
- TemplatePlugin, // Merge tags, conditionals, loops, preview, library
2310
- renderTemplate, // (template, data) => rendered string
2311
- extractTags, // (template) => string[] — unique tag names
2312
- registerTemplate, // Add custom template to library
2313
- unregisterTemplate, // Remove template from library
2314
- getTemplateLibrary, // () => all templates
2315
- getTemplate, // (id) => template by ID
2625
+ TemplatePlugin, // Merge tags, conditionals, loops, preview, library
2626
+ renderTemplate, // (template, data) => rendered string
2627
+ extractTags, // (template) => string[] — unique tag names
2628
+ registerTemplate, // Add custom template to library
2629
+ unregisterTemplate, // Remove template from library
2630
+ getTemplateLibrary, // () => all templates
2631
+ getTemplate, // (id) => template by ID
2316
2632
 
2317
2633
  // Keyboard-First Editing
2318
- KeyboardPlugin, // Vim/Emacs modes, auto-pair, multi-cursor
2319
- getHeadings, // (element) => heading list with level/text/element
2320
- selectNextOccurrence, // (element) => add next match to selection
2634
+ KeyboardPlugin, // Vim/Emacs modes, auto-pair, multi-cursor
2635
+ getHeadings, // (element) => heading list with level/text/element
2636
+ selectNextOccurrence, // (element) => add next match to selection
2321
2637
 
2322
2638
  // Drag & Drop
2323
- DragDropPlugin, // Drop zones, cross-editor, file drops, reorder
2639
+ DragDropPlugin, // Drop zones, cross-editor, file drops, reorder
2324
2640
 
2325
2641
  // Math & Equations
2326
- MathPlugin, // LaTeX math rendering, symbol palette, numbering
2327
- getSymbolPalette, // () => categorized symbol array
2328
- parseMathExpressions, // (text) => Array<{ type, src, index }>
2329
- latexToMathML, // (latex) => MathML string
2642
+ MathPlugin, // LaTeX math rendering, symbol palette, numbering
2643
+ getSymbolPalette, // () => categorized symbol array
2644
+ parseMathExpressions, // (text) => Array<{ type, src, index }>
2645
+ latexToMathML, // (latex) => MathML string
2330
2646
 
2331
2647
  // Table of Contents
2332
- TocPlugin, // Auto-generated TOC, outline, heading validation
2333
- buildOutline, // (element) => hierarchical outline tree
2334
- flattenOutline, // (outline) => flat item list
2335
- renderTocHTML, // (outline) => HTML nav string
2336
- validateHeadingHierarchy, // (flatItems) => warnings array
2648
+ TocPlugin, // Auto-generated TOC, outline, heading validation
2649
+ buildOutline, // (element) => hierarchical outline tree
2650
+ flattenOutline, // (outline) => flat item list
2651
+ renderTocHTML, // (outline) => HTML nav string
2652
+ validateHeadingHierarchy, // (flatItems) => warnings array
2337
2653
 
2338
2654
  // Content Analytics
2339
- AnalyticsPlugin, // Readability, reading time, SEO, goals
2340
- analyzeContent, // (text, options) => comprehensive stats
2341
- countSyllables, // (word) => number
2342
- splitSentences, // (text) => string[]
2343
- fleschKincaid, // (stats) => grade level
2344
- fleschReadingEase, // (stats) => 0-100 score
2345
- gunningFog, // (stats) => fog index
2346
- colemanLiau, // (stats) => index
2347
- vocabularyLevel, // (gradeLevel) => 'basic'|'intermediate'|'advanced'
2348
- keywordDensity, // (text, keyword) => { count, density, positions }
2349
- seoAnalysis, // (text, element, keyword) => { hints, ... }
2655
+ AnalyticsPlugin, // Readability, reading time, SEO, goals
2656
+ analyzeContent, // (text, options) => comprehensive stats
2657
+ countSyllables, // (word) => number
2658
+ splitSentences, // (text) => string[]
2659
+ fleschKincaid, // (stats) => grade level
2660
+ fleschReadingEase, // (stats) => 0-100 score
2661
+ gunningFog, // (stats) => fog index
2662
+ colemanLiau, // (stats) => index
2663
+ vocabularyLevel, // (gradeLevel) => 'basic'|'intermediate'|'advanced'
2664
+ keywordDensity, // (text, keyword) => { count, density, positions }
2665
+ seoAnalysis, // (text, element, keyword) => { hints, ... }
2350
2666
 
2351
2667
  // Spelling & Grammar
2352
- SpellcheckPlugin, // Spellcheck + grammar checking with inline underlines
2353
- analyzeGrammar, // (text, options) => issues array
2354
- summarizeIssues, // (issues) => { total, grammar, style, byRule }
2355
- detectPassiveVoice, // (text) => issues
2356
- detectWordiness, // (text) => issues
2357
- detectCliches, // (text) => issues
2358
- detectPunctuationIssues, // (text) => issues
2359
- STYLE_PRESETS, // { formal, casual, technical, academic }
2668
+ SpellcheckPlugin, // Spellcheck + grammar checking with inline underlines
2669
+ analyzeGrammar, // (text, options) => issues array
2670
+ summarizeIssues, // (issues) => { total, grammar, style, byRule }
2671
+ detectPassiveVoice, // (text) => issues
2672
+ detectWordiness, // (text) => issues
2673
+ detectCliches, // (text) => issues
2674
+ detectPunctuationIssues, // (text) => issues
2675
+ STYLE_PRESETS, // { formal, casual, technical, academic }
2360
2676
 
2361
2677
  // Real-time Collaboration
2362
- CollaborationPlugin, // CRDT co-editing, live cursors, presence, transport
2678
+ CollaborationPlugin, // CRDT co-editing, live cursors, presence, transport
2363
2679
 
2364
2680
  // Plugin registry
2365
- registerPluginInRegistry, // Register a plugin for discovery
2681
+ registerPluginInRegistry, // Register a plugin for discovery
2366
2682
  unregisterPluginFromRegistry, // Remove from registry
2367
- listRegisteredPlugins, // () => PluginRegistryEntry[] — all registered
2368
- searchPluginRegistry, // (query) => PluginRegistryEntry[] — search by name/desc/tags
2683
+ listRegisteredPlugins, // () => PluginRegistryEntry[] — all registered
2684
+ searchPluginRegistry, // (query) => PluginRegistryEntry[] — search by name/desc/tags
2369
2685
  } from '@remyxjs/core';
2370
2686
  ```
2371
2687
 
@@ -2375,7 +2691,11 @@ import {
2375
2691
 
2376
2692
  ```js
2377
2693
  // Minimal — only the engine and the commands you use
2378
- import { EditorEngine, registerFormattingCommands, registerListCommands } from '@remyxjs/core';
2694
+ import {
2695
+ EditorEngine,
2696
+ registerFormattingCommands,
2697
+ registerListCommands,
2698
+ } from '@remyxjs/core';
2379
2699
  ```
2380
2700
 
2381
2701
  ```js
@@ -2426,7 +2746,11 @@ When creating a wrapper for a new framework, your package should:
2426
2746
 
2427
2747
  ```js
2428
2748
  // useRemyxEditor.js
2429
- import { EditorEngine, registerFormattingCommands, registerListCommands } from '@remyxjs/core';
2749
+ import {
2750
+ EditorEngine,
2751
+ registerFormattingCommands,
2752
+ registerListCommands,
2753
+ } from '@remyxjs/core';
2430
2754
  import { ref, onMounted, onUnmounted } from 'vue';
2431
2755
 
2432
2756
  export function useRemyxEditor(elementRef, options = {}) {