@richscripts/richtexteditor 2.4.0 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/migration/README.md +100 -0
  2. package/migration/cli.js +294 -0
  3. package/migration/index.d.ts +18 -0
  4. package/migration/index.js +242 -0
  5. package/migration/report-schema-v2.json +127 -0
  6. package/package.json +14 -2
  7. package/richtexteditor/CHANGELOG.md +326 -1
  8. package/richtexteditor/all_plugins.js +14982 -4831
  9. package/richtexteditor/all_plugins.min.js +62 -42
  10. package/richtexteditor/plugins/accessibilitychecker.js +8 -7
  11. package/richtexteditor/plugins/aitoolkit.css +17 -8
  12. package/richtexteditor/plugins/aitoolkit.js +8 -8
  13. package/richtexteditor/plugins/all_plugins.js +14982 -4831
  14. package/richtexteditor/plugins/all_plugins.min.js +62 -42
  15. package/richtexteditor/plugins/autocorrect.js +353 -0
  16. package/richtexteditor/plugins/bookmarkcard.js +12 -1
  17. package/richtexteditor/plugins/changecase.js +238 -0
  18. package/richtexteditor/plugins/comments.js +15 -28
  19. package/richtexteditor/plugins/contentminimap.js +6 -5
  20. package/richtexteditor/plugins/crdt-engine.js +2201 -198
  21. package/richtexteditor/plugins/crdt-engine.min.js +5 -5
  22. package/richtexteditor/plugins/crossreference.js +408 -0
  23. package/richtexteditor/plugins/documentimport.js +915 -21
  24. package/richtexteditor/plugins/documentoutline.js +5 -4
  25. package/richtexteditor/plugins/docxexport.js +960 -0
  26. package/richtexteditor/plugins/draghandle.js +309 -0
  27. package/richtexteditor/plugins/footnotes.js +398 -0
  28. package/richtexteditor/plugins/formatpainter.js +298 -0
  29. package/richtexteditor/plugins/formattingmarks.js +246 -0
  30. package/richtexteditor/plugins/html2pdf.js +108 -4
  31. package/richtexteditor/plugins/imageeditor.js +37 -7
  32. package/richtexteditor/plugins/insertgallery.js +553 -170
  33. package/richtexteditor/plugins/keyboarda11y.js +316 -0
  34. package/richtexteditor/plugins/linenumbers.js +352 -0
  35. package/richtexteditor/plugins/linkchecker.js +396 -0
  36. package/richtexteditor/plugins/mentionplugin.js +4 -3
  37. package/richtexteditor/plugins/mergefields.js +328 -0
  38. package/richtexteditor/plugins/multilevellist.js +137 -0
  39. package/richtexteditor/plugins/pagination.js +505 -0
  40. package/richtexteditor/plugins/pdfexport.js +1137 -0
  41. package/richtexteditor/plugins/permanentpen.js +211 -0
  42. package/richtexteditor/plugins/revisionhistory.js +47 -34
  43. package/richtexteditor/plugins/rtlui.js +150 -0
  44. package/richtexteditor/plugins/sanitizer.js +510 -0
  45. package/richtexteditor/plugins/slashcommand.js +142 -17
  46. package/richtexteditor/plugins/spellcheck.js +6 -3
  47. package/richtexteditor/plugins/tableofcontents.js +353 -0
  48. package/richtexteditor/plugins/tabletools.js +342 -0
  49. package/richtexteditor/plugins/textdirection.js +313 -0
  50. package/richtexteditor/plugins/trackedchanges.js +37 -6
  51. package/richtexteditor/plugins/tui.image-editor/tui-code-snippet.min.js +1 -1
  52. package/richtexteditor/plugins/tui.image-editor/tui-color-picker.min.js +1 -1
  53. package/richtexteditor/plugins/tui.image-editor/tui-image-editor.min.js +1 -1
  54. package/richtexteditor/plugins/vendor/html2pdf.bundle.min.js +3 -0
  55. package/richtexteditor/plugins/watermark.js +214 -0
  56. package/richtexteditor/plugins/wordexport.js +0 -0
  57. package/richtexteditor/plugins/yjscollab.js +49 -11
  58. package/richtexteditor/rte-config.js +44 -44
  59. package/richtexteditor/rte.js +91 -63
  60. package/richtexteditor/rte_theme_default.css +483 -384
