@richscripts/richtexteditor 2.6.3 → 2.6.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/angular/component.ts +16 -3
- package/integrations/shared.js +27 -0
- package/package.json +1 -1
- package/react/index.d.ts +7 -0
- package/react/index.js +25 -3
- package/richtexteditor/CHANGELOG.md +87 -0
- package/richtexteditor/all_plugins.js +366 -48
- package/richtexteditor/all_plugins.min.js +57 -57
- package/richtexteditor/lang/rte-lang-de.js +96 -0
- package/richtexteditor/lang/rte-lang-zh-Hans.js +181 -0
- package/richtexteditor/lang/rte-lang-zh-Hant.js +181 -0
- package/richtexteditor/plugins/all_plugins.js +366 -48
- package/richtexteditor/plugins/all_plugins.min.js +57 -57
- package/richtexteditor/plugins/comments.js +11 -1
- package/richtexteditor/plugins/crdt-engine.js +244 -17
- package/richtexteditor/plugins/crdt-engine.min.js +5 -5
- package/richtexteditor/plugins/documentimport.js +6 -11
- package/richtexteditor/plugins/insertcode.js +2 -2
- package/richtexteditor/plugins/readabilitystats.js +6 -11
- package/richtexteditor/plugins/sanitizer.js +136 -10
- package/richtexteditor/plugins/shadowdom.js +11 -1
- package/richtexteditor/plugins/slashcommand.js +78 -7
- package/richtexteditor/plugins/yjscollab.js +116 -5
- package/richtexteditor/rte-webcomponent.js +15 -0
- package/richtexteditor/rte.js +2 -2
- package/richtexteditor/rte_theme_default.css +13 -4
- package/richtexteditor/rte_theme_default.min.css +1 -1
- package/vue/index.js +12 -2
package/angular/component.ts
CHANGED
|
@@ -60,6 +60,9 @@ export class RichTextEditorAngularComponent implements AfterViewInit, OnChanges,
|
|
|
60
60
|
@Input() config: Record<string, unknown> = {};
|
|
61
61
|
@Input() value?: RichTextEditorStructuredInput;
|
|
62
62
|
@Input() valueFormat: RichTextEditorValueFormat = "html";
|
|
63
|
+
// Quiet period before the document is serialised and `change` is emitted.
|
|
64
|
+
// 0 restores the pre-2.6 behaviour of emitting on every keystroke.
|
|
65
|
+
@Input() changeDelay = 120;
|
|
63
66
|
|
|
64
67
|
@Output() change = new EventEmitter<RichTextEditorAngularChangeEvent>();
|
|
65
68
|
@Output() error = new EventEmitter<Error>();
|
|
@@ -68,6 +71,8 @@ export class RichTextEditorAngularComponent implements AfterViewInit, OnChanges,
|
|
|
68
71
|
@ViewChild("host", { static: true }) hostRef!: ElementRef<HTMLDivElement>;
|
|
69
72
|
|
|
70
73
|
private changeHandler: (() => void) | null = null;
|
|
74
|
+
private emitChange: ((() => void) & { flush(): void; cancel(): void }) | null = null;
|
|
75
|
+
private lastEmitted: unknown;
|
|
71
76
|
private editor: RichTextEditorInstance | null = null;
|
|
72
77
|
|
|
73
78
|
async ngAfterViewInit(): Promise<void> {
|
|
@@ -84,16 +89,22 @@ export class RichTextEditorAngularComponent implements AfterViewInit, OnChanges,
|
|
|
84
89
|
this.syncEditor(this.value);
|
|
85
90
|
}
|
|
86
91
|
|
|
87
|
-
|
|
92
|
+
// Coalesce: the editor fires `change` per keystroke and getEditorValue
|
|
93
|
+
// serialises the whole document (78 ms on a 3000-paragraph document).
|
|
94
|
+
// See shared.createChangeCoalescer.
|
|
95
|
+
this.emitChange = shared.createChangeCoalescer(() => {
|
|
88
96
|
if (!this.editor) {
|
|
89
97
|
return;
|
|
90
98
|
}
|
|
91
99
|
|
|
100
|
+
this.lastEmitted = shared.getEditorValue(this.editor, this.valueFormat);
|
|
92
101
|
this.change.emit({
|
|
93
102
|
editor: this.editor,
|
|
94
|
-
value:
|
|
103
|
+
value: this.lastEmitted
|
|
95
104
|
});
|
|
96
|
-
};
|
|
105
|
+
}, this.changeDelay == null ? 120 : this.changeDelay);
|
|
106
|
+
|
|
107
|
+
this.changeHandler = () => { this.emitChange(); };
|
|
97
108
|
|
|
98
109
|
this.editor.attachEvent("change", this.changeHandler);
|
|
99
110
|
this.ready.emit(this.editor);
|
|
@@ -114,6 +125,8 @@ export class RichTextEditorAngularComponent implements AfterViewInit, OnChanges,
|
|
|
114
125
|
|
|
115
126
|
ngOnDestroy(): void {
|
|
116
127
|
if (this.editor) {
|
|
128
|
+
// Flush first, or the last keystroke before teardown is lost.
|
|
129
|
+
if (this.emitChange) { this.emitChange.flush(); }
|
|
117
130
|
if (this.changeHandler && typeof this.editor.detachEvent === "function") {
|
|
118
131
|
this.editor.detachEvent("change", this.changeHandler);
|
|
119
132
|
}
|
package/integrations/shared.js
CHANGED
|
@@ -5141,6 +5141,32 @@ function getEditorValue(editor, format) {
|
|
|
5141
5141
|
return format === "json" ? editor.getJSON() : editor.getHTMLCode();
|
|
5142
5142
|
}
|
|
5143
5143
|
|
|
5144
|
+
// Coalesce the editor's `change` bursts before serialising the document.
|
|
5145
|
+
//
|
|
5146
|
+
// The editor fires `change` once per keystroke, and every wrapper responded by
|
|
5147
|
+
// calling getHTMLCode() -- serialising the WHOLE document. Measured on a
|
|
5148
|
+
// 3000-paragraph (134 KB) document that is 78 ms per keypress, and in controlled
|
|
5149
|
+
// mode it happens twice per keystroke (once to emit, once for the value-sync
|
|
5150
|
+
// comparison), so typing falls behind the keyboard.
|
|
5151
|
+
//
|
|
5152
|
+
// setTimeout, not requestAnimationFrame: rAF stops firing in a background tab,
|
|
5153
|
+
// which would strand the last edit unsent.
|
|
5154
|
+
function createChangeCoalescer(emit, delay) {
|
|
5155
|
+
var timer = null;
|
|
5156
|
+
var wrapped = function () {
|
|
5157
|
+
if (timer) { clearTimeout(timer); }
|
|
5158
|
+
timer = setTimeout(function () { timer = null; emit(); }, delay);
|
|
5159
|
+
};
|
|
5160
|
+
// Callers MUST flush on teardown, or the final keystroke before unmount is lost.
|
|
5161
|
+
wrapped.flush = function () {
|
|
5162
|
+
if (timer) { clearTimeout(timer); timer = null; emit(); }
|
|
5163
|
+
};
|
|
5164
|
+
wrapped.cancel = function () {
|
|
5165
|
+
if (timer) { clearTimeout(timer); timer = null; }
|
|
5166
|
+
};
|
|
5167
|
+
return wrapped;
|
|
5168
|
+
}
|
|
5169
|
+
|
|
5144
5170
|
function setEditorValue(editor, value, format) {
|
|
5145
5171
|
if (format === "json") {
|
|
5146
5172
|
editor.setJSON(value);
|
|
@@ -5176,6 +5202,7 @@ module.exports = {
|
|
|
5176
5202
|
serializeStructuredContent: serializeStructuredContent,
|
|
5177
5203
|
setDocumentPageSetup: setDocumentPageSetup,
|
|
5178
5204
|
setEditorValue: setEditorValue,
|
|
5205
|
+
createChangeCoalescer: createChangeCoalescer,
|
|
5179
5206
|
syncTableOfContents: syncTableOfContents,
|
|
5180
5207
|
toMarkdown: toMarkdown,
|
|
5181
5208
|
validateStructuredContent: validateStructuredContent
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "2.6.
|
|
6
|
+
"version": "2.6.4",
|
|
7
7
|
"description": "A full-featured JavaScript rich text editor (WYSIWYG) with toolbar, plugins, i18n support, a built-in AI Toolkit (Ask AI, AI Chat, AI Review drawer, BYOK resolver), structured content APIs (JSON/Markdown), and an optional collaboration / review stack: slash commands, @mentions, human-author Track Changes, threaded comments, revision history, and Yjs-backed presence. Ships first-class React, Vue, Angular and Svelte bindings, a headless \"bring your own UI\" core, and server-safe conversion helpers.",
|
|
8
8
|
"main": "./richtexteditor/rte.js",
|
|
9
9
|
"bin": {
|
package/react/index.d.ts
CHANGED
|
@@ -73,6 +73,13 @@ export interface RichTextEditorReactProps
|
|
|
73
73
|
onReady?: (editor: RichTextEditorInstance) => void;
|
|
74
74
|
value?: RichTextEditorStructuredInput;
|
|
75
75
|
valueFormat?: RichTextEditorValueFormat;
|
|
76
|
+
/**
|
|
77
|
+
* Quiet period in ms before the document is serialised and `onChange` fires.
|
|
78
|
+
* The editor emits `change` on every keystroke and serialising a large
|
|
79
|
+
* document is not free, so changes are coalesced. Defaults to 120.
|
|
80
|
+
* Set to 0 to emit synchronously on every change.
|
|
81
|
+
*/
|
|
82
|
+
changeDelay?: number;
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
export const RichTextEditorReact: React.ForwardRefExoticComponent<
|
package/react/index.js
CHANGED
|
@@ -112,6 +112,10 @@ var RichTextEditorReact = React.forwardRef(function RichTextEditorReact(props, f
|
|
|
112
112
|
var hostRef = React.useRef(null);
|
|
113
113
|
var editorRef = React.useRef(null);
|
|
114
114
|
var onChangeRef = React.useRef(props.onChange);
|
|
115
|
+
var lastEmittedRef = React.useRef(undefined);
|
|
116
|
+
// 0 disables coalescing and restores the pre-2.6 behaviour of emitting
|
|
117
|
+
// synchronously on every change.
|
|
118
|
+
var changeDelayRef = React.useRef(props.changeDelay == null ? 120 : props.changeDelay);
|
|
115
119
|
var onReadyRef = React.useRef(props.onReady);
|
|
116
120
|
var onErrorRef = React.useRef(props.onError);
|
|
117
121
|
var formatRef = React.useRef(props.valueFormat || "html");
|
|
@@ -132,6 +136,7 @@ var RichTextEditorReact = React.forwardRef(function RichTextEditorReact(props, f
|
|
|
132
136
|
React.useEffect(function () {
|
|
133
137
|
var disposed = false;
|
|
134
138
|
var changeHandler = null;
|
|
139
|
+
var emitChange = null;
|
|
135
140
|
var activeHost = hostRef.current;
|
|
136
141
|
|
|
137
142
|
var readyPromise = props.autoload === false
|
|
@@ -154,11 +159,19 @@ var RichTextEditorReact = React.forwardRef(function RichTextEditorReact(props, f
|
|
|
154
159
|
shared.setEditorValue(editor, initialValue, formatRef.current);
|
|
155
160
|
}
|
|
156
161
|
|
|
157
|
-
|
|
162
|
+
// Serialise at most once per quiet period rather than once per
|
|
163
|
+
// keystroke. lastEmittedRef lets the value-sync effect below skip a
|
|
164
|
+
// SECOND full serialisation when the parent hands back exactly what
|
|
165
|
+
// we just emitted, which is the common controlled-component case.
|
|
166
|
+
emitChange = shared.createChangeCoalescer(function () {
|
|
167
|
+
var value = shared.getEditorValue(editor, formatRef.current);
|
|
168
|
+
lastEmittedRef.current = value;
|
|
158
169
|
if (onChangeRef.current) {
|
|
159
|
-
onChangeRef.current(
|
|
170
|
+
onChangeRef.current(value, editor);
|
|
160
171
|
}
|
|
161
|
-
};
|
|
172
|
+
}, changeDelayRef.current);
|
|
173
|
+
|
|
174
|
+
changeHandler = function () { emitChange(); };
|
|
162
175
|
|
|
163
176
|
editor.attachEvent("change", changeHandler);
|
|
164
177
|
|
|
@@ -174,6 +187,8 @@ var RichTextEditorReact = React.forwardRef(function RichTextEditorReact(props, f
|
|
|
174
187
|
|
|
175
188
|
return function () {
|
|
176
189
|
disposed = true;
|
|
190
|
+
// Flush before teardown or the last keystroke never reaches onChange.
|
|
191
|
+
if (emitChange) { emitChange.flush(); }
|
|
177
192
|
var ed = editorRef.current;
|
|
178
193
|
if (ed) {
|
|
179
194
|
if (changeHandler && typeof ed.detachEvent === "function") {
|
|
@@ -198,6 +213,13 @@ var RichTextEditorReact = React.forwardRef(function RichTextEditorReact(props, f
|
|
|
198
213
|
}
|
|
199
214
|
|
|
200
215
|
var nextHtml = shared.normalizeStructuredContent(props.value);
|
|
216
|
+
// Fast path: the parent handed back exactly what we last emitted, which is
|
|
217
|
+
// what a controlled component does on every keystroke. Comparing against the
|
|
218
|
+
// remembered string avoids a second full getHTMLCode() of the document.
|
|
219
|
+
if (lastEmittedRef.current !== undefined &&
|
|
220
|
+
shared.normalizeStructuredContent(lastEmittedRef.current) === nextHtml) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
201
223
|
if (editorRef.current.getHTMLCode() !== nextHtml) {
|
|
202
224
|
shared.setEditorValue(editorRef.current, props.value, props.valueFormat || "html");
|
|
203
225
|
}
|
|
@@ -1,5 +1,92 @@
|
|
|
1
1
|
# RichTextEditor Changelog
|
|
2
2
|
|
|
3
|
+
## Version 2.6.4 — core 2.5.7: undo, IME, sanitizer, collaboration and accessibility fixes
|
|
4
|
+
|
|
5
|
+
> Core bumped to 2.5.7. The earlier build of this work was labelled 2.5.6, which is
|
|
6
|
+
> already published (protected rte.js 564,563). Two different builds must not share a
|
|
7
|
+
> version number, least of all on npm and NuGet where a version cannot be reused or
|
|
8
|
+
> corrected after the fact.
|
|
9
|
+
>
|
|
10
|
+
> npm was the last channel to receive this build. The website, the customer download
|
|
11
|
+
> zips and NuGet (RichTextBox 1.0.8) were already serving core 2.5.7 / protected
|
|
12
|
+
> rte.js 565,112 while npm was still on 2.6.3 / core 2.5.6 — so npm users did not have
|
|
13
|
+
> the undo fix below, which looks exactly like data loss. Verified byte-identical to
|
|
14
|
+
> the live site before publishing.
|
|
15
|
+
|
|
16
|
+
### One Ctrl+Z after loading a document blanked it
|
|
17
|
+
`setHTMLCode()` did not seed the undo baseline, so the history baseline stayed the *empty* document and the last available undo discarded the loaded content instead of returning to it. Every framework wrapper was affected, because React, Vue and Angular all load through `setEditorValue()` -> `setHTMLCode()`. Load a document, type one character, press Ctrl+Z, and the document went blank. Redo recovered it, but it looked exactly like data loss on the most common keystroke in an editor. Typing granularity is unchanged; with two typing bursts the first undo always behaved correctly, which is why the bug hid behind however many edits you happened to make. (core rte.js)
|
|
18
|
+
|
|
19
|
+
### IME composition fired a change event per phonetic keystroke
|
|
20
|
+
There was no `compositionstart`/`compositionend` handling anywhere — not in the core, not in any wrapper. Composing 你好 produced 4 change events instead of 1, and intermediate phonetic buffers could become undo steps. Every `change` makes the framework wrappers serialise the whole document, and a controlled component that writes the value back mid-composition tears down the IME buffer. Now 0 events while composing and 1 on commit. (core rte.js)
|
|
21
|
+
|
|
22
|
+
### Framework wrappers serialised the whole document on every keystroke
|
|
23
|
+
React, Vue and Angular each called `getEditorValue()` — a full `getHTMLCode()` — inside the `change` handler with no coalescing, and in controlled mode a second time for the value comparison. Measured on a 3,000-paragraph (134 KB) document, typing 19 characters cost 19 serialisations and 1,735 ms; it now costs 1 and 90 ms. New optional `changeDelay` prop (default 120 ms; 0 restores the old synchronous behaviour).
|
|
24
|
+
|
|
25
|
+
### Unquoted attribute values had their first character duplicated
|
|
26
|
+
The paste/import parser passed the value's *first character* as the quote and the *rest* as the value, then wrapped the value in that "quote": `class=hello` became `class="helloh"`, `title=abc` became `title="abca"` (visibly wrong tooltip text), and single-character values vanished — `cellpadding=0` was dropped entirely. Unquoted attributes are legal HTML and are what Word emits, which is also why Word's `class=MsoNormal` survived the Mso filter as `MsoNormalM` and leaked into saved content. (core rte.js)
|
|
27
|
+
|
|
28
|
+
### HTML comments are preserved
|
|
29
|
+
`sanitizer.js` deleted every comment, justified only by the remark "comments can hide markup". That is false — serialise `<!--<script>alert(1)</script>-->`, re-parse it, and the script is inert. What is genuinely unsafe is a small enumerable set of comment *data* that cannot survive serialisation (containing `-->` or `--!>`, or starting with `>` or `->`), which breaks out on re-parse; only those are dropped now. This restores CMS marker round-tripping and Outlook conditional comments, the mechanism `emailtoolkit.js` needs. The live-DOM MutationObserver had to change too: it skipped every non-element node, which was safe only while comments were being deleted unconditionally.
|
|
30
|
+
|
|
31
|
+
### Tag and attribute allowlists accept patterns
|
|
32
|
+
`sanitizerAllowTags` / `sanitizerAllowAttributes` now take `"x-*"` wildcards and RegExp, not just exact names — the difference between a usable General-HTML-Support equivalent and one that requires an integrator to enumerate every element a CMS might emit. Wildcards are matched by hand rather than compiled to a RegExp, so a config value can never be a regex-injection vector. A pattern widens what is kept, never what executes.
|
|
33
|
+
|
|
34
|
+
### Split-button dropdown arrows failed WCAG 2.2 target size
|
|
35
|
+
The dropdown half of a split button was 14x24 CSS px, failing SC 2.5.8 Target Size (Minimum), Level AA, which requires 24x24; the spacing exception cannot apply because the two halves are adjacent by construction. Both the arrow and its container were widened (42 -> 52 px), because widening the arrow alone made it worse — the fixed-width flex row shrank both halves to 19 px. Split buttons are now 48 px wide instead of 38 px. SC 2.5.5 (44 px, Level AAA) is unchanged and not claimed.
|
|
36
|
+
|
|
37
|
+
Found by running features end-to-end on the source build; every item has a regression test in RTEJSWeb/_tests.
|
|
38
|
+
|
|
39
|
+
### 16 slash commands were unreachable
|
|
40
|
+
Plugins that sort before slashcommand.js registered their slash entries before the registry existed and were silently dropped (`/import document`, `/readability stats`), and slashcommand.js evaluated its own feature gates once at init, before later-sorting plugins defined their APIs (sort table asc/desc, RTL/LTR/auto direction, watermark, table of contents, typewriter, focus mode). Defaults are now refreshed after all plugins initialise and when the menu opens; entries for cross-reference, restricted editing and dictation (opt-in) were added. Menu: 68 -> 82.
|
|
41
|
+
|
|
42
|
+
### The slash menu is now localizable
|
|
43
|
+
Every entry (title, description, section) resolves through the language packs with an English fallback. zh-Hans, zh-Hant and de packs ship the keys (titles, sections, descriptions for Chinese); the plugin toolbar tooltips that stayed English (accessibility checker, comments, minimap, outline, AI assist, text-part language) are covered too.
|
|
44
|
+
|
|
45
|
+
### Ctrl+F opened a different Find dialog than the toolbar button
|
|
46
|
+
Ctrl+F and `editor.openFindDialog()` now open the same localized Find & Replace panel as the toolbar command; the hard-coded-English dialog remains only as a fallback. (core rte.js — ships with the next protected build)
|
|
47
|
+
|
|
48
|
+
### Shadow DOM: an inline theme stylesheet was not adopted
|
|
49
|
+
A theme injected as an inline `<style>` (Vite / webpack style-loader) left a shadow-root editor unstyled (39,726 px toolbar). Inline styles are now recognised by content.
|
|
50
|
+
|
|
51
|
+
### `<rich-text-editor>` child markup
|
|
52
|
+
Markup written between the tags becomes the initial value when no `value` attribute is set (like `<textarea>`) and is consumed either way instead of rendering beside the editor.
|
|
53
|
+
|
|
54
|
+
### Real-time collaboration: text sync through a relay was broken on the documented path
|
|
55
|
+
With `collabTextSync: "crdt"`, a `Y.Doc` built by your own Yjs (npm / esm `yjs` + `y-websocket`, exactly as the docs show) could not receive the engine's nodes — presence worked and the status reported `crdt`, but text never reached other peers. The plugin now bridges such a doc into the engine's Yjs and relays updates both ways; pass your module as `attach({ Y })` (the docs' snippets do). Without it the plugin fails loudly with a `fallbackReason` instead of silently claiming to sync. Regression test: `_tests/collab-relay-sync.test.js` (two real browser pages through a WebSocket relay).
|
|
56
|
+
|
|
57
|
+
### CRDT engine: comment anchors, mentions and tracked changes now travel to peers
|
|
58
|
+
In `crdt` text-sync mode, inline elements that are not formatting (comment anchors, @mention chips, tracked-change spans, footnote references) folded into plain text inside the Yjs document, so collaborators never received them and any re-render dropped them locally. The engine now carries such elements as a passthrough mark (tag + attributes) and renders them back as one element with formatting nested inside.
|
|
59
|
+
|
|
60
|
+
### CRDT engine: a local edit could be wiped by a same-tick document update
|
|
61
|
+
The engine re-rendered from Yjs on any non-local update and discarded not-yet-flushed local DOM mutations; writing a comment's ledger entry right after inserting its anchor erased the anchor. Pending local mutations are now committed before a re-render, and the plugin's own ledger writes no longer trigger one.
|
|
62
|
+
|
|
63
|
+
### comments.add accepts a DOM Range
|
|
64
|
+
`editor.comments.add({ text, anchor: range })` now highlights the range; previously only an anchor object with a `text` field did, so a plain Range silently produced a comment without a highlight.
|
|
65
|
+
|
|
66
|
+
### CRDT engine: concurrent edits to one paragraph no longer duplicate it
|
|
67
|
+
Two collaborators typing in the same paragraph at the same time used to converge to both versions of the paragraph body. The engine now applies minimal text edits (common prefix/suffix) so concurrent typing merges character by character.
|
|
68
|
+
|
|
69
|
+
### Undo in collaborative sessions is now scoped to your own edits
|
|
70
|
+
With `collabTextSync: "crdt"`, Ctrl+Z ran the editor's snapshot undo and could revert other people's changes on every peer. Undo/redo now use the engine's per-author undo manager while collaboration is active.
|
|
71
|
+
|
|
72
|
+
### insertcode: storage access guarded
|
|
73
|
+
`sessionStorage` reads/writes are wrapped, so the command no longer throws in sandboxed or opaque-origin embeds.
|
|
74
|
+
|
|
75
|
+
## Version 2.6.3 — Markdown task lists + escaped-character fixes (core 2.5.6)
|
|
76
|
+
|
|
77
|
+
More markdown round-trip completeness (core rte.js 2.5.5→2.5.6):
|
|
78
|
+
- GFM task lists now round-trip: a todo item (rte-task-item / data-checked) exports as "- [x]"/"- [ ]" and imports back to the same checkbox markup (nested tasks included). A normal list stays a normal list.
|
|
79
|
+
- Backslash-escaped markdown characters (* _ [ ) imported wrong — they were parsed as formatting and leaked NUL bytes into the output. They now import as the literal character.
|
|
80
|
+
- A literal < or > in text round-tripped as < ; the exporter no longer backslash-escapes angle brackets (the importer already escapes them safely).
|
|
81
|
+
|
|
82
|
+
## Version 2.6.2 — Markdown and .docx round-trip fixes (core 2.5.5)
|
|
83
|
+
|
|
84
|
+
Round-trip fixes found by dogfooding export→import (core rte.js 2.5.4→2.5.5):
|
|
85
|
+
- Markdown import threw on any link (URL sanitizer out of scope) — fixed.
|
|
86
|
+
- Markdown GFM tables + indented nested lists did not import (became paragraphs) — now parsed (table header → <th>; nested via an indentation stack).
|
|
87
|
+
- Inline code was double-escaped — fixed.
|
|
88
|
+
- .docx table headers (<w:tblHeader/>) and blockquotes (Quote style) dropped on import — now <th>/<blockquote>.
|
|
89
|
+
|
|
3
90
|
## Version 2.6.0 — Layout tables, crash recovery, shadow DOM, web component (Pass 69)
|
|
4
91
|
|
|
5
92
|
> `rte.js` is unchanged from 2.5.4 — every item below is a plugin or a companion
|