@richscripts/richtexteditor 2.14.0 → 2.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +173 -10
- package/angular/build-manifest.json +23 -0
- package/angular/index.d.ts +71 -76
- package/angular/index.mjs +2 -0
- package/angular/package.json +4 -0
- package/headless/index.d.ts +3 -1
- package/headless/index.js +72 -16
- package/index.d.ts +33 -54
- package/integrations/initialization-scope.js +27 -0
- package/integrations/native-mount.js +30 -0
- package/integrations/shared.d.ts +23 -1
- package/integrations/shared.js +58 -22
- package/package.json +25 -3
- package/react/index.js +118 -37
- package/react/index.mjs +29 -0
- package/richtexteditor/all_plugins.js +7113 -1111
- package/richtexteditor/all_plugins.min.js +62 -59
- package/richtexteditor/lang/emoji/LICENSE-CLDR.txt +41 -0
- package/richtexteditor/lang/emoji/rte-emoji-de.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-es.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-fr.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-it.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-ja.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-ko.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-nl.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-pt-pt.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-pt.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-ru.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-zh-Hans.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-zh-Hant.js +3 -0
- package/richtexteditor/lang/rte-lang-af.js +1 -0
- package/richtexteditor/lang/rte-lang-ar.js +1 -0
- package/richtexteditor/lang/rte-lang-bg.js +1 -0
- package/richtexteditor/lang/rte-lang-bn.js +1 -0
- package/richtexteditor/lang/rte-lang-bs.js +1 -0
- package/richtexteditor/lang/rte-lang-ca.js +1 -0
- package/richtexteditor/lang/rte-lang-cs.js +1 -0
- package/richtexteditor/lang/rte-lang-cy.js +1 -0
- package/richtexteditor/lang/rte-lang-da.js +1 -0
- package/richtexteditor/lang/rte-lang-de.js +1017 -2
- package/richtexteditor/lang/rte-lang-el.js +1 -0
- package/richtexteditor/lang/rte-lang-en.js +1 -0
- package/richtexteditor/lang/rte-lang-es.js +1023 -2
- package/richtexteditor/lang/rte-lang-et.js +1 -0
- package/richtexteditor/lang/rte-lang-fa.js +1 -0
- package/richtexteditor/lang/rte-lang-fi.js +1 -0
- package/richtexteditor/lang/rte-lang-fil.js +1 -0
- package/richtexteditor/lang/rte-lang-fj.js +1 -0
- package/richtexteditor/lang/rte-lang-fr.js +1023 -2
- package/richtexteditor/lang/rte-lang-ga.js +1 -0
- package/richtexteditor/lang/rte-lang-gu.js +1 -0
- package/richtexteditor/lang/rte-lang-he.js +1 -0
- package/richtexteditor/lang/rte-lang-hi.js +1 -0
- package/richtexteditor/lang/rte-lang-hr.js +1 -0
- package/richtexteditor/lang/rte-lang-ht.js +1 -0
- package/richtexteditor/lang/rte-lang-hu.js +1 -0
- package/richtexteditor/lang/rte-lang-id.js +1 -0
- package/richtexteditor/lang/rte-lang-is.js +1 -0
- package/richtexteditor/lang/rte-lang-it.js +1023 -2
- package/richtexteditor/lang/rte-lang-ja.js +1023 -2
- package/richtexteditor/lang/rte-lang-kn.js +1 -0
- package/richtexteditor/lang/rte-lang-ko.js +1023 -2
- package/richtexteditor/lang/rte-lang-lt.js +1 -0
- package/richtexteditor/lang/rte-lang-lv.js +1 -0
- package/richtexteditor/lang/rte-lang-mg.js +1 -0
- package/richtexteditor/lang/rte-lang-mi.js +1 -0
- package/richtexteditor/lang/rte-lang-ml.js +1 -0
- package/richtexteditor/lang/rte-lang-mr.js +1 -0
- package/richtexteditor/lang/rte-lang-ms.js +1 -0
- package/richtexteditor/lang/rte-lang-mt.js +1 -0
- package/richtexteditor/lang/rte-lang-mww.js +1 -0
- package/richtexteditor/lang/rte-lang-nb.js +1 -0
- package/richtexteditor/lang/rte-lang-nl.js +1023 -2
- package/richtexteditor/lang/rte-lang-otq.js +1 -0
- package/richtexteditor/lang/rte-lang-pa.js +1 -0
- package/richtexteditor/lang/rte-lang-pl.js +1 -0
- package/richtexteditor/lang/rte-lang-pt-pt.js +1023 -2
- package/richtexteditor/lang/rte-lang-pt.js +1023 -2
- package/richtexteditor/lang/rte-lang-ro.js +1 -0
- package/richtexteditor/lang/rte-lang-ru.js +1023 -2
- package/richtexteditor/lang/rte-lang-sk.js +1 -0
- package/richtexteditor/lang/rte-lang-sl.js +1 -0
- package/richtexteditor/lang/rte-lang-sm.js +1 -0
- package/richtexteditor/lang/rte-lang-sr-Cyrl.js +1 -0
- package/richtexteditor/lang/rte-lang-sr-Latn.js +1 -0
- package/richtexteditor/lang/rte-lang-sv.js +1 -0
- package/richtexteditor/lang/rte-lang-sw.js +1 -0
- package/richtexteditor/lang/rte-lang-ta.js +1 -0
- package/richtexteditor/lang/rte-lang-te.js +1 -0
- package/richtexteditor/lang/rte-lang-th.js +1 -0
- package/richtexteditor/lang/rte-lang-tlh-Latn.js +1 -0
- package/richtexteditor/lang/rte-lang-tlh-Piqd.js +1 -0
- package/richtexteditor/lang/rte-lang-to.js +1 -0
- package/richtexteditor/lang/rte-lang-tr.js +1 -0
- package/richtexteditor/lang/rte-lang-ty.js +1 -0
- package/richtexteditor/lang/rte-lang-uk.js +1 -0
- package/richtexteditor/lang/rte-lang-ur.js +1 -0
- package/richtexteditor/lang/rte-lang-vi.js +1 -0
- package/richtexteditor/lang/rte-lang-yua.js +1 -0
- package/richtexteditor/lang/rte-lang-zh-Hans.js +1017 -2
- package/richtexteditor/lang/rte-lang-zh-Hant.js +1017 -2
- package/richtexteditor/plugins/a11yenhance.js +2 -1
- package/richtexteditor/plugins/a11yenhance.min.js +1 -1
- package/richtexteditor/plugins/accessibilitychecker.js +252 -60
- package/richtexteditor/plugins/accessibilitychecker.min.js +1 -1
- package/richtexteditor/plugins/aitoolkit.js +966 -303
- package/richtexteditor/plugins/aitoolkit.min.js +7 -7
- package/richtexteditor/plugins/all_plugins.js +7113 -1111
- package/richtexteditor/plugins/all_plugins.min.js +62 -59
- package/richtexteditor/plugins/autocorrect.js +66 -31
- package/richtexteditor/plugins/autocorrect.min.js +1 -1
- package/richtexteditor/plugins/autoembed.js +17 -0
- package/richtexteditor/plugins/autoembed.min.js +1 -1
- package/richtexteditor/plugins/blocktypes.js +18 -9
- package/richtexteditor/plugins/blocktypes.min.js +1 -1
- package/richtexteditor/plugins/bookmarkcard.js +12 -5
- package/richtexteditor/plugins/bookmarkcard.min.js +2 -2
- package/richtexteditor/plugins/changecase.js +72 -19
- package/richtexteditor/plugins/changecase.min.js +1 -1
- package/richtexteditor/plugins/charlimit.js +62 -2
- package/richtexteditor/plugins/charlimit.min.js +2 -2
- package/richtexteditor/plugins/comments.js +458 -59
- package/richtexteditor/plugins/comments.min.js +4 -3
- package/richtexteditor/plugins/contentminimap.js +25 -6
- package/richtexteditor/plugins/contentminimap.min.js +1 -1
- package/richtexteditor/plugins/crossreference.js +1 -1
- package/richtexteditor/plugins/crossreference.min.js +1 -1
- package/richtexteditor/plugins/dialoga11y.js +26 -4
- package/richtexteditor/plugins/dialoga11y.min.js +1 -1
- package/richtexteditor/plugins/dictation.js +1 -1
- package/richtexteditor/plugins/documentimport.js +220 -51
- package/richtexteditor/plugins/documentimport.min.js +4 -4
- package/richtexteditor/plugins/documentoutline.js +88 -38
- package/richtexteditor/plugins/documentoutline.min.js +1 -1
- package/richtexteditor/plugins/docxexport.js +204 -32
- package/richtexteditor/plugins/docxexport.min.js +2 -2
- package/richtexteditor/plugins/draghandle.js +267 -3
- package/richtexteditor/plugins/draghandle.min.js +1 -1
- package/richtexteditor/plugins/emailtoolkit.js +28 -14
- package/richtexteditor/plugins/emailtoolkit.min.js +1 -1
- package/richtexteditor/plugins/emojiautocomplete.js +110 -56
- package/richtexteditor/plugins/emojiautocomplete.min.js +1 -1
- package/richtexteditor/plugins/footnotes.js +65 -11
- package/richtexteditor/plugins/footnotes.min.js +1 -1
- package/richtexteditor/plugins/formatpainter.js +10 -0
- package/richtexteditor/plugins/formatpainter.min.js +1 -1
- package/richtexteditor/plugins/formattingmarks.js +53 -8
- package/richtexteditor/plugins/formattingmarks.min.js +1 -1
- package/richtexteditor/plugins/gapcursor.js +60 -0
- package/richtexteditor/plugins/gapcursor.min.js +1 -1
- package/richtexteditor/plugins/ghostcomplete.js +1 -1
- package/richtexteditor/plugins/html2pdf.js +1 -1
- package/richtexteditor/plugins/html2pdf.min.js +1 -1
- package/richtexteditor/plugins/imageeditor.js +12 -6
- package/richtexteditor/plugins/imageeditor.min.js +1 -1
- package/richtexteditor/plugins/importstyles.js +1 -1
- package/richtexteditor/plugins/inlinecode.js +25 -0
- package/richtexteditor/plugins/inlinecode.min.js +1 -1
- package/richtexteditor/plugins/insertcode.js +21 -3
- package/richtexteditor/plugins/insertcode.min.js +4 -4
- package/richtexteditor/plugins/insertemoji.js +112 -15
- package/richtexteditor/plugins/insertemoji.min.js +1 -1
- package/richtexteditor/plugins/insertgallery.js +42 -36
- package/richtexteditor/plugins/insertgallery.min.js +1 -1
- package/richtexteditor/plugins/inserttemplate.js +33 -17
- package/richtexteditor/plugins/inserttemplate.min.js +1 -1
- package/richtexteditor/plugins/keyboarda11y.js +66 -3
- package/richtexteditor/plugins/keyboarda11y.min.js +1 -1
- package/richtexteditor/plugins/layouttable.js +20 -0
- package/richtexteditor/plugins/layouttable.min.js +1 -1
- package/richtexteditor/plugins/linkchecker.js +29 -9
- package/richtexteditor/plugins/linkchecker.min.js +1 -1
- package/richtexteditor/plugins/localdraft.js +51 -7
- package/richtexteditor/plugins/localdraft.min.js +1 -1
- package/richtexteditor/plugins/markdownexport.js +159 -0
- package/richtexteditor/plugins/markdownexport.min.js +2 -2
- package/richtexteditor/plugins/mathdialog.js +11 -5
- package/richtexteditor/plugins/mathdialog.min.js +1 -1
- package/richtexteditor/plugins/mentionplugin.js +88 -25
- package/richtexteditor/plugins/mentionplugin.min.js +1 -1
- package/richtexteditor/plugins/mermaiddiagram.js +9 -3
- package/richtexteditor/plugins/mermaiddiagram.min.js +2 -2
- package/richtexteditor/plugins/multilevellist.js +4 -0
- package/richtexteditor/plugins/multilevellist.min.js +1 -1
- package/richtexteditor/plugins/pdfexport.js +235 -35
- package/richtexteditor/plugins/pdfexport.min.js +12 -12
- package/richtexteditor/plugins/permanentpen.js +3 -3
- package/richtexteditor/plugins/permanentpen.min.js +1 -1
- package/richtexteditor/plugins/readabilitystats.js +25 -11
- package/richtexteditor/plugins/readabilitystats.min.js +1 -1
- package/richtexteditor/plugins/readaloud.js +29 -15
- package/richtexteditor/plugins/readaloud.min.js +1 -1
- package/richtexteditor/plugins/restrictedediting.js +166 -0
- package/richtexteditor/plugins/restrictedediting.min.js +2 -2
- package/richtexteditor/plugins/revisionhistory.js +169 -37
- package/richtexteditor/plugins/revisionhistory.min.js +3 -3
- package/richtexteditor/plugins/rubyannotation.js +19 -9
- package/richtexteditor/plugins/rubyannotation.min.js +1 -1
- package/richtexteditor/plugins/sanitizer.js +86 -4
- package/richtexteditor/plugins/sanitizer.min.js +1 -1
- package/richtexteditor/plugins/slashcommand.js +30 -16
- package/richtexteditor/plugins/slashcommand.min.js +2 -2
- package/richtexteditor/plugins/smartchips.js +36 -22
- package/richtexteditor/plugins/smartchips.min.js +2 -2
- package/richtexteditor/plugins/spellcheck.js +17 -8
- package/richtexteditor/plugins/spellcheck.min.js +2 -2
- package/richtexteditor/plugins/stateinspector.js +19 -9
- package/richtexteditor/plugins/stateinspector.min.js +2 -2
- package/richtexteditor/plugins/structured-content-bridge.js +56 -24
- package/richtexteditor/plugins/structured-content-bridge.min.js +7 -7
- package/richtexteditor/plugins/tableofcontents.js +5 -2
- package/richtexteditor/plugins/tableofcontents.min.js +1 -1
- package/richtexteditor/plugins/tabletools.js +388 -3
- package/richtexteditor/plugins/tabletools.min.js +1 -1
- package/richtexteditor/plugins/textdirection.js +18 -5
- package/richtexteditor/plugins/textdirection.min.js +1 -1
- package/richtexteditor/plugins/textpartlanguage.js +161 -32
- package/richtexteditor/plugins/textpartlanguage.min.js +1 -1
- package/richtexteditor/plugins/todolist.js +90 -6
- package/richtexteditor/plugins/todolist.min.js +1 -1
- package/richtexteditor/plugins/trackedchanges.js +1812 -25
- package/richtexteditor/plugins/trackedchanges.min.js +5 -3
- package/richtexteditor/plugins/wordexport.js +2 -2
- package/richtexteditor/plugins/yjscollab.js +9 -4
- package/richtexteditor/plugins/yjscollab.min.js +1 -1
- package/richtexteditor/plugins/zz_richtextbox_dialog_style.js +22 -13
- package/richtexteditor/plugins/zz_richtextbox_dialog_style.min.js +1 -1
- package/richtexteditor/rte-config.js +131 -50
- package/richtexteditor/rte-headless.js +19 -4
- package/richtexteditor/rte-webcomponent.js +89 -16
- package/richtexteditor/rte.js +133 -52
- package/richtexteditor/runtime/colorpicker_more_ns.htm +93 -16
- package/scripts/build-angular.mjs +97 -0
- package/svelte/index.d.ts +2 -1
- package/svelte/index.js +80 -9
- package/vue/index.js +130 -34
- package/angular/component.ts +0 -297
- package/angular/index.ts +0 -24
|
@@ -66,6 +66,86 @@ function RTE_Plugin_RestrictedEditing() {
|
|
|
66
66
|
refreshStyles();
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
+
// 2026-10-14 The lockdown is enforced, not only drawn. A paragraph that holds a field stays contenteditable so the
|
|
70
|
+
// caret can reach the field, which left the protected text around the field editable; Delete over a locked
|
|
71
|
+
// selection, Alt+Arrow (move block) and Ctrl+Shift+D (copy block) changed protected text too, because the core
|
|
72
|
+
// handles those keys itself. While the lockdown is on, a key or an input whose selection is not entirely inside
|
|
73
|
+
// one marked region is cancelled at the editing window, in the capture phase, before anything else sees it.
|
|
74
|
+
// Backspace at a field's start and Delete at its end would reach outside it, so they are cancelled as well.
|
|
75
|
+
// tools/restricted/restricted-audit.mjs
|
|
76
|
+
var guardedWindows = [];
|
|
77
|
+
function regionOf(node) {
|
|
78
|
+
var el = node && node.nodeType === 1 ? node : (node && node.parentNode);
|
|
79
|
+
while (el && el.nodeType === 1) {
|
|
80
|
+
if (el.getAttribute && el.getAttribute(editableAttr) === "true") return el;
|
|
81
|
+
el = el.parentNode;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
// the marked region that holds the whole selection, or null
|
|
86
|
+
function selectionRegion(doc) {
|
|
87
|
+
var sel = doc.getSelection();
|
|
88
|
+
if (!sel || sel.rangeCount === 0) return null;
|
|
89
|
+
var r = sel.getRangeAt(0);
|
|
90
|
+
var a = regionOf(r.startContainer), b = regionOf(r.endContainer);
|
|
91
|
+
return a && a === b ? { region: a, range: r } : null;
|
|
92
|
+
}
|
|
93
|
+
function atEdge(doc, region, range, atStart) {
|
|
94
|
+
var probe = doc.createRange();
|
|
95
|
+
probe.selectNodeContents(region);
|
|
96
|
+
if (atStart) probe.setEnd(range.startContainer, range.startOffset);
|
|
97
|
+
else probe.setStart(range.endContainer, range.endOffset);
|
|
98
|
+
return probe.toString() === "";
|
|
99
|
+
}
|
|
100
|
+
function editsDocument(e) {
|
|
101
|
+
var k = e.key || "";
|
|
102
|
+
if (e.isComposing) return true;
|
|
103
|
+
if (k === "Backspace" || k === "Delete" || k === "Enter" || k === "Tab") return true;
|
|
104
|
+
if (e.altKey && (k === "ArrowUp" || k === "ArrowDown")) return true;
|
|
105
|
+
if (e.ctrlKey || e.metaKey) {
|
|
106
|
+
var c = k.toLowerCase();
|
|
107
|
+
return c === "x" || c === "v" || c === "b" || c === "i" || c === "u" || c === "d";
|
|
108
|
+
}
|
|
109
|
+
return k.length === 1 && !e.altKey;
|
|
110
|
+
}
|
|
111
|
+
function guardKey(e) {
|
|
112
|
+
if (!enabled || !editsDocument(e)) return;
|
|
113
|
+
var doc = e.target && (e.target.ownerDocument || e.target);
|
|
114
|
+
if (!doc || !doc.getSelection) return;
|
|
115
|
+
var hit = selectionRegion(doc);
|
|
116
|
+
var refuse = !hit;
|
|
117
|
+
if (hit && hit.range.collapsed) {
|
|
118
|
+
if (e.key === "Backspace" && atEdge(doc, hit.region, hit.range, true)) refuse = true;
|
|
119
|
+
if (e.key === "Delete" && atEdge(doc, hit.region, hit.range, false)) refuse = true;
|
|
120
|
+
}
|
|
121
|
+
if (hit && (e.altKey && (e.key === "ArrowUp" || e.key === "ArrowDown"))) refuse = true;
|
|
122
|
+
if (hit && (e.ctrlKey || e.metaKey) && e.shiftKey && String(e.key).toLowerCase() === "d") refuse = true;
|
|
123
|
+
if (!refuse) return;
|
|
124
|
+
e.preventDefault();
|
|
125
|
+
e.stopImmediatePropagation();
|
|
126
|
+
}
|
|
127
|
+
function guardInput(e) {
|
|
128
|
+
if (!enabled) return;
|
|
129
|
+
var type = e.inputType || "";
|
|
130
|
+
if (type.indexOf("history") === 0) return;
|
|
131
|
+
var doc = e.target && (e.target.ownerDocument || e.target);
|
|
132
|
+
if (!doc || !doc.getSelection) return;
|
|
133
|
+
if (selectionRegion(doc)) return;
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
e.stopImmediatePropagation();
|
|
136
|
+
}
|
|
137
|
+
function ensureGuards() {
|
|
138
|
+
var body = getEditable();
|
|
139
|
+
var win = body && body.ownerDocument && body.ownerDocument.defaultView;
|
|
140
|
+
if (!win || guardedWindows.indexOf(win) >= 0) return;
|
|
141
|
+
guardedWindows.push(win);
|
|
142
|
+
win.addEventListener("keydown", guardKey, true);
|
|
143
|
+
win.addEventListener("beforeinput", guardInput, true);
|
|
144
|
+
win.addEventListener("cut", function (e) { if (enabled && !selectionRegion(e.target.ownerDocument || e.target)) { e.preventDefault(); e.stopImmediatePropagation(); } }, true);
|
|
145
|
+
win.addEventListener("paste", function (e) { if (enabled && !selectionRegion(e.target.ownerDocument || e.target)) { e.preventDefault(); e.stopImmediatePropagation(); } }, true);
|
|
146
|
+
win.addEventListener("drop", function (e) { if (enabled) { e.preventDefault(); e.stopImmediatePropagation(); } }, true);
|
|
147
|
+
}
|
|
148
|
+
|
|
69
149
|
function getEditable() {
|
|
70
150
|
return editor.getEditable ? editor.getEditable() : null;
|
|
71
151
|
}
|
|
@@ -111,6 +191,7 @@ function RTE_Plugin_RestrictedEditing() {
|
|
|
111
191
|
}
|
|
112
192
|
// Make the body itself NOT editable; rely on per-element contenteditable.
|
|
113
193
|
body.setAttribute("contenteditable", "false");
|
|
194
|
+
ensureGuards();
|
|
114
195
|
refreshStyles();
|
|
115
196
|
return true;
|
|
116
197
|
}
|
|
@@ -139,6 +220,38 @@ function RTE_Plugin_RestrictedEditing() {
|
|
|
139
220
|
if (!sel || sel.rangeCount === 0) return null;
|
|
140
221
|
var range = sel.getRangeAt(0);
|
|
141
222
|
if (range.collapsed) return null;
|
|
223
|
+
// 2026-10-02 A selection that crosses blocks marks the selected text of EACH block. One span
|
|
224
|
+
// went AROUND the blocks: two paragraphs became four (<p>al</p><span><p>pha</p><p>be</p>
|
|
225
|
+
// </span><p>ta</p>), list items and table cells got a span inside <ul> / <tr> - and that
|
|
226
|
+
// span, a direct child of the body, was LOCKED by enable() (it looks for a region among a
|
|
227
|
+
// child's descendants): the region could not be typed into. Returns the first region.
|
|
228
|
+
// tools/restricted/mark-across-blocks.mjs
|
|
229
|
+
var runs = crossBlockRuns(doc, range);
|
|
230
|
+
if (runs) {
|
|
231
|
+
var regions = [];
|
|
232
|
+
// from the last run back: wrapping one never moves the text of a run still to come
|
|
233
|
+
for (var ri = runs.length - 1; ri >= 0; ri--) {
|
|
234
|
+
var sub = doc.createRange();
|
|
235
|
+
sub.setStart(runs[ri].sn, runs[ri].so);
|
|
236
|
+
sub.setEnd(runs[ri].en, runs[ri].eo);
|
|
237
|
+
widenToInlineEdges(sub, runs[ri].block);
|
|
238
|
+
var part = doc.createElement("span");
|
|
239
|
+
part.setAttribute(editableAttr, "true");
|
|
240
|
+
if (options.label) part.setAttribute("aria-label", options.label);
|
|
241
|
+
part.appendChild(sub.extractContents());
|
|
242
|
+
sub.insertNode(part);
|
|
243
|
+
regions.unshift(part);
|
|
244
|
+
}
|
|
245
|
+
if (!regions.length) return null;
|
|
246
|
+
var all = doc.createRange();
|
|
247
|
+
all.setStart(regions[0], 0);
|
|
248
|
+
all.setEnd(regions[regions.length - 1], regions[regions.length - 1].childNodes.length);
|
|
249
|
+
sel.removeAllRanges();
|
|
250
|
+
sel.addRange(all);
|
|
251
|
+
refreshStyles();
|
|
252
|
+
if (enabled) enable();
|
|
253
|
+
return regions[0];
|
|
254
|
+
}
|
|
142
255
|
var span = doc.createElement("span");
|
|
143
256
|
span.setAttribute(editableAttr, "true");
|
|
144
257
|
if (options.label) span.setAttribute("aria-label", options.label);
|
|
@@ -161,6 +274,59 @@ function RTE_Plugin_RestrictedEditing() {
|
|
|
161
274
|
return span;
|
|
162
275
|
}
|
|
163
276
|
|
|
277
|
+
// The runs of selected text that share a block, when the selection crosses blocks; null when it
|
|
278
|
+
// starts and ends in one block (that keeps the single-span path). Whitespace-only runs (the line
|
|
279
|
+
// break between two blocks) are dropped; text inside a non-editable element moves with its run.
|
|
280
|
+
var BLOCK_RE = /^(P|DIV|LI|UL|OL|DL|DT|DD|TABLE|TBODY|THEAD|TFOOT|TR|TD|TH|CAPTION|H[1-6]|PRE|BLOCKQUOTE|ADDRESS|FIGURE|FIGCAPTION|SECTION|ARTICLE|ASIDE|HEADER|FOOTER|NAV|MAIN|DETAILS|SUMMARY|FORM|FIELDSET|LEGEND|HR|BODY)$/;
|
|
281
|
+
function blockOf(node) {
|
|
282
|
+
for (var n = node; n; n = n.parentNode)
|
|
283
|
+
if (n.nodeType === 1 && BLOCK_RE.test(n.nodeName)) return n;
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
function crossBlockRuns(doc, range) {
|
|
287
|
+
if (blockOf(range.startContainer) === blockOf(range.endContainer)) return null;
|
|
288
|
+
var root = range.commonAncestorContainer;
|
|
289
|
+
if (root.nodeType !== 1) root = root.parentNode;
|
|
290
|
+
var runs = [], cur = null;
|
|
291
|
+
var w = doc.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, {
|
|
292
|
+
acceptNode: function (n) { return n.nodeType === 1 && n.getAttribute("contenteditable") === "false" ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT; }
|
|
293
|
+
});
|
|
294
|
+
for (var n = w.nextNode(); n; n = w.nextNode()) {
|
|
295
|
+
if (n.nodeType === 1) {
|
|
296
|
+
if (BLOCK_RE.test(n.nodeName)) cur = null;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (!range.intersectsNode(n)) continue;
|
|
300
|
+
var a = n === range.startContainer ? range.startOffset : 0, b = n === range.endContainer ? range.endOffset : n.data.length;
|
|
301
|
+
if (b <= a) continue;
|
|
302
|
+
var blk = blockOf(n);
|
|
303
|
+
if (!cur || cur.block !== blk) {
|
|
304
|
+
cur = { block: blk, sn: n, so: a, text: "" };
|
|
305
|
+
runs.push(cur);
|
|
306
|
+
}
|
|
307
|
+
cur.en = n;
|
|
308
|
+
cur.eo = b;
|
|
309
|
+
cur.text += n.data.substring(a, b);
|
|
310
|
+
}
|
|
311
|
+
var keep = [];
|
|
312
|
+
for (var i = 0; i < runs.length; i++) if (/\S/.test(runs[i].text)) keep.push(runs[i]);
|
|
313
|
+
return keep;
|
|
314
|
+
}
|
|
315
|
+
// A run starting or ending at the edge of an inline element (<b>) takes the whole element, so
|
|
316
|
+
// no empty <b></b> is left behind by extractContents.
|
|
317
|
+
function widenToInlineEdges(range, block) {
|
|
318
|
+
var n = range.startContainer;
|
|
319
|
+
if (n.nodeType === 3 && range.startOffset === 0) {
|
|
320
|
+
while (n.parentNode && n.parentNode !== block && !n.previousSibling && !BLOCK_RE.test(n.parentNode.nodeName)) n = n.parentNode;
|
|
321
|
+
range.setStartBefore(n);
|
|
322
|
+
}
|
|
323
|
+
n = range.endContainer;
|
|
324
|
+
if (n.nodeType === 3 && range.endOffset === n.data.length) {
|
|
325
|
+
while (n.parentNode && n.parentNode !== block && !n.nextSibling && !BLOCK_RE.test(n.parentNode.nodeName)) n = n.parentNode;
|
|
326
|
+
range.setEndAfter(n);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
164
330
|
function unmark(node) {
|
|
165
331
|
node = node || (function () {
|
|
166
332
|
var body = getEditable();
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
window.RTE_DefaultConfig||(window.RTE_DefaultConfig={}),RTE_DefaultConfig.svgCode_restrictedediting||(RTE_DefaultConfig.svgCode_restrictedediting='<svg viewBox="0 0 24 24" fill="none" stroke="#5F6368" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="9" rx="1.5"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg>'),RTE_DefaultConfig.plugin_restrictedediting=RTE_Plugin_RestrictedEditing;function RTE_Plugin_RestrictedEditing(){var
|
|
2
|
-
`),(
|
|
1
|
+
window.RTE_DefaultConfig||(window.RTE_DefaultConfig={}),RTE_DefaultConfig.svgCode_restrictedediting||(RTE_DefaultConfig.svgCode_restrictedediting='<svg viewBox="0 0 24 24" fill="none" stroke="#5F6368" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="9" rx="1.5"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg>'),RTE_DefaultConfig.plugin_restrictedediting=RTE_Plugin_RestrictedEditing;function RTE_Plugin_RestrictedEditing(){var N=this,c,g,d=!1,f="data-rte-editable";N.PluginName="RestrictedEditing",N.InitConfig=function(t){c=t,c.text_restrictedediting=c.text_restrictedediting||"Restricted editing",c.text_restrictedediting_mark=c.text_restrictedediting_mark||"Mark as editable",c.text_restrictedediting_next=c.text_restrictedediting_next||"Next editable region",c.text_restrictedediting_hint=c.text_restrictedediting_hint||"Lock the document; only marked regions accept input."},N.InitEditor=function(t){g=t,d=!!c.restrictedEditingMode,g.restrictedEditing={isEnabled:function(){return d},enable:b,disable:D,toggle:function(){return d?D():b()},markSelection:B,unmark:K,list:R,goToNext:function(){return x(1)},goToPrev:function(){return x(-1)}},g.attachEvent("exec_command_restrictedediting",function(r){r.returnValue=!0,r.stopBubble=!0,g.restrictedEditing.toggle()}),d&&b(),g.attachEvent("change",h),h()};var A=[];function _(t){for(var r=t&&t.nodeType===1?t:t&&t.parentNode;r&&r.nodeType===1;){if(r.getAttribute&&r.getAttribute(f)==="true")return r;r=r.parentNode}return null}function y(t){var r=t.getSelection();if(!r||r.rangeCount===0)return null;var e=r.getRangeAt(0),n=_(e.startContainer),i=_(e.endContainer);return n&&n===i?{region:n,range:e}:null}function w(t,r,e,n){var i=t.createRange();return i.selectNodeContents(r),n?i.setEnd(e.startContainer,e.startOffset):i.setStart(e.endContainer,e.endOffset),i.toString()===""}function k(t){var r=t.key||"";if(t.isComposing||r==="Backspace"||r==="Delete"||r==="Enter"||r==="Tab"||t.altKey&&(r==="ArrowUp"||r==="ArrowDown"))return!0;if(t.ctrlKey||t.metaKey){var e=r.toLowerCase();return e==="x"||e==="v"||e==="b"||e==="i"||e==="u"||e==="d"}return r.length===1&&!t.altKey}function O(t){if(!(!d||!k(t))){var r=t.target&&(t.target.ownerDocument||t.target);if(!(!r||!r.getSelection)){var e=y(r),n=!e;e&&e.range.collapsed&&(t.key==="Backspace"&&w(r,e.region,e.range,!0)&&(n=!0),t.key==="Delete"&&w(r,e.region,e.range,!1)&&(n=!0)),e&&t.altKey&&(t.key==="ArrowUp"||t.key==="ArrowDown")&&(n=!0),e&&(t.ctrlKey||t.metaKey)&&t.shiftKey&&String(t.key).toLowerCase()==="d"&&(n=!0),n&&(t.preventDefault(),t.stopImmediatePropagation())}}}function L(t){if(d){var r=t.inputType||"";if(r.indexOf("history")!==0){var e=t.target&&(t.target.ownerDocument||t.target);!e||!e.getSelection||y(e)||(t.preventDefault(),t.stopImmediatePropagation())}}}function I(){var t=v(),r=t&&t.ownerDocument&&t.ownerDocument.defaultView;!r||A.indexOf(r)>=0||(A.push(r),r.addEventListener("keydown",O,!0),r.addEventListener("beforeinput",L,!0),r.addEventListener("cut",function(e){d&&!y(e.target.ownerDocument||e.target)&&(e.preventDefault(),e.stopImmediatePropagation())},!0),r.addEventListener("paste",function(e){d&&!y(e.target.ownerDocument||e.target)&&(e.preventDefault(),e.stopImmediatePropagation())},!0),r.addEventListener("drop",function(e){d&&(e.preventDefault(),e.stopImmediatePropagation())},!0))}function v(){return g.getEditable?g.getEditable():null}function h(){var t=v();if(t){var r=t.ownerDocument;if(!(!r||r.getElementById("__rte_restrictedediting_styles"))){var e=r.createElement("style");e.id="__rte_restrictedediting_styles",e.textContent=["["+f+"='true']{background:rgba(34,197,94,.10);outline:1px dashed rgba(34,197,94,.55);outline-offset:1px;border-radius:2px;padding:1px 2px}","body.rte-restricted [contenteditable='false']{cursor:not-allowed}","body.rte-restricted ["+f+"='true']{background:rgba(34,197,94,.16)}"].join(`
|
|
2
|
+
`),(r.head||r.getElementsByTagName("head")[0]).appendChild(e),g&&typeof g.ensureStyleSheetLive=="function"&&g.ensureStyleSheetLive(r,e,"__rte_restrictedediting_styles",e.textContent)}}}function b(){var t=v();if(!t)return!1;d=!0,t.classList.add("rte-restricted");for(var r=0;r<t.children.length;r++){var e=t.children[r];e.querySelector("["+f+"='true']")?(e.setAttribute("contenteditable","true"),e.setAttribute(f+"-wrapper","true")):e.setAttribute("contenteditable","false")}return t.setAttribute("contenteditable","false"),I(),h(),!0}function D(){var t=v();if(!t)return!1;d=!1,t.classList.remove("rte-restricted");for(var r=t.querySelectorAll("[contenteditable]"),e=0;e<r.length;e++)r[e].removeAttribute("contenteditable"),r[e].removeAttribute(f+"-wrapper");return t.setAttribute("contenteditable","true"),!0}function B(t){t=t||{};var r=v();if(!r)return null;var e=r.ownerDocument,n=e.getSelection();if(!n||n.rangeCount===0)return null;var i=n.getRangeAt(0);if(i.collapsed)return null;var o=F(e,i);if(o){for(var a=[],u=o.length-1;u>=0;u--){var l=e.createRange();l.setStart(o[u].sn,o[u].so),l.setEnd(o[u].en,o[u].eo),P(l,o[u].block);var p=e.createElement("span");p.setAttribute(f,"true"),t.label&&p.setAttribute("aria-label",t.label),p.appendChild(l.extractContents()),l.insertNode(p),a.unshift(p)}if(!a.length)return null;var E=e.createRange();return E.setStart(a[0],0),E.setEnd(a[a.length-1],a[a.length-1].childNodes.length),n.removeAllRanges(),n.addRange(E),h(),d&&b(),a[0]}var s=e.createElement("span");s.setAttribute(f,"true"),t.label&&s.setAttribute("aria-label",t.label);try{i.surroundContents(s)}catch{var C=i.extractContents();s.appendChild(C),i.insertNode(s)}var S=e.createRange();return S.selectNodeContents(s),n.removeAllRanges(),n.addRange(S),h(),d&&b(),s}var m=/^(P|DIV|LI|UL|OL|DL|DT|DD|TABLE|TBODY|THEAD|TFOOT|TR|TD|TH|CAPTION|H[1-6]|PRE|BLOCKQUOTE|ADDRESS|FIGURE|FIGCAPTION|SECTION|ARTICLE|ASIDE|HEADER|FOOTER|NAV|MAIN|DETAILS|SUMMARY|FORM|FIELDSET|LEGEND|HR|BODY)$/;function T(t){for(var r=t;r;r=r.parentNode)if(r.nodeType===1&&m.test(r.nodeName))return r;return null}function F(t,r){if(T(r.startContainer)===T(r.endContainer))return null;var e=r.commonAncestorContainer;e.nodeType!==1&&(e=e.parentNode);for(var n=[],i=null,o=t.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT,{acceptNode:function(C){return C.nodeType===1&&C.getAttribute("contenteditable")==="false"?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}}),a=o.nextNode();a;a=o.nextNode()){if(a.nodeType===1){m.test(a.nodeName)&&(i=null);continue}if(r.intersectsNode(a)){var u=a===r.startContainer?r.startOffset:0,l=a===r.endContainer?r.endOffset:a.data.length;if(!(l<=u)){var p=T(a);(!i||i.block!==p)&&(i={block:p,sn:a,so:u,text:""},n.push(i)),i.en=a,i.eo=l,i.text+=a.data.substring(u,l)}}}for(var E=[],s=0;s<n.length;s++)/\S/.test(n[s].text)&&E.push(n[s]);return E}function P(t,r){var e=t.startContainer;if(e.nodeType===3&&t.startOffset===0){for(;e.parentNode&&e.parentNode!==r&&!e.previousSibling&&!m.test(e.parentNode.nodeName);)e=e.parentNode;t.setStartBefore(e)}if(e=t.endContainer,e.nodeType===3&&t.endOffset===e.data.length){for(;e.parentNode&&e.parentNode!==r&&!e.nextSibling&&!m.test(e.parentNode.nodeName);)e=e.parentNode;t.setEndAfter(e)}}function K(t){if(t=t||(function(){var e=v(),n=e&&e.ownerDocument.getSelection();if(!n||n.rangeCount===0)return null;for(var i=n.anchorNode;i&&i!==e;){if(i.nodeType===1&&i.getAttribute&&i.getAttribute(f)==="true")return i;i=i.parentNode}return null})(),!t)return!1;for(var r=t.parentNode;t.firstChild;)r.insertBefore(t.firstChild,t);return r.removeChild(t),d&&b(),!0}function R(){var t=v();return t?Array.prototype.slice.call(t.querySelectorAll("["+f+"='true']")):[]}function x(t){var r=R();if(r.length===0)return null;var e=v(),n=e&&e.ownerDocument.getSelection(),i=null;if(n&&n.anchorNode)for(var o=n.anchorNode;o&&o!==e;){if(o.nodeType===1&&o.getAttribute&&o.getAttribute(f)==="true"){i=o;break}o=o.parentNode}var a=i?r.indexOf(i):-1,u=r[(a+t+r.length)%r.length];if(!u)return null;var l=e.ownerDocument.createRange();return l.selectNodeContents(u),l.collapse(!1),n.removeAllRanges(),n.addRange(l),u.scrollIntoView({block:"center",behavior:"smooth"}),u}}
|
|
@@ -75,7 +75,8 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
75
75
|
|
|
76
76
|
// Auto-snapshot on idle typing (opt-in).
|
|
77
77
|
if (config.revisionHistoryAutoSnapshotMs > 0) {
|
|
78
|
-
|
|
78
|
+
// 2026-10-17 bound through the core (follows the live editing document). tools/keyboard/plugin-listeners-live.mjs
|
|
79
|
+
(editor.bindDocumentEvent ? function (fn) { editor.bindDocumentEvent("input", fn, true); } : function (fn) { editor.getDocument().addEventListener("input", fn, true); })(function () {
|
|
79
80
|
if (autoSnapshotTimer) clearTimeout(autoSnapshotTimer);
|
|
80
81
|
autoSnapshotTimer = setTimeout(function () {
|
|
81
82
|
var currentHtml = editor.getHTML();
|
|
@@ -83,16 +84,18 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
83
84
|
snapshot("Auto", { auto: true });
|
|
84
85
|
}
|
|
85
86
|
}, config.revisionHistoryAutoSnapshotMs);
|
|
86
|
-
}
|
|
87
|
+
});
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
// Keyboard shortcut: Ctrl/Cmd + Shift + H opens the browser.
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
// Keyboard shortcut: Ctrl/Cmd + Alt + Shift + H opens the browser (Google Docs' version-history keys).
|
|
91
|
+
// 2026-10-15 it was Ctrl+Shift+H, which is also Highlight: highlighting opened this modal over the page and,
|
|
92
|
+
// once the dialog took focus, the highlight could not be toggled off. tools/keyboard/shortcut-collisions.mjs
|
|
93
|
+
(editor.bindDocumentEvent ? function (fn) { editor.bindDocumentEvent("keydown", fn, true); } : function (fn) { editor.getDocument().addEventListener("keydown", fn, true); })(function (e) {
|
|
94
|
+
if ((e.ctrlKey || e.metaKey) && e.altKey && e.shiftKey && (e.key === "H" || e.key === "h" || e.code === "KeyH")) {
|
|
92
95
|
e.preventDefault();
|
|
93
96
|
openDialog();
|
|
94
97
|
}
|
|
95
|
-
}
|
|
98
|
+
});
|
|
96
99
|
};
|
|
97
100
|
|
|
98
101
|
function appendToolbarCommand(toolbar, item) {
|
|
@@ -106,9 +109,50 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
106
109
|
return { id: "user", name: "User", color: "#64748b" };
|
|
107
110
|
}
|
|
108
111
|
|
|
112
|
+
// 2026-10-03 One store per DOCUMENT. The key was "RTE.Revisions." + (aiToolkitPersistenceKey ||
|
|
113
|
+
// "default"), so without that key every editor and every document on a site shared one list: two
|
|
114
|
+
// editors on a page overwrote each other's saved versions, and a version taken on one document was
|
|
115
|
+
// listed - and restorable, replacing the content - on every other one (localdraft had the same
|
|
116
|
+
// defect). An explicit key wins (config.revisionHistoryKey, or aiToolkitPersistenceKey as
|
|
117
|
+
// before); otherwise the page (path + query, which tells /edit?id=1 from ?id=2) and, with several
|
|
118
|
+
// editors on it, the editor's own name. tools/revisions/revision-keys.mjs
|
|
119
|
+
var cachedStoreKey = null;
|
|
120
|
+
var LEGACY_STORE_KEY = "RTE.Revisions.default";
|
|
121
|
+
|
|
109
122
|
function getStoreKey() {
|
|
110
|
-
|
|
111
|
-
return "RTE.Revisions." +
|
|
123
|
+
if (config.revisionHistoryKey) return "RTE.Revisions." + config.revisionHistoryKey;
|
|
124
|
+
if (config.aiToolkitPersistenceKey) return "RTE.Revisions." + config.aiToolkitPersistenceKey;
|
|
125
|
+
if (cachedStoreKey) return cachedStoreKey;
|
|
126
|
+
var page = "default";
|
|
127
|
+
try { page = location.pathname + location.search; } catch (e) { }
|
|
128
|
+
var name = "";
|
|
129
|
+
try {
|
|
130
|
+
var inp = editor.input;
|
|
131
|
+
if (inp) name = inp.id || inp.getAttribute("name") || "";
|
|
132
|
+
if (!name && editor.container && editor.container.id) name = editor.container.id;
|
|
133
|
+
if (!name) {
|
|
134
|
+
var all = document.querySelectorAll(".richtexteditor");
|
|
135
|
+
for (var i = 1; i < all.length; i++) if (all[i] === editor.container) { name = "editor-" + i; break; }
|
|
136
|
+
}
|
|
137
|
+
} catch (e) { }
|
|
138
|
+
return (cachedStoreKey = "RTE.Revisions." + page + (name ? "#" + name : ""));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Versions saved under the old shared key belong to this editor only when it is the page's only
|
|
142
|
+
// one (checked once every editor on the page exists); then they move to this editor's key.
|
|
143
|
+
function takeLegacyRevisions() {
|
|
144
|
+
if (config.revisionHistoryKey || config.aiToolkitPersistenceKey || getStore().length) return;
|
|
145
|
+
try {
|
|
146
|
+
if (!window.localStorage || document.querySelectorAll(".richtexteditor").length !== 1) return;
|
|
147
|
+
var key = getStoreKey(), raw = window.localStorage.getItem(LEGACY_STORE_KEY);
|
|
148
|
+
if (!raw || key === LEGACY_STORE_KEY || window.localStorage.getItem(key)) return;
|
|
149
|
+
var parsed = JSON.parse(raw);
|
|
150
|
+
if (!Array.isArray(parsed)) return;
|
|
151
|
+
window.localStorage.setItem(key, raw);
|
|
152
|
+
window.localStorage.removeItem(LEGACY_STORE_KEY);
|
|
153
|
+
editor.__revisions = parsed;
|
|
154
|
+
if (dialog && dialog.isConnected) renderDialog();
|
|
155
|
+
} catch (ignore) { }
|
|
112
156
|
}
|
|
113
157
|
|
|
114
158
|
function getStore() {
|
|
@@ -188,6 +232,9 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
188
232
|
}
|
|
189
233
|
|
|
190
234
|
function restoreStore() {
|
|
235
|
+
// other editors on the page are created after this one: decide about the old shared
|
|
236
|
+
// versions once they all exist
|
|
237
|
+
setTimeout(takeLegacyRevisions, 0);
|
|
191
238
|
try {
|
|
192
239
|
if (!window.localStorage) return;
|
|
193
240
|
var raw = window.localStorage.getItem(getStoreKey());
|
|
@@ -222,10 +269,16 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
222
269
|
|
|
223
270
|
// Prompt the user for a snapshot name via native window.prompt, then take a snapshot.
|
|
224
271
|
// Returns the entry (or null if the user cancelled).
|
|
272
|
+
// 2026-10-18 the history's words read config.text_ keys (language packs), English as the fallback; {0} fills a
|
|
273
|
+
// template. tools/i18n/revision-history-language.mjs
|
|
274
|
+
function rhText(key, fallback, arg) {
|
|
275
|
+
var s = (config && config["text_" + key]) || fallback;
|
|
276
|
+
return arg === undefined ? s : s.split("{0}").join(String(arg));
|
|
277
|
+
}
|
|
225
278
|
function promptAndSnapshot(defaultLabel) {
|
|
226
279
|
var iframeDoc = editor.iframe ? editor.iframe.ownerDocument : document;
|
|
227
280
|
var win = iframeDoc.defaultView || window;
|
|
228
|
-
var value = win.prompt("Name this version (e.g. \"Draft sent to legal\"):", defaultLabel || "");
|
|
281
|
+
var value = win.prompt(rhText("rh_nameversion", "Name this version (e.g. \"Draft sent to legal\"):"), defaultLabel || "");
|
|
229
282
|
if (value === null) return null;
|
|
230
283
|
var trimmed = (value || "").trim();
|
|
231
284
|
if (!trimmed) return null;
|
|
@@ -277,13 +330,31 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
277
330
|
opts = opts || {};
|
|
278
331
|
var entry = findById(id);
|
|
279
332
|
if (!entry) return false;
|
|
333
|
+
// 2026-10-03 Restoring replaces the content with setHTMLCode, which is not an undo step, and saved
|
|
334
|
+
// nothing first - restoring a version by mistake threw away everything written since it, for
|
|
335
|
+
// good. The current content is now kept as a (named) version first, unless the newest version
|
|
336
|
+
// already holds it; config.revisionHistorySnapshotBeforeRestore: false for the old behaviour.
|
|
337
|
+
// tools/revisions/restore-safety.mjs
|
|
338
|
+
var keep = config.revisionHistorySnapshotBeforeRestore !== false;
|
|
280
339
|
if (!opts.skipConfirm) {
|
|
281
340
|
var doc = editor.iframe ? editor.iframe.ownerDocument : document;
|
|
282
|
-
|
|
341
|
+
var message = keep
|
|
342
|
+
? rhText("rh_restorekeep", "Restore this version? Your current content is kept as a version first, so you can go back to it.")
|
|
343
|
+
: rhText("rh_restorereplace", "Restore this version? Your current content will be replaced (this itself is not auto-snapshotted).");
|
|
344
|
+
if (!doc.defaultView.confirm(message)) {
|
|
283
345
|
return false;
|
|
284
346
|
}
|
|
285
347
|
}
|
|
286
|
-
if (
|
|
348
|
+
if (keep) {
|
|
349
|
+
var current = editor.getHTML ? editor.getHTML() : (editor.getHTMLCode ? editor.getHTMLCode() : "");
|
|
350
|
+
var newest = null, store = getStore();
|
|
351
|
+
for (var i = 0; i < store.length; i++) if (!newest || (store[i].createdAt || 0) >= (newest.createdAt || 0)) newest = store[i];
|
|
352
|
+
if (current !== entry.html && (!newest || newest.html !== current)) {
|
|
353
|
+
snapshot(rhText("rh_beforerestore", "Before restoring {0}", entry.label), { beforeRestore: entry.id });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// undoable: Ctrl+Z after a restore gives back exactly the document it replaced (import-undo.mjs)
|
|
357
|
+
if (editor.setHTMLCode) editor.setHTMLCode(entry.html, { undoable: true });
|
|
287
358
|
else if (editor.setHTML) editor.setHTML(entry.html);
|
|
288
359
|
lastSnapshotHtml = entry.html;
|
|
289
360
|
if (dialog && dialog.isConnected) closeDialog();
|
|
@@ -406,13 +477,68 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
406
477
|
panel.className = "rte-rev-panel";
|
|
407
478
|
backdrop.appendChild(panel);
|
|
408
479
|
dialog.appendChild(backdrop);
|
|
480
|
+
// 2026-10-15 keyboard: the dialog is modal, so focus moves in, Tab stays in, Escape closes, and focus goes back
|
|
481
|
+
// to where it was. Escape did nothing and the backdrop kept blocking the page. tools/a11y/revision-dialog-keyboard.mjs
|
|
482
|
+
dialogReturnFocus = host.activeElement;
|
|
483
|
+
dialog.addEventListener("keydown", onDialogKey, true);
|
|
409
484
|
host.body.appendChild(dialog);
|
|
410
485
|
renderDialog();
|
|
486
|
+
// the toolbar returns focus to the editor just after a command runs; move in after that, and once more
|
|
487
|
+
// if something took it back
|
|
488
|
+
setTimeout(focusFirstInDialog, 0);
|
|
489
|
+
setTimeout(function () { if (dialog && !dialog.contains(dialog.ownerDocument.activeElement)) focusFirstInDialog(); }, 250);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
var dialogReturnFocus = null;
|
|
493
|
+
function dialogFocusables() {
|
|
494
|
+
if (!dialog) return [];
|
|
495
|
+
var all = dialog.querySelectorAll("button, input, select, textarea, a[href], [tabindex]:not([tabindex='-1'])");
|
|
496
|
+
var out = [];
|
|
497
|
+
for (var i = 0; i < all.length; i++) if (!all[i].disabled && all[i].offsetParent !== null) out.push(all[i]);
|
|
498
|
+
return out;
|
|
499
|
+
}
|
|
500
|
+
function focusFirstInDialog() {
|
|
501
|
+
var f = dialogFocusables();
|
|
502
|
+
if (f.length) { try { f[0].focus(); } catch (e) { /* ignore */ } }
|
|
503
|
+
}
|
|
504
|
+
// 2026-10-18 a dialog control runs its action on click, so Enter and Space work (they fire click, not mousedown;
|
|
505
|
+
// these ran on mousedown only and did nothing from the keyboard). mousedown still prevents default so a mouse
|
|
506
|
+
// press keeps the editor's selection. The action usually re-renders the dialog; after a keyboard press, focus
|
|
507
|
+
// goes back to the control with the same key. tools/a11y/revision-buttons-keyboard.mjs
|
|
508
|
+
function onActivate(el, key, fn) {
|
|
509
|
+
el.setAttribute("data-rte-rev-key", key);
|
|
510
|
+
el.addEventListener("mousedown", function (e) { e.preventDefault(); });
|
|
511
|
+
el.addEventListener("click", function (e) {
|
|
512
|
+
e.preventDefault();
|
|
513
|
+
var fromKeyboard = e.detail === 0;
|
|
514
|
+
fn();
|
|
515
|
+
if (!fromKeyboard || !dialog) return;
|
|
516
|
+
var all = dialog.querySelectorAll("[data-rte-rev-key]");
|
|
517
|
+
for (var i = 0; i < all.length; i++) {
|
|
518
|
+
if (all[i].getAttribute("data-rte-rev-key") === key) { try { all[i].focus(); } catch (x) { /* ignore */ } return; }
|
|
519
|
+
}
|
|
520
|
+
if (!dialog.contains(dialog.ownerDocument.activeElement)) focusFirstInDialog();
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
function onDialogKey(e) {
|
|
524
|
+
if (e.key === "Escape" || e.key === "Esc") { e.preventDefault(); e.stopPropagation(); closeDialog(); return; }
|
|
525
|
+
if (e.key !== "Tab") return;
|
|
526
|
+
var f = dialogFocusables();
|
|
527
|
+
if (!f.length) return;
|
|
528
|
+
var a = dialog.ownerDocument.activeElement, i = f.indexOf(a);
|
|
529
|
+
if (e.shiftKey && (i <= 0)) { e.preventDefault(); f[f.length - 1].focus(); }
|
|
530
|
+
else if (!e.shiftKey && (i === -1 || i === f.length - 1)) { e.preventDefault(); f[0].focus(); }
|
|
411
531
|
}
|
|
412
532
|
|
|
413
533
|
function closeDialog() {
|
|
414
534
|
if (dialog && dialog.parentNode) dialog.parentNode.removeChild(dialog);
|
|
415
535
|
dialog = null;
|
|
536
|
+
var back = dialogReturnFocus;
|
|
537
|
+
dialogReturnFocus = null;
|
|
538
|
+
try {
|
|
539
|
+
if (back && back.isConnected && back.tagName !== "IFRAME" && back !== back.ownerDocument.body) back.focus();
|
|
540
|
+
else if (typeof editor.focus === "function") editor.focus();
|
|
541
|
+
} catch (e) { /* ignore */ }
|
|
416
542
|
}
|
|
417
543
|
|
|
418
544
|
var dialogSelectedId = null;
|
|
@@ -433,17 +559,16 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
433
559
|
var title = host.createElement("div");
|
|
434
560
|
title.id = "rte-rev-dialog-title";
|
|
435
561
|
title.className = "rte-rev-title";
|
|
436
|
-
title.textContent = "Revision history";
|
|
562
|
+
title.textContent = rhText("revisionhistory", "Revision history");
|
|
437
563
|
header.appendChild(title);
|
|
438
564
|
var actions = host.createElement("div");
|
|
439
565
|
actions.className = "rte-rev-header-actions";
|
|
440
566
|
var snapBtn = host.createElement("button");
|
|
441
567
|
snapBtn.type = "button";
|
|
442
568
|
snapBtn.className = "rte-rev-btn rte-rev-btn-ghost";
|
|
443
|
-
snapBtn.textContent = "Save version now";
|
|
444
|
-
snapBtn
|
|
445
|
-
|
|
446
|
-
var label = host.defaultView.prompt("Label this version (optional):", "");
|
|
569
|
+
snapBtn.textContent = rhText("rh_savenow", "Save version now");
|
|
570
|
+
onActivate(snapBtn, "save", function () {
|
|
571
|
+
var label = host.defaultView.prompt(rhText("rh_labelversion", "Label this version (optional):"), "");
|
|
447
572
|
if (label === null) return; // cancelled
|
|
448
573
|
snapshot(label || "");
|
|
449
574
|
});
|
|
@@ -451,9 +576,9 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
451
576
|
var closeBtn = host.createElement("button");
|
|
452
577
|
closeBtn.type = "button";
|
|
453
578
|
closeBtn.className = "rte-rev-btn rte-rev-btn-ghost";
|
|
454
|
-
closeBtn.textContent = "Close";
|
|
455
|
-
closeBtn.setAttribute("aria-label", "Close revision history");
|
|
456
|
-
closeBtn
|
|
579
|
+
closeBtn.textContent = rhText("close", "Close");
|
|
580
|
+
closeBtn.setAttribute("aria-label", rhText("rh_closehistory", "Close revision history"));
|
|
581
|
+
onActivate(closeBtn, "close", closeDialog);
|
|
457
582
|
actions.appendChild(closeBtn);
|
|
458
583
|
header.appendChild(actions);
|
|
459
584
|
panel.appendChild(header);
|
|
@@ -464,13 +589,17 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
464
589
|
// Left: list
|
|
465
590
|
var list = host.createElement("div");
|
|
466
591
|
list.className = "rte-rev-list";
|
|
467
|
-
list.setAttribute("role", "listbox");
|
|
468
|
-
list.setAttribute("aria-label", "Saved revisions");
|
|
469
592
|
var entries = editor.revisionHistory.list();
|
|
593
|
+
// 2026-10-15 a listbox only when it lists revisions: the empty message inside role=listbox left a listbox with
|
|
594
|
+
// no options (axe aria-required-children) that read as an empty list. tools/a11y/revision-list-roles.mjs
|
|
595
|
+
if (entries.length) {
|
|
596
|
+
list.setAttribute("role", "listbox");
|
|
597
|
+
list.setAttribute("aria-label", rhText("rh_saved", "Saved revisions"));
|
|
598
|
+
}
|
|
470
599
|
if (!entries.length) {
|
|
471
600
|
var empty = host.createElement("div");
|
|
472
601
|
empty.className = "rte-rev-empty";
|
|
473
|
-
empty.textContent = "No revisions yet. Click \"Save version now\" to capture the current document.";
|
|
602
|
+
empty.textContent = rhText("rh_empty", "No revisions yet. Click \"Save version now\" to capture the current document.");
|
|
474
603
|
list.appendChild(empty);
|
|
475
604
|
} else {
|
|
476
605
|
if (!dialogSelectedId || !findById(dialogSelectedId)) {
|
|
@@ -488,7 +617,7 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
488
617
|
dot.style.background = (e.author && e.author.color) || "#64748b";
|
|
489
618
|
var who = host.createElement("div");
|
|
490
619
|
who.className = "rte-rev-row-who";
|
|
491
|
-
who.textContent = (e.author && e.author.name) || "User";
|
|
620
|
+
who.textContent = (e.author && e.author.name) || rhText("rh_user", "User");
|
|
492
621
|
var when = host.createElement("div");
|
|
493
622
|
when.className = "rte-rev-row-when";
|
|
494
623
|
when.textContent = formatDate(e.createdAt) + " \u00B7 " + (e.label || "");
|
|
@@ -498,8 +627,7 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
498
627
|
col.appendChild(when);
|
|
499
628
|
row.appendChild(dot);
|
|
500
629
|
row.appendChild(col);
|
|
501
|
-
row
|
|
502
|
-
ev.preventDefault();
|
|
630
|
+
onActivate(row, "row-" + e.id, function () {
|
|
503
631
|
dialogSelectedId = e.id;
|
|
504
632
|
renderDialog();
|
|
505
633
|
});
|
|
@@ -518,21 +646,21 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
518
646
|
var tabs = host.createElement("div");
|
|
519
647
|
tabs.className = "rte-rev-tabs";
|
|
520
648
|
tabs.setAttribute("role", "tablist");
|
|
521
|
-
tabs.setAttribute("aria-label", "Revision view");
|
|
649
|
+
tabs.setAttribute("aria-label", rhText("rh_view", "Revision view"));
|
|
522
650
|
var tabPreview = host.createElement("button");
|
|
523
651
|
tabPreview.type = "button";
|
|
524
652
|
tabPreview.className = "rte-rev-tab" + (dialogView === "preview" ? " rte-rev-tab-active" : "");
|
|
525
653
|
tabPreview.setAttribute("role", "tab");
|
|
526
654
|
tabPreview.setAttribute("aria-selected", dialogView === "preview" ? "true" : "false");
|
|
527
|
-
tabPreview.textContent = "Preview";
|
|
528
|
-
tabPreview
|
|
655
|
+
tabPreview.textContent = rhText("rh_preview", "Preview");
|
|
656
|
+
onActivate(tabPreview, "tab-preview", function () { dialogView = "preview"; renderDialog(); });
|
|
529
657
|
var tabDiff = host.createElement("button");
|
|
530
658
|
tabDiff.type = "button";
|
|
531
659
|
tabDiff.className = "rte-rev-tab" + (dialogView === "diff" ? " rte-rev-tab-active" : "");
|
|
532
660
|
tabDiff.setAttribute("role", "tab");
|
|
533
661
|
tabDiff.setAttribute("aria-selected", dialogView === "diff" ? "true" : "false");
|
|
534
|
-
tabDiff.textContent = "Diff vs current";
|
|
535
|
-
tabDiff
|
|
662
|
+
tabDiff.textContent = rhText("rh_diff", "Diff vs current");
|
|
663
|
+
onActivate(tabDiff, "tab-diff", function () { dialogView = "diff"; renderDialog(); });
|
|
536
664
|
tabs.appendChild(tabPreview);
|
|
537
665
|
tabs.appendChild(tabDiff);
|
|
538
666
|
pane.appendChild(tabs);
|
|
@@ -542,7 +670,7 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
542
670
|
if (dialogView === "preview") {
|
|
543
671
|
var iframe = host.createElement("iframe");
|
|
544
672
|
iframe.className = "rte-rev-preview-frame";
|
|
545
|
-
iframe.setAttribute("title", "Revision preview");
|
|
673
|
+
iframe.setAttribute("title", rhText("rh_previewframe", "Revision preview"));
|
|
546
674
|
iframe.setAttribute("sandbox", "allow-same-origin");
|
|
547
675
|
content.appendChild(iframe);
|
|
548
676
|
// Write after appending so document is available.
|
|
@@ -576,18 +704,18 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
576
704
|
footer.className = "rte-rev-footer";
|
|
577
705
|
var labelInfo = host.createElement("div");
|
|
578
706
|
labelInfo.className = "rte-rev-footer-info";
|
|
579
|
-
labelInfo.textContent = selected.label ? "Label: "
|
|
707
|
+
labelInfo.textContent = selected.label ? rhText("rh_label", "Label: {0}", selected.label) : "";
|
|
580
708
|
var footerBtns = host.createElement("div");
|
|
581
709
|
footerBtns.className = "rte-rev-footer-btns";
|
|
582
710
|
var delBtn = host.createElement("button");
|
|
583
711
|
delBtn.type = "button";
|
|
584
712
|
delBtn.className = "rte-rev-btn rte-rev-btn-danger";
|
|
585
|
-
delBtn.textContent = "Delete";
|
|
586
|
-
delBtn
|
|
713
|
+
delBtn.textContent = rhText("delete", "Delete");
|
|
714
|
+
onActivate(delBtn, "delete", function () { deleteEntry(selected.id); });
|
|
587
715
|
var restoreBtn = host.createElement("button");
|
|
588
716
|
restoreBtn.type = "button";
|
|
589
717
|
restoreBtn.className = "rte-rev-btn rte-rev-btn-primary";
|
|
590
|
-
restoreBtn.textContent = "Restore this version";
|
|
718
|
+
restoreBtn.textContent = rhText("rh_restore", "Restore this version");
|
|
591
719
|
restoreBtn.addEventListener("mousedown", function (e) { e.preventDefault(); });
|
|
592
720
|
restoreBtn.addEventListener("click", function (e) {
|
|
593
721
|
e.preventDefault();
|
|
@@ -599,7 +727,10 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
599
727
|
footer.appendChild(footerBtns);
|
|
600
728
|
pane.appendChild(footer);
|
|
601
729
|
}
|
|
602
|
-
|
|
730
|
+
// 2026-10-18 nothing to preview: no empty pane (a blank half on desktop, a 360px block on a phone); the
|
|
731
|
+
// no-revisions message spans the dialog instead. tools/ui/revision-empty-state.mjs
|
|
732
|
+
if (entries.length) body.appendChild(pane);
|
|
733
|
+
else list.classList.add("rte-rev-list-solo");
|
|
603
734
|
panel.appendChild(body);
|
|
604
735
|
}
|
|
605
736
|
|
|
@@ -609,7 +740,7 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
609
740
|
var now = new Date();
|
|
610
741
|
function pad(n) { return n < 10 ? "0" + n : "" + n; }
|
|
611
742
|
var time = pad(d.getHours()) + ":" + pad(d.getMinutes());
|
|
612
|
-
if (d.toDateString() === now.toDateString()) return "Today "
|
|
743
|
+
if (d.toDateString() === now.toDateString()) return rhText("rh_today", "Today {0}", time);
|
|
613
744
|
return d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate()) + " " + time;
|
|
614
745
|
}
|
|
615
746
|
|
|
@@ -646,6 +777,7 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
646
777
|
".rte-rev-row-who{font-weight:720;font-size:12px;line-height:1.25;color:#172033}",
|
|
647
778
|
".rte-rev-row-when{margin-top:2px;font-size:11px;color:#637487;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
|
|
648
779
|
".rte-rev-pane{flex:1;display:flex;flex-direction:column;min-width:0;padding:10px}",
|
|
780
|
+
".rte-rev-list.rte-rev-list-solo{flex:1 1 auto;width:auto;max-height:none;border-right:0;border-bottom:0}",
|
|
649
781
|
".rte-rev-tabs{display:flex;align-self:flex-start;gap:3px;padding:3px;margin:0 0 10px;border:1px solid rgba(117,137,163,.2);border-radius:7px;background:#eef3f8}",
|
|
650
782
|
".rte-rev-tab{padding:7px 12px;border:0;border-radius:6px;background:transparent;font:inherit;font-size:12px;font-weight:680;color:#637487;cursor:pointer}",
|
|
651
783
|
".rte-rev-tab:hover{color:#1d3557;background:rgba(255,255,255,.58)}",
|