@@ -1,6 +1,331 @@
1
1
  # RichTextEditor Changelog
2
2
 
3
- ## Version 2.3.0 — Block types, equation editor, bookmark cards, spell check, drag-handles, smart chips, email export (Pass 64)
3
+ ## Version 2.4.0 — Long-document authoring, real document export, security and accessibility (Pass 65)
4
+
5
+ Twenty-nine plugins, no `rte.js` core changes. The theme of the release is
6
+ closing the gaps that competitors gate behind paid tiers, and fixing three
7
+ things about our own product that were not true.
8
+
9
+ ### Security (three real holes, found by testing rather than by review)
10
+ - **`plugins/sanitizer.js`** — allowlist content filter on input, output **and
11
+ the live DOM**. Unknown elements are unwrapped rather than deleted, so a
12
+ user's words are never silently lost. Parses with `DOMParser` (never
13
+ `innerHTML` on a live node, which fires `<img onerror>` before the filter
14
+ runs) and re-parses until stable to defeat mutation XSS.
15
+ - **Stored XSS:** `<iframe srcdoc="<script>…">` survived the core filter into
16
+ saved HTML and executed when that HTML was rendered downstream. "Nothing
17
+ executes in the editor" was the wrong bar.
18
+ - **Transient XSS:** the first version cleaned up *after* the paste — measured
19
+ execution at +70 ms. Paste is now intercepted in the capture phase and only
20
+ when the clipboard HTML actually executes on insertion, so ordinary Word
21
+ pastes still reach the editor's own Word cleanup untouched.
22
+ - **Collaborator XSS:** a CRDT applies remote edits by writing nodes straight
23
+ into the live document, bypassing both paste and `setHTMLCode`. Any
24
+ participant could run script in every other participant's browser, and the
25
+ payload replicated onward. Closed by a live-DOM guard
26
+ (`sanitizerGuardLiveDom`, on by default) that only engages when an inserted
27
+ node or attribute is executable, so typing costs nothing.
28
+ - **`plugins/restrictedediting.js`** — Word-style protected documents: content
29
+ is read-only except regions tagged `data-rte-editable="true"`.
30
+
31
+ ### Accessibility
32
+ - **`plugins/keyboarda11y.js`** — an audit of the running editor found four open
33
+ issues, three of them WCAG Level A: a **keyboard trap** (focus could enter the
34
+ editing area and never leave), `aria-pressed` missing on every toggle button,
35
+ `aria-expanded` missing on every menu button, and 53 toolbar tab stops.
36
+ `Escape` now leaves the editing area (Tab keeps its editing meaning), toggle
37
+ state is mirrored from the existing active class so announced state cannot
38
+ disagree with the highlight, and each toolbar has a single roving tab stop.
39
+ - **`plugins/accessibilitychecker.js`** — flags and one-click repairs missing
40
+ alt text, missing table headers, empty or non-descriptive links, and skipped
41
+ heading levels. Header repair emits `<th scope="col">`, which is what the
42
+ tagged PDF and .docx exports then consume.
43
+
44
+ ### Document export and import
45
+ - **`plugins/pdfexport.js`** — PDF written by hand, with **selectable text** and
46
+ a full accessibility tag tree (H1–H6, P, L/LI, Table/TH/TD, Figure; decoration
47
+ emitted as artifacts), so the output passes a Section 508 / EN 301 549 review
48
+ rather than only a copy-paste test. The raster html2pdf path is kept for cases
49
+ where pixel fidelity matters more.
50
+ - **`plugins/docxexport.js`** — a genuine OOXML package built in the browser via
51
+ `CompressionStream`: no library, no server, no upload. Emits exactly what
52
+ `documentimport.js` reads, so HTML → .docx → HTML round-trips.
53
+ - **`plugins/documentimport.js`** — .docx import completeness: text boxes,
54
+ endnotes as well as footnotes, equations (OMML ↔ LaTeX), SmartArt diagrams and
55
+ WMF placeholders. Nothing is dropped silently; what HTML cannot represent
56
+ degrades visibly.
57
+ - **`plugins/wordexport.js`** — Word-compatible HTML export (library-free).
58
+
59
+ ### Long-document authoring
60
+ - **`plugins/pagination.js`** — print-layout page view. CKEditor and Tiptap both
61
+ sell this as a paid add-on.
62
+ - **`plugins/multilevellist.js`** — legal numbering (1, 1.1, 1.1.1) via CSS
63
+ counters.
64
+ - **`plugins/footnotes.js`** — inline markers with an auto-renumbering notes
65
+ section.
66
+ - **`plugins/crossreference.js`** — references that keep pointing at the right
67
+ target after the document is edited around them.
68
+ - **`plugins/tableofcontents.js`** — self-updating contents block from the
69
+ document's headings. Premium in both majors.
70
+ - **`plugins/linenumbers.js`** — margin line numbers for pleadings, statutes and
71
+ transcripts.
72
+ - **`plugins/documentoutline.js`**, **`plugins/contentminimap.js`** — navigation
73
+ for long documents.
74
+ - **`plugins/watermark.js`** — DRAFT / CONFIDENTIAL marking behind the content.
75
+
76
+ ### Editing and productivity
77
+ - **`plugins/formatpainter.js`** — the clipboard-brush interaction, premium in
78
+ both majors.
79
+ - **`plugins/changecase.js`** — UPPERCASE / lowercase / Title / Sentence /
80
+ tOGGLE. CKEditor gates this behind a plan.
81
+ - **`plugins/autocorrect.js`** — typo fixes, sentence capitalisation, smart
82
+ quotes and dashes as you type.
83
+ - **`plugins/permanentpen.js`** — a fixed format applied to everything typed
84
+ until switched off.
85
+ - **`plugins/draghandle.js`** — grab a block by its grip and drop it elsewhere.
86
+ - **`plugins/tabletools.js`** — column sorting and row numbering; TinyMCE sells
87
+ both as premium "Enhanced Tables".
88
+ - **`plugins/mergefields.js`** — placeholders that turn a document into a
89
+ template for mail merge and invoice runs.
90
+ - **`plugins/linkchecker.js`** — finds broken, unsafe, misleading, or
91
+ assistive-technology-unreachable links, with a host-supplied resolver so no
92
+ vendor infrastructure is required. TinyMCE's equivalent is premium *and*
93
+ requires their service.
94
+ - **`plugins/formattingmarks.js`** — Word's ¶ button, including the invisible
95
+ non-breaking and zero-width characters that cause mysterious layout bugs.
96
+ - **`plugins/charlimit.js`** — enforces a maximum (the built-in statistics
97
+ readout only displays counts).
98
+ - **`plugins/readabilitystats.js`** — local Flesch / Flesch-Kincaid readout, no
99
+ API call.
100
+
101
+ ### Right-to-left
102
+ - **`plugins/textdirection.js`** — direction commands, toolbar items and
103
+ language strings for Arabic, Hebrew, Persian and Urdu, which the editor
104
+ previously had no way to express at all.
105
+ - **`plugins/rtlui.js`** — flips the editor chrome (toolbar, dropdowns, menus,
106
+ dialogs) so an RTL user is not typing right-to-left inside a left-to-right
107
+ frame.
108
+
109
+ ### Build
110
+ - `build-bundle.ps1` now searches several locations for esbuild and fails hard
111
+ when it finds none. It previously looked at one hardcoded path and, when that
112
+ path was pruned, silently skipped minification while still exiting 0 — so a
113
+ stale `all_plugins.min.js` was mirrored to every tier for several builds.
114
+
115
+ ## Version 2.3.0 — Block types, equation editor, bookmark cards, spell check, drag-handles, smart chips, email export, inline Markdown, Mermaid diagrams, read-aloud, emoji autocomplete, task lists, auto-embed, foldable headings, typewriter & focus modes, Markdown export, local-draft recovery, AI ghost-text autocomplete (Pass 64)
116
+
117
+ **New plugin: `plugins/ghostcomplete.js` — AI ghost-text autocomplete**
118
+ - GitHub-Copilot / Notion-AI-style inline completion: after a typing pause a
119
+ greyed suggestion appears after the caret continuing your sentence; **Tab**
120
+ accepts it, **Esc** (or simply continuing to type) dismisses it. Ambient and
121
+ hands-free — distinct from the command-driven AI Toolkit.
122
+ - **Bring-your-own-key**: supply `config.ghostTextResolver({before, after, full})`
123
+ returning a string (or Promise). No model ships and no network call is made
124
+ without it; the feature is inert until both `config.ghostText` and a resolver
125
+ are set. Tunables: `delayMs` (default 600), `minChars` (3), `maxContext` (2000).
126
+ - **Serialization-safe**: the suggestion is a `contenteditable="false"`
127
+ `<span class="rte-ghost">` decoration that is never part of the saved content —
128
+ the caret is kept *before* it so typing/saving behave normally, and
129
+ `getHTMLCode` / `getJSON` are wrapped to strip any ghost span. Accept goes
130
+ through `execCommand("insertText")` so one Ctrl+Z reverts it. Stale-request
131
+ tokens prevent races while typing. API: `editor.requestGhostText()` /
132
+ `acceptGhostText()` / `dismissGhostText()` / `hasGhostText()` /
133
+ `setGhostTextResolver(fn)`; slash `/ai complete`. No core `rte.js` changes.
134
+ Verified live (ghost shows, Tab + API accept inserts undoable text with clean
135
+ serialize, Esc / typing / mousedown / API dismiss, ghost never leaks into saved
136
+ HTML).
137
+
138
+ **New plugin: `plugins/localdraft.js` — local draft autosave & crash recovery**
139
+ - Complements the server-oriented core `config.autoSave` with offline resilience:
140
+ persists the document to `localStorage` as you type (debounced) and, on the
141
+ next load, offers to restore the unsaved draft via an unobtrusive bar
142
+ ("An unsaved draft from 3 min ago was found — Restore / Discard"). Gmail /
143
+ Notion style "never lose your work".
144
+ - Opt-in via `config.localDraft = true` or an options object (`key`,
145
+ `intervalMs`, `ttlMs`, `promptRestore`). Safe by design: the draft is **never
146
+ auto-applied** (the server content shows until the user clicks Restore),
147
+ empty/unchanged docs aren't persisted, and stale drafts past the TTL (default
148
+ 14 days) are pruned. Also saves on `beforeunload`/`pagehide`. API:
149
+ `editor.saveLocalDraft()` / `hasLocalDraft()` / `getLocalDraft()` /
150
+ `restoreLocalDraft()` / `clearLocalDraft()` (call `clear` after a real save).
151
+ Verified live (autosave→reload→restore-bar→Restore round-trip) + 11 logic
152
+ assertions (TTL prune, blank/baseline skip, restore, clear).
153
+
154
+ **New plugin: `plugins/markdownexport.js` — Copy / Download as Markdown**
155
+ - Surfaces the core HTML→Markdown engine (`editor.toMarkdown()`) as a
156
+ discoverable export, the way Notion "Copy as Markdown" / Obsidian do. Slash
157
+ commands `/copy as markdown` and `/download as markdown`, plus the API
158
+ `editor.getMarkdown()`, `editor.copyAsMarkdown()` (clipboard, with an
159
+ execCommand fallback for older browsers) and `editor.downloadMarkdown(name?)`
160
+ (saves a `.md` Blob; the filename is derived from the first heading when not
161
+ given). Read-only — nothing in the document changes. Toggle with
162
+ `config.markdownExportEnabled`. Verified live (Markdown output for headings /
163
+ bold / italic / lists, clipboard receives the correct text, download filename
164
+ derivation + custom name keep the `.md` extension).
165
+
166
+ **New plugin: `plugins/dialoga11y.js` — dialog accessibility**
167
+ - The editor's dialog popups (`<rte-dropdown-panel>` with an `<rte-dialog-header>`)
168
+ shipped without `role`/`aria-label`, and their fields without programmatic
169
+ labels — so a screen reader announced an unnamed "menu" with unlabeled inputs
170
+ (WCAG 4.1.2 / 1.3.1 / 3.3.2). This plugin adds the missing metadata, deriving
171
+ every name from the editor's OWN markup (never guessing): the dialog name is the
172
+ header text ("Insert Link", "Insert Table"), the role is corrected from `menu`
173
+ to `dialog` on titled form popups, and each field is labelled from its
174
+ `<rte-dialog-line-URL/-TEXT/-TARGET>` wrapper. Natively-labelled fields are left
175
+ untouched, and plain dropdowns (color/font pickers, no header) are deliberately
176
+ skipped so they aren't mislabelled. No core changes; opt out with
177
+ `config.dialogA11y = false`. Verified live on the link/table dialogs (role,
178
+ name, and field labels all correct; native `<label>`s preserved).
179
+
180
+ **New plugin: `plugins/a11yenhance.js` — accessibility metadata**
181
+ - Closes the most common WCAG 2.2 findings for an iframe-based editor by emitting
182
+ the assistive-technology metadata the editing surface needs: the editor iframe
183
+ gets a `title` (WCAG 2.4.1 / 4.1.2), the editing region gets `role="textbox"` +
184
+ `aria-multiline="true"` + an `aria-label` (4.1.2), and the editing document gets
185
+ a `lang` attribute (3.1.1 / 3.1.2). Re-applied on every document remount so the
186
+ metadata survives `setHTMLCode`. Configurable via `config.contentAriaLabel`,
187
+ `config.contentLang` (defaults to the host page `<html lang>` or `en`), and
188
+ `config.a11yEnhance = false` to opt out; API `editor.applyAccessibilityMetadata()`.
189
+ Verified live (iframe title, role, aria-multiline, aria-label, document lang all
190
+ present and surviving remount).
191
+
192
+ **New plugin: `plugins/typewriter.js` — typewriter scrolling + focus mode**
193
+ - Two distraction-free writing modes, both purely presentational (no content or
194
+ DOM mutation — nothing to serialize):
195
+ - **Typewriter mode** keeps the caret line pinned to a fixed vertical anchor
196
+ (default: the middle of the viewport) by scrolling the editable as you type,
197
+ the way iA Writer / Typora / Ulysses do — the text rises to meet a steady
198
+ caret instead of the caret marching down the page. Anchor is configurable
199
+ via `config.typewriterAnchor` (0.15–0.85 fraction of viewport height).
200
+ - **Focus mode** dims every block except the paragraph / heading / list-item
201
+ the caret is in (Notion / iA Writer "focus" behaviour), so the current
202
+ sentence stands out.
203
+ - Toggle either independently: slash `/typewriter` + `/focus mode`, or the API
204
+ `editor.toggleTypewriterMode()` / `editor.toggleFocusMode()` (plus
205
+ `set…`/`is…` variants). Off by default; opt in with `config.typewriterModeEnabled`
206
+ / `config.focusModeEnabled`.
207
+ - **Serialization-safe**: focus mode toggles a single class on the editable
208
+ (`rte-focus-active`) and a transient class on the active block
209
+ (`rte-focus-current`); typewriter mode only scrolls. `getHTMLCode` / `getJSON`
210
+ are wrapped to strip the transient class around the call, so saved markup is
211
+ always clean even if a save fires mid-keystroke. No core `rte.js` changes.
212
+ Verified live (focus dimming current=1.0 / dimmed=0.28 opacity, typewriter
213
+ scroll pulls the caret line to the anchor, serialization clean) + 19 logic
214
+ assertions.
215
+
216
+ **New plugin: `plugins/foldheadings.js` — collapsible heading sections**
217
+ - Obsidian / Notion / Word-style section folding: hover a heading and click the
218
+ gutter chevron (or call the API) to collapse every block beneath it, up to the
219
+ next heading of the same or higher level. Great for navigating long structured
220
+ documents; pairs with the existing document-outline panel. API:
221
+ `editor.toggleHeadingFold(h)`, `editor.foldAtCaret()`, `editor.unfoldAll()`;
222
+ slash `/fold section` and `/unfold all`. Toggle with `config.foldHeadingsEnabled`.
223
+ - **Serialization-safe by design**: the fold state lives ONLY in runtime CSS
224
+ classes (`rte-fold-collapsed` / `rte-fold-hidden`) and the chevron is a CSS
225
+ `::before` pseudo-element &mdash; nothing is injected into the content.
226
+ `getHTMLCode` / `getJSON` / `getHTMLContent` are wrapped to strip those classes
227
+ around the call, so **saved markup is always clean and folded content is never
228
+ lost**. Caret safety: folding a section that contains the caret moves the caret
229
+ to the heading so it is never stuck in hidden content. No core `rte.js` changes.
230
+ Verified live (14 checks: level-aware folds, serialization clean + content
231
+ preserved + restored, caret safety, gutter-click vs text-click).
232
+
233
+ **New plugin: `plugins/autoembed.js` — auto-embed media URLs on paste**
234
+ - Paste a bare YouTube or Vimeo URL on its own line (collapsed caret) and it
235
+ becomes a responsive 16:9 embed &mdash; the Notion / Medium behaviour.
236
+ Complements the command-driven Insert Video dialog and the bookmark-card plugin
237
+ (which makes a link-preview card from any URL). Also exposed programmatically as
238
+ `editor.embedUrl(url)`.
239
+ - **Injection-safe by construction**: the iframe `src` is NEVER the raw pasted
240
+ string. The video id is extracted with a strict whitelist regex, constrained to
241
+ `[A-Za-z0-9_-]` / digits, and a trusted provider embed URL is built from a
242
+ template (verified against `youtube.com.evil.com`, `javascript:`,
243
+ `"><script>`, wrong-domain and non-numeric inputs). Mid-paragraph and
244
+ text-selected pastes are left untouched (they stay links). Extend providers via
245
+ `config.autoEmbedResolver(url)` (must return an `https://` embed URL); toggle
246
+ with `config.autoEmbedEnabled` (default true). No core `rte.js` changes.
247
+ - Robust paste hook: the listener attaches to the iframe **document** and
248
+ re-attaches on `ready` / `aftersethtml` (the editor recreates the editable body
249
+ after init, which would orphan a body-level listener). Verified live (13 checks
250
+ + 14 resolver/security unit tests).
251
+
252
+ **New plugin: `plugins/todolist.js` — interactive to-do / task lists**
253
+ - `editor.execCommand("inserttodolist")` (slash `/to-do`, toolbar) inserts a
254
+ checklist whose items each carry a clickable checkbox; clicking toggles the item
255
+ between open and done (a check + strike-through). Notion / GitHub / Google-Docs
256
+ style. Closes a real gap &mdash; the editor shipped an orphan `inserttodolist`
257
+ toolbar icon (`svgCode_inserttodolist`) with no command behind it.
258
+ - Checkboxes are styled `contenteditable="false"` spans (not `<input>`s, which
259
+ misbehave in contenteditable); the done state lives in `data-checked`, so the
260
+ list round-trips through `getHTMLCode` / `getJSON` and re-binds on load via
261
+ click delegation (works for pasted/loaded lists). New items created with Enter
262
+ are auto-normalized to include a checkbox and an open state. CSS injected at
263
+ insert time. No core `rte.js` changes. Verified live (10 checks: insert,
264
+ click-toggle both ways, text-click no-op, style injection, Enter-item
265
+ normalization, round-trip).
266
+
267
+ **New plugin: `plugins/emojiautocomplete.js` — emoji `:shortcode:` autocomplete**
268
+ - GitHub / Slack / Discord-style inline emoji entry, no picker needed. Two
269
+ behaviours: (1) typing a complete `:shortcode:` (closing colon) replaces it with
270
+ the emoji (`:fire:` → 🔥, `:+1:` → 👍, `:tada:` → 🎉); (2) typing `:` + a partial
271
+ query shows a popup of matches — Arrow keys move, Enter/Tab insert, Esc dismisses.
272
+ - Curated built-in shortcode map (~140 common emoji, what people actually type);
273
+ extend or replace via `config.emojiShortcodes`. Toggle with
274
+ `config.emojiAutocompleteEnabled` (default true). Word-boundary gated (a colon
275
+ mid-identifier like `path:fire:` is ignored), inert inside `<code>`/`<pre>`.
276
+ Complements the existing emoji picker (insertemoji) with keyboard-only entry.
277
+ No core `rte.js` changes. Verified live (12 checks: immediate replace, unknown-
278
+ code passthrough, popup matches, Enter-commit, Arrow-nav, mid-word guard,
279
+ code-inert).
280
+
281
+ **New plugin: `plugins/readaloud.js` — read-aloud (text-to-speech)**
282
+ - `editor.execCommand("readaloud")` (slash `/read aloud`, toolbar) opens a small
283
+ non-modal control bar (play/pause, stop, voice, rate) and reads the current
284
+ selection — or the whole document if nothing is selected — aloud, sentence by
285
+ sentence, **highlighting each sentence as it is spoken** (via the native
286
+ selection, so document content is never modified). The speech-OUT complement
287
+ to the dictation plugin's speech-IN, and an accessibility win alongside the
288
+ accessibility checker.
289
+ - Browser-native via the Web Speech API (`window.speechSynthesis`) — **no library
290
+ bundled, no backend, no API key**. Reads cross-paragraph content correctly
291
+ (offset→DOM-range mapping). Public API: `editor.readAloud({ rate, voice })` /
292
+ `editor.stopReadAloud()`. `config.readAloudRate` (default 1.0) and
293
+ `config.readAloudHighlight` (default true). Gracefully detects browsers without
294
+ a synthesizer. Stops on page-hide; pauses on tab-hide. No core `rte.js` changes.
295
+ Verified live (8 checks: sentence split, cross-node highlight, selection-only
296
+ read, control bar, auto-start, toggle-close, stop-restores-caret, API surface).
297
+
298
+ **New plugin: `plugins/mermaiddiagram.js` — Mermaid diagrams (diagrams-as-code)**
299
+ - `editor.execCommand("insertdiagram")` (slash `/diagram`, toolbar) opens a dialog
300
+ to author Mermaid text (flowchart, sequence, gantt, pie, class, state…) with a
301
+ live preview, and renders it to SVG. Closes the diagrams-as-code gap vs GitHub /
302
+ GitLab / Notion / Obsidian. Double-click a block to edit it in place.
303
+ - No renderer is bundled (Mermaid is ~2 MB). Three tiers, mirroring the equation
304
+ editor's "host supplies KaTeX/MathJax" model: (1) `config.diagramResolver(source)`
305
+ → `Promise<{svg}|{imageUrl}>` for server-side render (e.g. Kroki); (2) a
306
+ `window.mermaid` instance loaded by the host page; (3) neither — the source is
307
+ stored and shown as a `<pre>` fallback, re-renderable later via
308
+ `editor.renderDiagrams()`. The Mermaid source always round-trips in
309
+ `data-diagram`, so blocks stay editable regardless of how they were rendered.
310
+ - Mermaid source is HTML-escaped into the attribute and the fallback `<pre>`;
311
+ blocks are `contenteditable="false"`. CSS injected at insert time. No core
312
+ `rte.js` changes. Verified live (14 checks: render via host renderer, BYOK
313
+ resolver, no-renderer fallback, injection-safety, `renderDiagrams()`, round-trip).
314
+
315
+ **Inline Markdown autoformat (core)**
316
+ - Typing the closing delimiter of an inline Markdown span now converts it in
317
+ place: `**bold**` / `__bold__` → `<strong>`, `*italic*` / `_italic_` → `<em>`,
318
+ `` `code` `` → `<code>`, `~~strike~~` → `<s>`. Completes the Markdown story —
319
+ the editor already had block shortcuts (`# `, `> `, `- `, `1. `, `---`); this
320
+ adds the inline half that Notion / Slack / GitHub / Typora users expect.
321
+ - The opening delimiter must sit at a **word boundary**, so `2*3*` (math),
322
+ `*.txt*` is reduced, `snake_case_` and intraword `a*b*` are left untouched.
323
+ Inert inside `<code>` / `<pre>` / links. Output uses the editor's own semantic
324
+ tags (`<strong>`/`<em>`), so it round-trips with the existing Markdown
325
+ import/export. Undoable with Ctrl+Z. Toggle with `config.markdownInlineEnabled`
326
+ (default `true`). No new toolbar/command surface — pure typing UX. Verified
327
+ live (14 checks: 8 conversions, 4 false-positive guards, code-inert, end-to-end
328
+ typing).
4
329
 
5
330
  **New plugin: `plugins/emailtoolkit.js` — email inline-style export**
6
331
  - `editor.getEmailHTML(options?)` returns email-client-safe HTML: a curated set