@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.
- package/README.md +902 -578
- package/dist/convertCsv-DRxJY6iq.js +2 -0
- package/dist/{convertCsv-CKzZjzLJ.js.map → convertCsv-DRxJY6iq.js.map} +1 -1
- package/dist/{convertCsv-B8RVtdcs.cjs → convertCsv-_-qbSNir.cjs} +2 -2
- package/dist/{convertCsv-B8RVtdcs.cjs.map → convertCsv-_-qbSNir.cjs.map} +1 -1
- package/dist/convertDocx-D-V0dfFd.js +2 -0
- package/dist/{convertDocx-Dmx88twM.js.map → convertDocx-D-V0dfFd.js.map} +1 -1
- package/dist/{convertDocx-4q89XLLv.cjs → convertDocx-Dpd5BG-G.cjs} +2 -2
- package/dist/{convertDocx-4q89XLLv.cjs.map → convertDocx-Dpd5BG-G.cjs.map} +1 -1
- package/dist/{convertHtml-DbHrdrD3.cjs → convertHtml-DmfodLsz.cjs} +2 -2
- package/dist/{convertHtml-DbHrdrD3.cjs.map → convertHtml-DmfodLsz.cjs.map} +1 -1
- package/dist/convertHtml-gycStuZn.js +2 -0
- package/dist/{convertHtml-CtYVhiTh.js.map → convertHtml-gycStuZn.js.map} +1 -1
- package/dist/convertMarkdown-1BmsjWXP.js +2 -0
- package/dist/{convertMarkdown-Di239Gtn.js.map → convertMarkdown-1BmsjWXP.js.map} +1 -1
- package/dist/{convertMarkdown-eJ9Nkoid.cjs → convertMarkdown-CelFJJT9.cjs} +2 -2
- package/dist/{convertMarkdown-eJ9Nkoid.cjs.map → convertMarkdown-CelFJJT9.cjs.map} +1 -1
- package/dist/convertPdf-D4SNUyBk.js +2 -0
- package/dist/{convertPdf-CFA1eNNH.js.map → convertPdf-D4SNUyBk.js.map} +1 -1
- package/dist/{convertPdf-CSLmTrB8.cjs → convertPdf-DSZy--TD.cjs} +2 -2
- package/dist/{convertPdf-CSLmTrB8.cjs.map → convertPdf-DSZy--TD.cjs.map} +1 -1
- package/dist/{convertRtf-BfiBLMig.cjs → convertRtf-DK-4c2fe.cjs} +2 -2
- package/dist/{convertRtf-BfiBLMig.cjs.map → convertRtf-DK-4c2fe.cjs.map} +1 -1
- package/dist/convertRtf-JWu514-h.js +2 -0
- package/dist/{convertRtf-08CoScGD.js.map → convertRtf-JWu514-h.js.map} +1 -1
- package/dist/{convertText-BpgzHRuh.cjs → convertText-BHUD1tLm.cjs} +2 -2
- package/dist/{convertText-BpgzHRuh.cjs.map → convertText-BHUD1tLm.cjs.map} +1 -1
- package/dist/convertText-DCGItleK.js +2 -0
- package/dist/{convertText-sa7PxKTe.js.map → convertText-DCGItleK.js.map} +1 -1
- package/dist/index-C3_cH6Zy.cjs +2 -0
- package/dist/index-C3_cH6Zy.cjs.map +1 -0
- package/dist/index-Cv62E14a.js +2 -0
- package/dist/index-Cv62E14a.js.map +1 -0
- package/dist/remyx-core.cjs +1 -1
- package/dist/remyx-core.css +1 -1
- package/dist/remyx-core.js +1 -1
- package/package.json +1 -1
- package/dist/convertCsv-CKzZjzLJ.js +0 -2
- package/dist/convertDocx-Dmx88twM.js +0 -2
- package/dist/convertHtml-CtYVhiTh.js +0 -2
- package/dist/convertMarkdown-Di239Gtn.js +0 -2
- package/dist/convertPdf-CFA1eNNH.js +0 -2
- package/dist/convertRtf-08CoScGD.js +0 -2
- package/dist/convertText-sa7PxKTe.js +0 -2
- package/dist/index-4syk9eEO.js +0 -2
- package/dist/index-4syk9eEO.js.map +0 -1
- package/dist/index-B25zSs0W.js +0 -2
- package/dist/index-B25zSs0W.js.map +0 -1
- package/dist/index-B7VT6ZLa.cjs +0 -2
- package/dist/index-B7VT6ZLa.cjs.map +0 -1
- package/dist/index-BCpytFKJ.js +0 -2
- package/dist/index-BCpytFKJ.js.map +0 -1
- package/dist/index-BNKANY5i.cjs +0 -2
- package/dist/index-BNKANY5i.cjs.map +0 -1
- package/dist/index-B_g_579T.cjs +0 -2
- package/dist/index-B_g_579T.cjs.map +0 -1
- package/dist/index-BvwyeoMb.js +0 -3
- package/dist/index-BvwyeoMb.js.map +0 -1
- package/dist/index-Bw7mlUQo.js +0 -2
- package/dist/index-Bw7mlUQo.js.map +0 -1
- package/dist/index-Byatzd-A.js +0 -2
- package/dist/index-Byatzd-A.js.map +0 -1
- package/dist/index-C0z9eZLm.cjs +0 -2
- package/dist/index-C0z9eZLm.cjs.map +0 -1
- package/dist/index-C88XPqjX.js +0 -2
- package/dist/index-C88XPqjX.js.map +0 -1
- package/dist/index-CI6FPF49.cjs +0 -2
- package/dist/index-CI6FPF49.cjs.map +0 -1
- package/dist/index-CLZF5_GB.cjs +0 -2
- package/dist/index-CLZF5_GB.cjs.map +0 -1
- package/dist/index-CXSwYlG4.cjs +0 -2
- package/dist/index-CXSwYlG4.cjs.map +0 -1
- package/dist/index-Ch9gotLk.js +0 -2
- package/dist/index-Ch9gotLk.js.map +0 -1
- package/dist/index-CifDpN1Y.js +0 -2
- package/dist/index-CifDpN1Y.js.map +0 -1
- package/dist/index-D5o8VpWJ.cjs +0 -2
- package/dist/index-D5o8VpWJ.cjs.map +0 -1
- package/dist/index-DKT1bABL.js +0 -2
- package/dist/index-DKT1bABL.js.map +0 -1
- package/dist/index-DWcn72PW.js +0 -2
- package/dist/index-DWcn72PW.js.map +0 -1
- package/dist/index-DjCGzPEv.cjs +0 -2
- package/dist/index-DjCGzPEv.cjs.map +0 -1
- package/dist/index-Dq0Jr1Ae.js +0 -2
- package/dist/index-Dq0Jr1Ae.js.map +0 -1
- package/dist/index-Dw0MVypb.cjs +0 -2
- package/dist/index-Dw0MVypb.cjs.map +0 -1
- package/dist/index-FEo3LShh.cjs +0 -2
- package/dist/index-FEo3LShh.cjs.map +0 -1
- package/dist/index-O1hzAUzi.cjs +0 -2
- package/dist/index-O1hzAUzi.cjs.map +0 -1
- package/dist/index-T1ZyLzeF.cjs +0 -2
- package/dist/index-T1ZyLzeF.cjs.map +0 -1
- package/dist/index-iRikoCdK.cjs +0 -2
- package/dist/index-iRikoCdK.cjs.map +0 -1
- package/dist/index-l6Yddj6x.js +0 -2
- package/dist/index-l6Yddj6x.js.map +0 -1
- package/dist/index-rD8LZENp.js +0 -2
- package/dist/index-rD8LZENp.js.map +0 -1
- package/dist/themes/callouts.css +0 -79
- package/dist/themes/collaboration.css +0 -117
- package/dist/themes/comments.css +0 -198
- package/dist/themes/dark.css +0 -109
- package/dist/themes/forest.css +0 -109
- package/dist/themes/light.css +0 -4
- package/dist/themes/links.css +0 -115
- package/dist/themes/math-toc-analytics.css +0 -129
- package/dist/themes/ocean.css +0 -109
- package/dist/themes/rose.css +0 -109
- package/dist/themes/spellcheck.css +0 -173
- package/dist/themes/sunset.css +0 -109
- 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`](
|
|
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
|
-
- [
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
17
|
-
|
|
18
|
-
- [
|
|
19
|
-
|
|
20
|
-
- [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
- [
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
- [
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- [
|
|
50
|
-
- [
|
|
51
|
-
- [
|
|
52
|
-
- [
|
|
53
|
-
- [
|
|
54
|
-
- [
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- [
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
- [
|
|
67
|
-
- [
|
|
68
|
-
- [
|
|
69
|
-
- [
|
|
70
|
-
- [
|
|
71
|
-
- [
|
|
72
|
-
- [
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
- [
|
|
76
|
-
- [
|
|
77
|
-
- [
|
|
78
|
-
- [
|
|
79
|
-
- [
|
|
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
|
|
180
|
-
|
|
|
181
|
-
| `init()`
|
|
182
|
-
| `destroy()`
|
|
183
|
-
| `getHTML()`
|
|
184
|
-
| `setHTML(html)`
|
|
185
|
-
| `getText()`
|
|
186
|
-
| `isEmpty()`
|
|
187
|
-
| `focus()`
|
|
188
|
-
| `blur()`
|
|
189
|
-
| `executeCommand(name, ...args)` | `any`
|
|
190
|
-
| `on(event, handler)`
|
|
191
|
-
| `off(event, handler)`
|
|
192
|
-
| `getWordCount()`
|
|
193
|
-
| `getCharCount()`
|
|
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
|
|
198
|
-
|
|
|
199
|
-
| `content:change`
|
|
200
|
-
| `selection:change`
|
|
201
|
-
| `focus`
|
|
202
|
-
| `blur`
|
|
203
|
-
| `command:executed`
|
|
204
|
-
| `paste`
|
|
205
|
-
| `drop`
|
|
206
|
-
| `upload:error`
|
|
207
|
-
| `file:too-large`
|
|
208
|
-
| `editor:error`
|
|
209
|
-
| `mode:change`
|
|
210
|
-
| `mode:change:markdown`
|
|
211
|
-
| `fullscreen:toggle`
|
|
212
|
-
| `find:results`
|
|
213
|
-
| `history:undo`
|
|
214
|
-
| `history:redo`
|
|
215
|
-
| `plugin:registered`
|
|
216
|
-
| `plugin:error`
|
|
217
|
-
| `codeblock:created`
|
|
218
|
-
| `codeblock:language-change` | `{ language, element }`
|
|
219
|
-
| `wordcount:update`
|
|
220
|
-
| `autosave:saving`
|
|
221
|
-
| `autosave:saved`
|
|
222
|
-
| `autosave:error`
|
|
223
|
-
| `autosave:recovered`
|
|
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
|
|
251
|
-
|
|
|
252
|
-
| `registerFormattingCommands`
|
|
253
|
-
| `registerHeadingCommands`
|
|
254
|
-
| `registerAlignmentCommands`
|
|
255
|
-
| `registerListCommands`
|
|
256
|
-
| `registerLinkCommands`
|
|
257
|
-
| `registerImageCommands`
|
|
258
|
-
| `registerTableCommands`
|
|
259
|
-
| `registerBlockCommands`
|
|
260
|
-
| `registerFontCommands`
|
|
261
|
-
| `registerMediaCommands`
|
|
262
|
-
| `registerFindReplaceCommands`
|
|
263
|
-
| `registerSourceModeCommands`
|
|
264
|
-
| `registerFullscreenCommands`
|
|
265
|
-
| `registerDistractionFreeCommands` | distractionFree
|
|
266
|
-
| `registerSplitViewCommands`
|
|
267
|
-
| `registerColorPresetCommands`
|
|
268
|
-
| `registerMarkdownToggleCommands`
|
|
269
|
-
| `registerAttachmentCommands`
|
|
270
|
-
| `registerImportDocumentCommands`
|
|
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');
|
|
278
|
-
engine.executeCommand('italic');
|
|
279
|
-
engine.executeCommand('underline');
|
|
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');
|
|
282
|
-
engine.executeCommand('superscript');
|
|
283
|
-
engine.executeCommand('removeFormat');
|
|
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);
|
|
292
|
-
engine.executeCommand('heading', 3);
|
|
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');
|
|
295
|
-
engine.executeCommand('paragraph');
|
|
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');
|
|
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');
|
|
308
|
-
engine.executeCommand('indent');
|
|
309
|
-
engine.executeCommand('outdent');
|
|
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',
|
|
332
|
-
target: '_blank',
|
|
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',
|
|
339
|
-
target: '_self',
|
|
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',
|
|
355
|
-
width: 400,
|
|
356
|
-
height: 300,
|
|
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', {
|
|
399
|
-
|
|
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', {
|
|
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', {
|
|
445
|
+
engine.executeCommand('formatCell', {
|
|
446
|
+
format: 'currency',
|
|
447
|
+
options: { currency: 'EUR' },
|
|
448
|
+
});
|
|
416
449
|
engine.executeCommand('formatCell', { format: 'percentage' });
|
|
417
|
-
engine.executeCommand('formatCell', {
|
|
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
|
|
430
|
-
|
|
|
431
|
-
| `insertTable`
|
|
432
|
-
| `addRowBefore`
|
|
433
|
-
| `addRowAfter`
|
|
434
|
-
| `addColBefore`
|
|
435
|
-
| `addColAfter`
|
|
436
|
-
| `deleteRow`
|
|
437
|
-
| `deleteCol`
|
|
438
|
-
| `deleteTable`
|
|
439
|
-
| `mergeCells`
|
|
440
|
-
| `splitCell`
|
|
441
|
-
| `toggleHeaderRow`
|
|
442
|
-
| `sortTable`
|
|
443
|
-
| `filterTable`
|
|
444
|
-
| `clearTableFilters` | —
|
|
445
|
-
| `formatCell`
|
|
446
|
-
| `evaluateFormulas`
|
|
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 = (
|
|
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
|
|
471
|
-
|
|
|
472
|
-
| `SUM`
|
|
473
|
-
| `AVERAGE` | Arithmetic mean of values | `=AVERAGE(B2:B8)`
|
|
474
|
-
| `COUNT`
|
|
475
|
-
| `MIN`
|
|
476
|
-
| `MAX`
|
|
477
|
-
| `IF`
|
|
478
|
-
| `CONCAT`
|
|
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', {
|
|
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', {
|
|
561
|
+
engine.executeCommand('formatCell', {
|
|
562
|
+
format: 'currency',
|
|
563
|
+
options: { currency: 'USD' },
|
|
564
|
+
});
|
|
517
565
|
|
|
518
566
|
// Euro: "1.234,50 €"
|
|
519
|
-
engine.executeCommand('formatCell', {
|
|
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', {
|
|
573
|
+
engine.executeCommand('formatCell', {
|
|
574
|
+
format: 'percentage',
|
|
575
|
+
options: { decimals: 1 },
|
|
576
|
+
});
|
|
523
577
|
|
|
524
578
|
// Date: locale-formatted date
|
|
525
|
-
engine.executeCommand('formatCell', {
|
|
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');
|
|
549
|
-
engine.executeCommand('codeBlock');
|
|
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');
|
|
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,
|
|
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', {
|
|
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',
|
|
773
|
+
filesize: '2.4 MB', // optional, displayed in UI
|
|
704
774
|
});
|
|
705
775
|
|
|
706
|
-
engine.executeCommand('removeAttachment', {
|
|
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 {
|
|
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, '');
|
|
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 {
|
|
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) =>
|
|
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
|
-
{
|
|
781
|
-
|
|
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({
|
|
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
|
|
814
|
-
|
|
|
815
|
-
| `LocalStorageProvider`
|
|
816
|
-
| `SessionStorageProvider` | Tab-scoped saves
|
|
817
|
-
| `FileSystemProvider`
|
|
818
|
-
| `CloudProvider`
|
|
819
|
-
| `CustomProvider`
|
|
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();
|
|
827
|
-
const session = createStorageProvider('sessionStorage');
|
|
828
|
-
const cloud = createStorageProvider({
|
|
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({
|
|
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),
|
|
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',
|
|
859
|
-
key: 'doc-123',
|
|
860
|
-
interval: 30000,
|
|
861
|
-
debounce: 2000,
|
|
862
|
-
enabled: 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();
|
|
967
|
+
manager.init(); // start listening to content:change
|
|
866
968
|
|
|
867
|
-
await manager.save();
|
|
868
|
-
await manager.checkRecovery(engine.getHTML());
|
|
869
|
-
await manager.clearRecovery();
|
|
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();
|
|
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(
|
|
890
|
-
|
|
891
|
-
|
|
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
|
|
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) =>
|
|
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({
|
|
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) {
|
|
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
|
-
{
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
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: {
|
|
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 {
|
|
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');
|
|
1203
|
+
searchPluginRegistry('math'); // → [{ name: 'math-equations', ... }]
|
|
1064
1204
|
searchPluginRegistry('latex'); // → [{ name: 'math-equations', ... }] (matches tags)
|
|
1065
|
-
listRegisteredPlugins();
|
|
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
|
|
1091
|
-
|
|
|
1092
|
-
| `element`
|
|
1093
|
-
| `options`
|
|
1094
|
-
| `executeCommand(name, ...args)` | Execute a command
|
|
1095
|
-
| `on(event, handler)`
|
|
1096
|
-
| `off(event, handler)`
|
|
1097
|
-
| `getSelection()`
|
|
1098
|
-
| `getRange()`
|
|
1099
|
-
| `getActiveFormats()`
|
|
1100
|
-
| `getHTML()`
|
|
1101
|
-
| `getText()`
|
|
1102
|
-
| `isEmpty()`
|
|
1103
|
-
| `getSetting(key)`
|
|
1104
|
-
| `setSetting(key, value)`
|
|
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,
|
|
1137
|
-
|
|
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 {
|
|
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
|
-
[
|
|
1202
|
-
|
|
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(
|
|
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');
|
|
1212
|
-
tokenize('puts "hello"', 'rb');
|
|
1213
|
-
engine.executeCommand('setCodeLanguage', { language: 'ruby' });
|
|
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(
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
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({
|
|
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, {
|
|
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();
|
|
1427
|
+
engine._comments.getAllThreads(); // all threads (newest first)
|
|
1260
1428
|
engine._comments.getUnresolvedThreads();
|
|
1261
1429
|
engine._comments.getResolvedThreads();
|
|
1262
|
-
engine._comments.exportThreads();
|
|
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 {
|
|
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', {
|
|
1280
|
-
|
|
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({
|
|
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(
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
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', {
|
|
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 {
|
|
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', {
|
|
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}}', {
|
|
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(
|
|
1374
|
-
|
|
1375
|
-
|
|
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(
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
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');
|
|
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 {
|
|
1610
|
+
import {
|
|
1611
|
+
MathPlugin,
|
|
1612
|
+
getSymbolPalette,
|
|
1613
|
+
parseMathExpressions,
|
|
1614
|
+
latexToMathML,
|
|
1615
|
+
} from '@remyxjs/core';
|
|
1405
1616
|
|
|
1406
|
-
engine.plugins.register(
|
|
1407
|
-
|
|
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', {
|
|
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', {
|
|
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 {
|
|
1650
|
+
import {
|
|
1651
|
+
TocPlugin,
|
|
1652
|
+
buildOutline,
|
|
1653
|
+
flattenOutline,
|
|
1654
|
+
renderTocHTML,
|
|
1655
|
+
validateHeadingHierarchy,
|
|
1656
|
+
} from '@remyxjs/core';
|
|
1431
1657
|
|
|
1432
|
-
engine.plugins.register(
|
|
1433
|
-
|
|
1434
|
-
|
|
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 {
|
|
1682
|
+
import {
|
|
1683
|
+
AnalyticsPlugin,
|
|
1684
|
+
analyzeContent,
|
|
1685
|
+
keywordDensity,
|
|
1686
|
+
seoAnalysis,
|
|
1687
|
+
} from '@remyxjs/core';
|
|
1455
1688
|
|
|
1456
|
-
engine.plugins.register(
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
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 {
|
|
1722
|
+
import {
|
|
1723
|
+
SpellcheckPlugin,
|
|
1724
|
+
analyzeGrammar,
|
|
1725
|
+
STYLE_PRESETS,
|
|
1726
|
+
} from '@remyxjs/core';
|
|
1488
1727
|
|
|
1489
|
-
engine.plugins.register(
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
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(
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
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
|
|
1570
|
-
|
|
|
1571
|
-
| `collaboration:connected`
|
|
1572
|
-
| `collaboration:disconnected` | `{ reason }`
|
|
1573
|
-
| `collaboration:peer-joined`
|
|
1574
|
-
| `collaboration:peer-left`
|
|
1575
|
-
| `collaboration:sync`
|
|
1576
|
-
| `collaboration: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 {
|
|
1831
|
+
return {
|
|
1832
|
+
send(data) {
|
|
1833
|
+
/* ... */
|
|
1834
|
+
},
|
|
1835
|
+
close() {
|
|
1836
|
+
/* ... */
|
|
1837
|
+
},
|
|
1838
|
+
};
|
|
1588
1839
|
},
|
|
1589
1840
|
};
|
|
1590
1841
|
|
|
1591
|
-
engine.plugins.register(
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
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();
|
|
1607
|
-
sel.getSelectedText();
|
|
1608
|
-
sel.getSelectedHTML();
|
|
1609
|
-
sel.getBoundingRect();
|
|
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();
|
|
1627
|
-
sel.getParentBlock();
|
|
1628
|
-
sel.getClosestElement('a');
|
|
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();
|
|
1643
|
-
sel.collapse(true);
|
|
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();
|
|
1652
|
-
engine.history.redo();
|
|
1653
|
-
engine.history.canUndo();
|
|
1654
|
-
engine.history.canRedo();
|
|
1655
|
-
engine.history.snapshot();
|
|
1656
|
-
engine.history.clear();
|
|
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,
|
|
1669
|
-
debounceMs: 500,
|
|
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');
|
|
1687
|
-
engine.keyboard.getShortcutLabel('mod+b');
|
|
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
|
|
1695
|
-
|
|
|
1696
|
-
| Mod+B
|
|
1697
|
-
| Mod+I
|
|
1698
|
-
| Mod+U
|
|
1699
|
-
| Mod+Shift+X | strikethrough
|
|
1700
|
-
| Mod+,
|
|
1701
|
-
| Mod+.
|
|
1702
|
-
| Mod+K
|
|
1703
|
-
| Mod+Shift+7 | orderedList
|
|
1704
|
-
| Mod+Shift+8 | unorderedList
|
|
1705
|
-
| Mod+Shift+9 | blockquote
|
|
1706
|
-
| Mod+Shift+C | codeBlock
|
|
1707
|
-
| Mod+F
|
|
1708
|
-
| Mod+Shift+U | sourceMode
|
|
1709
|
-
| Mod+Shift+F | fullscreen
|
|
1710
|
-
| Mod+Z
|
|
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
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
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',
|
|
1736
|
-
'
|
|
1737
|
-
'
|
|
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',
|
|
1743
|
-
'
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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',
|
|
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 {
|
|
2156
|
+
import {
|
|
2157
|
+
closestBlock,
|
|
2158
|
+
closestTag,
|
|
2159
|
+
wrapInTag,
|
|
2160
|
+
unwrapTag,
|
|
2161
|
+
generateId,
|
|
2162
|
+
isBlockEmpty,
|
|
2163
|
+
} from '@remyxjs/core';
|
|
1875
2164
|
|
|
1876
|
-
closestBlock(node, editorElement);
|
|
1877
|
-
closestTag(node, 'a', editorElement);
|
|
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);
|
|
1880
|
-
generateId();
|
|
1881
|
-
isBlockEmpty(paragraphElement);
|
|
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();
|
|
1902
|
-
getModKey();
|
|
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
|
|
1912
|
-
|
|
|
1913
|
-
| `--rmx-bg`
|
|
1914
|
-
| `--rmx-text`
|
|
1915
|
-
| `--rmx-border`
|
|
1916
|
-
| `--rmx-toolbar-bg`
|
|
1917
|
-
| `--rmx-toolbar-text`
|
|
1918
|
-
| `--rmx-toolbar-hover`
|
|
1919
|
-
| `--rmx-toolbar-active` | `#dee2e6`
|
|
1920
|
-
| `--rmx-primary`
|
|
1921
|
-
| `--rmx-primary-hover`
|
|
1922
|
-
| `--rmx-primary-text`
|
|
1923
|
-
| `--rmx-shadow`
|
|
1924
|
-
| `--rmx-radius`
|
|
1925
|
-
| `--rmx-font-family`
|
|
1926
|
-
| `--rmx-font-size`
|
|
1927
|
-
| `--rmx-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
|
|
1936
|
-
|
|
|
1937
|
-
| `light`
|
|
1938
|
-
| `dark`
|
|
1939
|
-
| `ocean`
|
|
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`
|
|
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
|
|
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 = {
|
|
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;
|
|
1998
|
-
TOOLBAR_PRESETS.rich;
|
|
1999
|
-
TOOLBAR_PRESETS.standard;
|
|
2000
|
-
TOOLBAR_PRESETS.minimal;
|
|
2001
|
-
TOOLBAR_PRESETS.bare;
|
|
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'],
|
|
2302
|
+
['bold', 'italic', 'underline'], // Group 1
|
|
2009
2303
|
['heading', 'orderedList', 'unorderedList'], // Group 2
|
|
2010
|
-
['link', 'image'],
|
|
2011
|
-
]
|
|
2304
|
+
['link', 'image'], // Group 3
|
|
2305
|
+
];
|
|
2012
2306
|
```
|
|
2013
2307
|
|
|
2014
2308
|
### Custom Toolbars
|
|
2015
2309
|
|
|
2016
2310
|
```js
|
|
2017
|
-
import {
|
|
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, [
|
|
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',
|
|
2028
|
-
relativeTo: 'link',
|
|
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 {
|
|
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(
|
|
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', {
|
|
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', {
|
|
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
|
|
2152
|
-
|
|
|
2153
|
-
| `url`
|
|
2154
|
-
| `options.env`
|
|
2155
|
-
| `options.headers` | `Record<string, string>` | Custom fetch headers
|
|
2156
|
-
| `options.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
|
|
2205
|
-
|
|
|
2206
|
-
| `register(id, engine)`
|
|
2207
|
-
| `unregister(id)`
|
|
2208
|
-
| `getEditor(id)`
|
|
2209
|
-
| `getEditorIds()`
|
|
2210
|
-
| `editorCount`
|
|
2211
|
-
| `on(event, handler)`
|
|
2212
|
-
| `off(event, handler)`
|
|
2213
|
-
| `once(event, handler)`
|
|
2214
|
-
| `emit(event, data)`
|
|
2215
|
-
| `broadcast(event, data, opts)` | Emit into each editor's local `eventBus`
|
|
2216
|
-
| `reset()`
|
|
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 } =
|
|
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(
|
|
2242
|
-
|
|
2243
|
-
|
|
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,
|
|
2256
|
-
DEFAULT_MENU_BAR,
|
|
2571
|
+
DEFAULT_TOOLBAR, // Full toolbar configuration
|
|
2572
|
+
DEFAULT_MENU_BAR, // Menu bar structure
|
|
2257
2573
|
|
|
2258
2574
|
// Font & color defaults
|
|
2259
|
-
DEFAULT_FONTS,
|
|
2260
|
-
DEFAULT_FONT_SIZES,
|
|
2261
|
-
DEFAULT_COLORS,
|
|
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,
|
|
2580
|
+
DEFAULT_KEYBINDINGS, // Map: command name → shortcut string
|
|
2265
2581
|
|
|
2266
2582
|
// Heading options
|
|
2267
|
-
HEADING_OPTIONS,
|
|
2583
|
+
HEADING_OPTIONS, // H1-H6 + paragraph
|
|
2268
2584
|
|
|
2269
2585
|
// Security schema
|
|
2270
|
-
ALLOWED_TAGS,
|
|
2271
|
-
ALLOWED_STYLES,
|
|
2586
|
+
ALLOWED_TAGS, // Tag → allowed attributes map
|
|
2587
|
+
ALLOWED_STYLES, // Allowed CSS property names
|
|
2272
2588
|
|
|
2273
2589
|
// Command metadata
|
|
2274
|
-
BUTTON_COMMANDS,
|
|
2275
|
-
TOOLTIP_MAP,
|
|
2276
|
-
SHORTCUT_MAP,
|
|
2277
|
-
MODAL_COMMANDS,
|
|
2278
|
-
getShortcutLabel,
|
|
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,
|
|
2283
|
-
filterSlashItems,
|
|
2284
|
-
getRecentCommands,
|
|
2285
|
-
recordRecentCommand,
|
|
2286
|
-
clearRecentCommands,
|
|
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,
|
|
2293
|
-
parseMentions,
|
|
2608
|
+
CommentsPlugin, // Inline comment threads plugin
|
|
2609
|
+
parseMentions, // (text) => string[] — extract @mentions from text
|
|
2294
2610
|
|
|
2295
2611
|
// Callouts & Alerts
|
|
2296
|
-
CalloutPlugin,
|
|
2297
|
-
registerCalloutType,
|
|
2298
|
-
unregisterCalloutType,
|
|
2299
|
-
getCalloutTypes,
|
|
2300
|
-
getCalloutType,
|
|
2301
|
-
parseGFMAlert,
|
|
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,
|
|
2305
|
-
detectLinks,
|
|
2306
|
-
slugify,
|
|
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,
|
|
2310
|
-
renderTemplate,
|
|
2311
|
-
extractTags,
|
|
2312
|
-
registerTemplate,
|
|
2313
|
-
unregisterTemplate,
|
|
2314
|
-
getTemplateLibrary,
|
|
2315
|
-
getTemplate,
|
|
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,
|
|
2319
|
-
getHeadings,
|
|
2320
|
-
selectNextOccurrence,
|
|
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,
|
|
2639
|
+
DragDropPlugin, // Drop zones, cross-editor, file drops, reorder
|
|
2324
2640
|
|
|
2325
2641
|
// Math & Equations
|
|
2326
|
-
MathPlugin,
|
|
2327
|
-
getSymbolPalette,
|
|
2328
|
-
parseMathExpressions,
|
|
2329
|
-
latexToMathML,
|
|
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,
|
|
2333
|
-
buildOutline,
|
|
2334
|
-
flattenOutline,
|
|
2335
|
-
renderTocHTML,
|
|
2336
|
-
validateHeadingHierarchy,
|
|
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,
|
|
2340
|
-
analyzeContent,
|
|
2341
|
-
countSyllables,
|
|
2342
|
-
splitSentences,
|
|
2343
|
-
fleschKincaid,
|
|
2344
|
-
fleschReadingEase,
|
|
2345
|
-
gunningFog,
|
|
2346
|
-
colemanLiau,
|
|
2347
|
-
vocabularyLevel,
|
|
2348
|
-
keywordDensity,
|
|
2349
|
-
seoAnalysis,
|
|
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,
|
|
2353
|
-
analyzeGrammar,
|
|
2354
|
-
summarizeIssues,
|
|
2355
|
-
detectPassiveVoice,
|
|
2356
|
-
detectWordiness,
|
|
2357
|
-
detectCliches,
|
|
2358
|
-
detectPunctuationIssues,
|
|
2359
|
-
STYLE_PRESETS,
|
|
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,
|
|
2678
|
+
CollaborationPlugin, // CRDT co-editing, live cursors, presence, transport
|
|
2363
2679
|
|
|
2364
2680
|
// Plugin registry
|
|
2365
|
-
registerPluginInRegistry,
|
|
2681
|
+
registerPluginInRegistry, // Register a plugin for discovery
|
|
2366
2682
|
unregisterPluginFromRegistry, // Remove from registry
|
|
2367
|
-
listRegisteredPlugins,
|
|
2368
|
-
searchPluginRegistry,
|
|
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 {
|
|
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 {
|
|
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 = {}) {
|