@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.
- package/migration/README.md +100 -0
- package/migration/cli.js +294 -0
- package/migration/index.d.ts +18 -0
- package/migration/index.js +242 -0
- package/migration/report-schema-v2.json +127 -0
- package/package.json +14 -2
- package/richtexteditor/CHANGELOG.md +326 -1
- package/richtexteditor/all_plugins.js +14982 -4831
- package/richtexteditor/all_plugins.min.js +62 -42
- package/richtexteditor/plugins/accessibilitychecker.js +8 -7
- package/richtexteditor/plugins/aitoolkit.css +17 -8
- package/richtexteditor/plugins/aitoolkit.js +8 -8
- package/richtexteditor/plugins/all_plugins.js +14982 -4831
- package/richtexteditor/plugins/all_plugins.min.js +62 -42
- package/richtexteditor/plugins/autocorrect.js +353 -0
- package/richtexteditor/plugins/bookmarkcard.js +12 -1
- package/richtexteditor/plugins/changecase.js +238 -0
- package/richtexteditor/plugins/comments.js +15 -28
- package/richtexteditor/plugins/contentminimap.js +6 -5
- package/richtexteditor/plugins/crdt-engine.js +2201 -198
- package/richtexteditor/plugins/crdt-engine.min.js +5 -5
- package/richtexteditor/plugins/crossreference.js +408 -0
- package/richtexteditor/plugins/documentimport.js +915 -21
- package/richtexteditor/plugins/documentoutline.js +5 -4
- package/richtexteditor/plugins/docxexport.js +960 -0
- package/richtexteditor/plugins/draghandle.js +309 -0
- package/richtexteditor/plugins/footnotes.js +398 -0
- package/richtexteditor/plugins/formatpainter.js +298 -0
- package/richtexteditor/plugins/formattingmarks.js +246 -0
- package/richtexteditor/plugins/html2pdf.js +108 -4
- package/richtexteditor/plugins/imageeditor.js +37 -7
- package/richtexteditor/plugins/insertgallery.js +553 -170
- package/richtexteditor/plugins/keyboarda11y.js +316 -0
- package/richtexteditor/plugins/linenumbers.js +352 -0
- package/richtexteditor/plugins/linkchecker.js +396 -0
- package/richtexteditor/plugins/mentionplugin.js +4 -3
- package/richtexteditor/plugins/mergefields.js +328 -0
- package/richtexteditor/plugins/multilevellist.js +137 -0
- package/richtexteditor/plugins/pagination.js +505 -0
- package/richtexteditor/plugins/pdfexport.js +1137 -0
- package/richtexteditor/plugins/permanentpen.js +211 -0
- package/richtexteditor/plugins/revisionhistory.js +47 -34
- package/richtexteditor/plugins/rtlui.js +150 -0
- package/richtexteditor/plugins/sanitizer.js +510 -0
- package/richtexteditor/plugins/slashcommand.js +142 -17
- package/richtexteditor/plugins/spellcheck.js +6 -3
- package/richtexteditor/plugins/tableofcontents.js +353 -0
- package/richtexteditor/plugins/tabletools.js +342 -0
- package/richtexteditor/plugins/textdirection.js +313 -0
- package/richtexteditor/plugins/trackedchanges.js +37 -6
- package/richtexteditor/plugins/tui.image-editor/tui-code-snippet.min.js +1 -1
- package/richtexteditor/plugins/tui.image-editor/tui-color-picker.min.js +1 -1
- package/richtexteditor/plugins/tui.image-editor/tui-image-editor.min.js +1 -1
- package/richtexteditor/plugins/vendor/html2pdf.bundle.min.js +3 -0
- package/richtexteditor/plugins/watermark.js +214 -0
- package/richtexteditor/plugins/wordexport.js +0 -0
- package/richtexteditor/plugins/yjscollab.js +49 -11
- package/richtexteditor/rte-config.js +44 -44
- package/richtexteditor/rte.js +91 -63
- package/richtexteditor/rte_theme_default.css +483 -384
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
|
|
2
|
+
|
|
3
|
+
// 2026-07-27 Permanent pen. Turns on a fixed character format that applies to
|
|
4
|
+
// everything typed from then on, until switched off — the marker you pick up to
|
|
5
|
+
// annotate a document in a colour that is obviously not the original text.
|
|
6
|
+
//
|
|
7
|
+
// TinyMCE Permanent Pen: "This plugin is only available for paid TinyMCE
|
|
8
|
+
// subscriptions".
|
|
9
|
+
//
|
|
10
|
+
// Design notes:
|
|
11
|
+
// - The pen writes into a span that it OWNS and keeps extending, rather than
|
|
12
|
+
// re-wrapping every keystroke. Wrapping per character produces one span per
|
|
13
|
+
// letter, which bloats the HTML and makes the text impossible to edit
|
|
14
|
+
// sensibly afterwards.
|
|
15
|
+
// - It re-arms on selection changes. Click somewhere else and keep typing and
|
|
16
|
+
// the pen still applies, which is the entire point of "permanent" — but the
|
|
17
|
+
// new run gets its own span rather than reaching back to the old one.
|
|
18
|
+
// - Pen output is REAL CONTENT. Unlike the format painter's transient state,
|
|
19
|
+
// annotations written with the pen are meant to persist in the saved HTML.
|
|
20
|
+
RTE_DefaultConfig.plugin_permanentpen = RTE_Plugin_PermanentPen;
|
|
21
|
+
|
|
22
|
+
// The format the pen writes. Any CSS the host wants; these are the properties a
|
|
23
|
+
// reviewer's marker usually needs.
|
|
24
|
+
if (typeof RTE_DefaultConfig.permanentPenStyle === "undefined") {
|
|
25
|
+
RTE_DefaultConfig.permanentPenStyle = {
|
|
26
|
+
"color": "#c81e1e",
|
|
27
|
+
"font-weight": "700"
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// Class placed on every run the pen writes, so a host can style or find them.
|
|
31
|
+
if (typeof RTE_DefaultConfig.permanentPenClass === "undefined") RTE_DefaultConfig.permanentPenClass = "rte-pen";
|
|
32
|
+
|
|
33
|
+
function RTE_Plugin_PermanentPen() {
|
|
34
|
+
var obj = this;
|
|
35
|
+
var config, editor;
|
|
36
|
+
var boundDoc = null;
|
|
37
|
+
var active = false;
|
|
38
|
+
var currentRun = null; // the span this pen stroke is currently extending
|
|
39
|
+
|
|
40
|
+
obj.PluginName = "PermanentPen";
|
|
41
|
+
|
|
42
|
+
obj.InitConfig = function (argconfig) { config = argconfig; };
|
|
43
|
+
|
|
44
|
+
obj.InitEditor = function (argeditor) {
|
|
45
|
+
editor = argeditor;
|
|
46
|
+
|
|
47
|
+
editor.attachEvent("exec_command_permanentpen", function (state) {
|
|
48
|
+
state.returnValue = true;
|
|
49
|
+
obj.Toggle();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
setup();
|
|
53
|
+
try { editor.attachEvent("ready", setup); } catch (e) {}
|
|
54
|
+
try { editor.attachEvent("aftersethtml", setup); } catch (e) {}
|
|
55
|
+
setTimeout(setup, 0);
|
|
56
|
+
|
|
57
|
+
// Public API.
|
|
58
|
+
editor.setPermanentPen = function (on) { return apply(!!on); };
|
|
59
|
+
editor.togglePermanentPen = function () { return apply(!active); };
|
|
60
|
+
editor.isPermanentPenActive = function () { return active; };
|
|
61
|
+
editor.setPermanentPenStyle = function (style) {
|
|
62
|
+
if (style && typeof style === "object") { config.permanentPenStyle = style; currentRun = null; }
|
|
63
|
+
return config.permanentPenStyle;
|
|
64
|
+
};
|
|
65
|
+
editor.getPermanentPenStyle = function () {
|
|
66
|
+
var s = config.permanentPenStyle || {}, out = {};
|
|
67
|
+
for (var k in s) if (s.hasOwnProperty(k)) out[k] = s[k];
|
|
68
|
+
return out;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
function setup() {
|
|
73
|
+
var doc = getDoc();
|
|
74
|
+
if (!doc || doc === boundDoc) return;
|
|
75
|
+
boundDoc = doc;
|
|
76
|
+
var editable = getEditable();
|
|
77
|
+
if (!editable) return;
|
|
78
|
+
// beforeinput fires while the caret is still where the text will land,
|
|
79
|
+
// which is when the run has to exist for the character to go into it.
|
|
80
|
+
editable.addEventListener("beforeinput", onBeforeInput, true);
|
|
81
|
+
// Moving the caret ends the current stroke: the next typing starts a new
|
|
82
|
+
// run instead of teleporting text into the old one.
|
|
83
|
+
editable.addEventListener("mouseup", function () { currentRun = null; });
|
|
84
|
+
editable.addEventListener("keydown", function (e) {
|
|
85
|
+
if (/^Arrow|^Home|^End|^Page/.test(e.key)) currentRun = null;
|
|
86
|
+
});
|
|
87
|
+
editable.addEventListener("blur", function () { currentRun = null; });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getDoc() { try { return editor.getDocument(); } catch (e) { return null; } }
|
|
91
|
+
function getEditable() { try { return editor.getEditable(); } catch (e) { return null; } }
|
|
92
|
+
|
|
93
|
+
function apply(on) {
|
|
94
|
+
var wasActive = active;
|
|
95
|
+
active = !!on;
|
|
96
|
+
var editable = getEditable();
|
|
97
|
+
if (editable && editable.classList) {
|
|
98
|
+
if (active) editable.classList.add("rte-pen-active");
|
|
99
|
+
else editable.classList.remove("rte-pen-active");
|
|
100
|
+
}
|
|
101
|
+
// Switching OFF has to move the caret out of the run. Typing continues
|
|
102
|
+
// in whatever element the caret is in, so leaving it inside the styled
|
|
103
|
+
// span means the pen visibly refuses to turn off — the user keeps typing
|
|
104
|
+
// red text after pressing the button that was supposed to stop that.
|
|
105
|
+
if (wasActive && !active) stepOutOfRun();
|
|
106
|
+
currentRun = null;
|
|
107
|
+
return active;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function stepOutOfRun() {
|
|
111
|
+
var doc = getDoc();
|
|
112
|
+
if (!doc) return;
|
|
113
|
+
var sel;
|
|
114
|
+
try { sel = editor.getSelection(); } catch (e) { return; }
|
|
115
|
+
if (!sel || sel.rangeCount === 0) return;
|
|
116
|
+
var run = closestPenRun(sel.getRangeAt(0).startContainer);
|
|
117
|
+
if (!run || !run.parentNode) return;
|
|
118
|
+
tidyRun(run);
|
|
119
|
+
try {
|
|
120
|
+
var r = doc.createRange();
|
|
121
|
+
r.setStartAfter(run);
|
|
122
|
+
r.collapse(true);
|
|
123
|
+
sel.removeAllRanges();
|
|
124
|
+
sel.addRange(r);
|
|
125
|
+
} catch (e) {}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// The run is seeded with a zero-width space so the caret has somewhere to
|
|
129
|
+
// live inside an otherwise empty inline element. Once the run has real text
|
|
130
|
+
// that placeholder is just litter in the saved HTML, so drop it.
|
|
131
|
+
function tidyRun(run) {
|
|
132
|
+
if (!run) return;
|
|
133
|
+
var text = run.textContent || "";
|
|
134
|
+
if (text.replace(//g, "") === "") {
|
|
135
|
+
// Nothing was ever typed: remove the empty run entirely.
|
|
136
|
+
if (run.parentNode) run.parentNode.removeChild(run);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
var walker = run.ownerDocument.createTreeWalker(run, 4 /* SHOW_TEXT */, null, false);
|
|
140
|
+
var n;
|
|
141
|
+
while ((n = walker.nextNode())) {
|
|
142
|
+
if (n.nodeValue.indexOf("") >= 0) n.nodeValue = n.nodeValue.replace(//g, "");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function onBeforeInput(e) {
|
|
147
|
+
if (!active) return;
|
|
148
|
+
// Only ordinary typing. Deletions, formatting commands and paste keep
|
|
149
|
+
// their normal behaviour — a pen that hijacked paste would be a menace.
|
|
150
|
+
if (e.inputType && e.inputType !== "insertText" && e.inputType !== "insertCompositionText") return;
|
|
151
|
+
|
|
152
|
+
var doc = getDoc();
|
|
153
|
+
if (!doc) return;
|
|
154
|
+
var sel;
|
|
155
|
+
try { sel = editor.getSelection(); } catch (x) { return; }
|
|
156
|
+
if (!sel || sel.rangeCount === 0) return;
|
|
157
|
+
var range = sel.getRangeAt(0);
|
|
158
|
+
if (!range.collapsed) return; // replacing a selection: let it be
|
|
159
|
+
|
|
160
|
+
// Already inside our current run — nothing to do, the character will
|
|
161
|
+
// land in it naturally.
|
|
162
|
+
if (currentRun && containsNode(currentRun, range.startContainer)) return;
|
|
163
|
+
|
|
164
|
+
// Already inside an identically-styled pen run (e.g. the user clicked
|
|
165
|
+
// back into an annotation): adopt it rather than nesting a new span.
|
|
166
|
+
var existing = closestPenRun(range.startContainer);
|
|
167
|
+
if (existing && sameStyle(existing)) { currentRun = existing; return; }
|
|
168
|
+
|
|
169
|
+
var span = doc.createElement("span");
|
|
170
|
+
span.className = String(config.permanentPenClass || "rte-pen");
|
|
171
|
+
var style = config.permanentPenStyle || {};
|
|
172
|
+
for (var k in style) if (style.hasOwnProperty(k)) span.style.setProperty(k, style[k]);
|
|
173
|
+
// A zero-width space gives the caret something real to sit inside; an
|
|
174
|
+
// empty inline element cannot hold a caret, so the first character would
|
|
175
|
+
// land outside the span and the pen would appear not to work.
|
|
176
|
+
span.appendChild(doc.createTextNode(""));
|
|
177
|
+
|
|
178
|
+
range.insertNode(span);
|
|
179
|
+
var r = doc.createRange();
|
|
180
|
+
r.setStart(span.firstChild, 1); // after the ZWSP
|
|
181
|
+
r.collapse(true);
|
|
182
|
+
sel.removeAllRanges();
|
|
183
|
+
sel.addRange(r);
|
|
184
|
+
currentRun = span;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function containsNode(root, node) {
|
|
188
|
+
while (node) { if (node === root) return true; node = node.parentNode; }
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function closestPenRun(node) {
|
|
193
|
+
var editable = getEditable();
|
|
194
|
+
var cls = String(config.permanentPenClass || "rte-pen");
|
|
195
|
+
var n = node && node.nodeType === 3 ? node.parentNode : node;
|
|
196
|
+
while (n && n !== editable) {
|
|
197
|
+
if (n.nodeType === 1 && n.classList && n.classList.contains(cls)) return n;
|
|
198
|
+
n = n.parentNode;
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function sameStyle(el) {
|
|
204
|
+
var style = config.permanentPenStyle || {};
|
|
205
|
+
for (var k in style) {
|
|
206
|
+
if (!style.hasOwnProperty(k)) continue;
|
|
207
|
+
if (el.style.getPropertyValue(k) !== String(style[k])) return false;
|
|
208
|
+
}
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -23,6 +23,8 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
23
23
|
config.revisionHistoryMaxEntries = config.revisionHistoryMaxEntries || 50;
|
|
24
24
|
config.revisionHistoryAutoSnapshotMs = config.revisionHistoryAutoSnapshotMs || 0;
|
|
25
25
|
config.revisionHistoryUrl = config.revisionHistoryUrl || "";
|
|
26
|
+
config.revisionHistoryRequestHeaders = config.revisionHistoryRequestHeaders || null;
|
|
27
|
+
config.onRevisionHistorySyncError = config.onRevisionHistorySyncError || null;
|
|
26
28
|
config.text_revisionhistory = config.text_revisionhistory || "Revision history";
|
|
27
29
|
|
|
28
30
|
appendToolbarCommand("toolbar_default", "#{revisionhistory}");
|
|
@@ -143,14 +145,46 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
143
145
|
var xhr = new XMLHttpRequest();
|
|
144
146
|
xhr.open("POST", config.revisionHistoryUrl, true);
|
|
145
147
|
xhr.setRequestHeader("Content-Type", "application/json");
|
|
148
|
+
var headers = resolveRequestHeaders();
|
|
149
|
+
for (var name in headers) {
|
|
150
|
+
if (!Object.prototype.hasOwnProperty.call(headers, name)) continue;
|
|
151
|
+
if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name)) continue;
|
|
152
|
+
var value = String(headers[name]);
|
|
153
|
+
if (/\r|\n/.test(value)) continue;
|
|
154
|
+
try { xhr.setRequestHeader(name, value); } catch (ignore) { }
|
|
155
|
+
}
|
|
156
|
+
xhr.onerror = function () { reportRemoteFailure({ reason: "network", status: xhr.status || 0 }); };
|
|
157
|
+
xhr.onload = function () {
|
|
158
|
+
if (xhr.status < 200 || xhr.status >= 300) {
|
|
159
|
+
reportRemoteFailure({ reason: "http", status: xhr.status, responseText: (xhr.responseText || "").slice(0, 512) });
|
|
160
|
+
}
|
|
161
|
+
};
|
|
146
162
|
xhr.send(JSON.stringify({
|
|
147
163
|
documentKey: config.aiToolkitPersistenceKey || "",
|
|
148
164
|
savedAt: Date.now(),
|
|
149
165
|
revisions: store
|
|
150
166
|
}));
|
|
151
167
|
} catch (err) {
|
|
152
|
-
|
|
168
|
+
reportRemoteFailure({ reason: "exception", error: err });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function resolveRequestHeaders() {
|
|
173
|
+
var source = config.revisionHistoryRequestHeaders;
|
|
174
|
+
if (typeof source === "function") {
|
|
175
|
+
try { source = source(editor); }
|
|
176
|
+
catch (err) {
|
|
177
|
+
reportRemoteFailure({ reason: "header-provider", error: err });
|
|
178
|
+
return {};
|
|
179
|
+
}
|
|
153
180
|
}
|
|
181
|
+
return source && typeof source === "object" ? source : {};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function reportRemoteFailure(detail) {
|
|
185
|
+
console.warn("revisionhistory: remote persistence failed:", detail && detail.reason, detail && detail.status);
|
|
186
|
+
try { typeof config.onRevisionHistorySyncError === "function" && config.onRevisionHistorySyncError(detail); } catch (ignore) { }
|
|
187
|
+
try { typeof editor.fireEvent === "function" && editor.fireEvent("revision_history_sync_error", detail); } catch (ignore2) { }
|
|
154
188
|
}
|
|
155
189
|
|
|
156
190
|
function restoreStore() {
|
|
@@ -346,8 +380,7 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
346
380
|
var host = editor.iframe.ownerDocument;
|
|
347
381
|
if (!host || !host.body) return;
|
|
348
382
|
dialog = host.createElement("div");
|
|
349
|
-
// Body-appended modal
|
|
350
|
-
// dark is handled by a prefers-color-scheme media query in the styles).
|
|
383
|
+
// Body-appended modal mirrors only an editor explicitly forced dark.
|
|
351
384
|
var __revDark = false;
|
|
352
385
|
try {
|
|
353
386
|
var __h = editor.container || (editor.getEditable && editor.getEditable().closest && editor.getEditable().closest(".richtexteditor"));
|
|
@@ -585,11 +618,12 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
585
618
|
style.textContent = [
|
|
586
619
|
".rte-rev-dialog{position:fixed;inset:0;z-index:2147483600;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:13px;color:#172033}",
|
|
587
620
|
".rte-rev-backdrop{position:absolute;inset:0;background:linear-gradient(135deg,rgba(12,18,32,.44),rgba(40,54,77,.26));display:flex;align-items:center;justify-content:center;padding:18px}",
|
|
588
|
-
"
|
|
621
|
+
"/* 2026-07-12 revision dialog precision */",
|
|
622
|
+
".rte-rev-panel{background:linear-gradient(180deg,#ffffff 0%,#f8fbff 100%);border:1px solid rgba(117,137,163,.28);border-radius:8px;width:min(960px,calc(100vw - 28px));max-height:min(82vh,740px);display:flex;flex-direction:column;box-shadow:0 16px 40px rgba(35,48,72,.18);overflow:hidden}",
|
|
589
623
|
".rte-rev-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border-bottom:1px solid rgba(117,137,163,.18);background:rgba(255,255,255,.72)}",
|
|
590
624
|
".rte-rev-title{font-size:16px;font-weight:760;letter-spacing:-.01em;color:#172033}",
|
|
591
625
|
".rte-rev-header-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}",
|
|
592
|
-
".rte-rev-btn{padding:8px 12px;border-radius:
|
|
626
|
+
".rte-rev-btn{padding:8px 12px;border-radius:7px;border:1px solid rgba(116,135,162,.25);font:inherit;font-size:12px;font-weight:680;line-height:1;cursor:pointer;background:#fff;color:#223652;box-shadow:0 1px 2px rgba(32,45,66,.06);transition:background .14s ease,border-color .14s ease,box-shadow .14s ease,transform .14s ease}",
|
|
593
627
|
".rte-rev-btn:hover{background:#f5f8fc;border-color:rgba(81,111,151,.42);box-shadow:0 5px 14px rgba(32,45,66,.09);transform:translateY(-1px)}",
|
|
594
628
|
".rte-rev-btn:focus-visible{outline:2px solid rgba(56,122,255,.34);outline-offset:2px}",
|
|
595
629
|
".rte-rev-btn-ghost{background:#fff;color:#34506f}",
|
|
@@ -598,8 +632,8 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
598
632
|
".rte-rev-btn-danger{background:#fff5f5;color:#9f1d1d;border-color:rgba(190,45,45,.22)}",
|
|
599
633
|
".rte-rev-body{display:flex;flex:1;min-height:0;background:linear-gradient(90deg,#f8fbff 0%,#fff 42%)}",
|
|
600
634
|
".rte-rev-list{width:258px;flex:0 0 258px;border-right:1px solid rgba(117,137,163,.18);overflow-y:auto;background:rgba(244,248,253,.78);padding:10px}",
|
|
601
|
-
".rte-rev-empty{padding:16px;border:1px dashed rgba(99,118,145,.28);border-radius:
|
|
602
|
-
".rte-rev-row{display:flex;align-items:center;gap:10px;width:100%;padding:10px;border:1px solid transparent;border-radius:
|
|
635
|
+
".rte-rev-empty{padding:16px;border:1px dashed rgba(99,118,145,.28);border-radius:8px;background:#fff;color:#5b6c81;font-size:12px;line-height:1.5}",
|
|
636
|
+
".rte-rev-row{display:flex;align-items:center;gap:10px;width:100%;padding:10px;border:1px solid transparent;border-radius:8px;background:transparent;text-align:left;cursor:pointer;font:inherit;color:inherit}",
|
|
603
637
|
".rte-rev-row:hover{background:#fff;border-color:rgba(93,122,161,.24);box-shadow:0 8px 18px rgba(33,48,70,.08)}",
|
|
604
638
|
".rte-rev-row-active{background:#eef6ff;border-color:rgba(37,99,235,.28);box-shadow:0 8px 22px rgba(37,99,235,.12)}",
|
|
605
639
|
".rte-rev-dot{width:9px;height:9px;border-radius:50%;flex:0 0 9px;box-shadow:0 0 0 3px rgba(255,255,255,.95),0 0 0 4px rgba(96,116,140,.18)}",
|
|
@@ -607,12 +641,12 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
607
641
|
".rte-rev-row-who{font-weight:720;font-size:12px;line-height:1.25;color:#172033}",
|
|
608
642
|
".rte-rev-row-when{margin-top:2px;font-size:11px;color:#637487;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
|
|
609
643
|
".rte-rev-pane{flex:1;display:flex;flex-direction:column;min-width:0;padding:10px}",
|
|
610
|
-
".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:
|
|
611
|
-
".rte-rev-tab{padding:7px 12px;border:0;border-radius:
|
|
644
|
+
".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}",
|
|
645
|
+
".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}",
|
|
612
646
|
".rte-rev-tab:hover{color:#1d3557;background:rgba(255,255,255,.58)}",
|
|
613
647
|
".rte-rev-tab:focus-visible{outline:2px solid rgba(56,122,255,.34);outline-offset:2px}",
|
|
614
648
|
".rte-rev-tab-active{color:#163966;background:#fff;box-shadow:0 2px 7px rgba(35,48,72,.08)}",
|
|
615
|
-
".rte-rev-content{flex:1;overflow:auto;padding:0;border:1px solid rgba(117,137,163,.18);border-radius:
|
|
649
|
+
".rte-rev-content{flex:1;overflow:auto;padding:0;border:1px solid rgba(117,137,163,.18);border-radius:8px;background:#fff;min-height:300px}",
|
|
616
650
|
".rte-rev-preview-frame{width:100%;height:100%;min-height:330px;border:0;display:block;background:#fff}",
|
|
617
651
|
".rte-rev-diff{margin:0;padding:14px 16px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.55;white-space:pre-wrap;color:#172033;background:#fbfdff;min-height:100%}",
|
|
618
652
|
".rte-rev-diff-line{display:block}",
|
|
@@ -622,30 +656,9 @@ function RTE_Plugin_RevisionHistory() {
|
|
|
622
656
|
".rte-rev-footer{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:10px 0 0;background:transparent}",
|
|
623
657
|
".rte-rev-footer-info{font-size:12px;color:#637487;min-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
624
658
|
".rte-rev-footer-btns{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end}",
|
|
625
|
-
"@media(max-width:720px){.rte-rev-backdrop{padding:10px;align-items:flex-start}.rte-rev-panel{width:calc(100vw - 20px);max-height:calc(100vh - 20px);border-radius:
|
|
626
|
-
//
|
|
627
|
-
//
|
|
628
|
-
// preview iframe stays white (it renders the actual document).
|
|
629
|
-
"@media (prefers-color-scheme:dark){",
|
|
630
|
-
" .rte-rev-dialog{color:#e2e8f0}",
|
|
631
|
-
" .rte-rev-panel{background:linear-gradient(180deg,#1e293b,#0f172a);border-color:#334155}",
|
|
632
|
-
" .rte-rev-header{background:rgba(30,41,59,.72);border-color:#334155}",
|
|
633
|
-
" .rte-rev-title,.rte-rev-row-who{color:#e2e8f0}",
|
|
634
|
-
" .rte-rev-btn,.rte-rev-btn-ghost{background:#0f172a;color:#cbd5e1;border-color:#334155}",
|
|
635
|
-
" .rte-rev-btn:hover{background:#334155}",
|
|
636
|
-
" .rte-rev-btn-primary,.rte-rev-btn-primary:hover{background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;border-color:#3b82f6}",
|
|
637
|
-
" .rte-rev-body{background:#0f172a}",
|
|
638
|
-
" .rte-rev-list{background:rgba(15,23,42,.6);border-color:#334155}",
|
|
639
|
-
" .rte-rev-empty{background:#1e293b;color:#94a3b8;border-color:#334155}",
|
|
640
|
-
" .rte-rev-row:hover{background:#1e293b;border-color:#334155}",
|
|
641
|
-
" .rte-rev-row-active{background:#1e3a8a;border-color:#3b82f6}",
|
|
642
|
-
" .rte-rev-row-when,.rte-rev-footer-info{color:#94a3b8}",
|
|
643
|
-
" .rte-rev-tabs{background:#1e293b;border-color:#334155}",
|
|
644
|
-
" .rte-rev-tab{color:#94a3b8}.rte-rev-tab-active{color:#fff;background:#1d4ed8}",
|
|
645
|
-
" .rte-rev-diff{background:#0f172a;color:#cbd5e1}.rte-rev-diff-eq{color:#94a3b8}",
|
|
646
|
-
" .rte-rev-diff-add{background:rgba(16,185,129,.16);color:#6ee7b7}",
|
|
647
|
-
" .rte-rev-diff-del{background:rgba(244,63,94,.16);color:#fda4af}",
|
|
648
|
-
"}",
|
|
659
|
+
"@media(max-width:720px){.rte-rev-backdrop{padding:10px;align-items:flex-start}.rte-rev-panel{width:calc(100vw - 20px);max-height:calc(100vh - 20px);border-radius:8px}.rte-rev-header{align-items:flex-start;flex-direction:column}.rte-rev-body{flex-direction:column}.rte-rev-list{width:auto;flex:0 0 auto;max-height:178px;border-right:0;border-bottom:1px solid rgba(117,137,163,.18)}.rte-rev-pane{min-height:360px}.rte-rev-footer{align-items:stretch;flex-direction:column}.rte-rev-footer-info{white-space:normal}.rte-rev-footer-btns{justify-content:stretch}.rte-rev-footer-btns .rte-rev-btn{flex:1}}",
|
|
660
|
+
// Revision history stays light unless the editor explicitly adds
|
|
661
|
+
// rte-rev-dark. The preview frame still renders document content.
|
|
649
662
|
".rte-rev-dark{color:#e2e8f0}",
|
|
650
663
|
".rte-rev-dark .rte-rev-panel{background:linear-gradient(180deg,#1e293b,#0f172a);border-color:#334155}",
|
|
651
664
|
".rte-rev-dark .rte-rev-header{background:rgba(30,41,59,.72);border-color:#334155}",
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
|
|
2
|
+
|
|
3
|
+
// 2026-07-31 Right-to-left EDITOR CHROME. textdirection.js flips the document;
|
|
4
|
+
// this flips the editor around it — toolbar, dropdowns, menus, dialogs — so an
|
|
5
|
+
// Arabic or Hebrew user is not typing right-to-left text inside a left-to-right
|
|
6
|
+
// application.
|
|
7
|
+
//
|
|
8
|
+
// Deliberately a plugin rather than an edit to rte_theme_default.css:
|
|
9
|
+
// - the theme is 146 KB, shared by every tier, and cached behind a fixed
|
|
10
|
+
// "?v=" query, so changing it means a cache-buster bump across the site
|
|
11
|
+
// - LTR users get zero new CSS; every rule here is scoped under [dir="rtl"]
|
|
12
|
+
// - it rides the existing plugin bundle, which is already audited for drift
|
|
13
|
+
//
|
|
14
|
+
// Why this is mostly small: the chrome is flexbox with NO floats, no
|
|
15
|
+
// text-align:left/right and no left:0/right:0 anchors, so `direction: rtl`
|
|
16
|
+
// reverses the layout on its own. What remains is the handful of PHYSICAL
|
|
17
|
+
// margins/paddings the theme still uses, which have to be mirrored by hand.
|
|
18
|
+
//
|
|
19
|
+
// Icons are deliberately NOT mirrored. Word does not flip its toolbar icons in
|
|
20
|
+
// RTL either: bold, italic and the alignment glyphs mean the same thing in both
|
|
21
|
+
// directions, and flipping them makes the toolbar harder to read, not easier.
|
|
22
|
+
// The only icons that SHOULD flip are the ones that encode reading order --
|
|
23
|
+
// indent/outdent and the menu disclosure arrow -- and those are handled below.
|
|
24
|
+
RTE_DefaultConfig.plugin_rtlui = RTE_Plugin_RtlUi;
|
|
25
|
+
|
|
26
|
+
// "auto" = mirror when the document/base direction is RTL
|
|
27
|
+
// true = always mirror | false = never
|
|
28
|
+
if (typeof RTE_DefaultConfig.rtlUserInterface === "undefined") RTE_DefaultConfig.rtlUserInterface = "auto";
|
|
29
|
+
|
|
30
|
+
function RTE_Plugin_RtlUi() {
|
|
31
|
+
var obj = this;
|
|
32
|
+
var config, editor;
|
|
33
|
+
var applied = false;
|
|
34
|
+
|
|
35
|
+
obj.PluginName = "RtlUi";
|
|
36
|
+
|
|
37
|
+
obj.InitConfig = function (argconfig) { config = argconfig; };
|
|
38
|
+
|
|
39
|
+
obj.InitEditor = function (argeditor) {
|
|
40
|
+
editor = argeditor;
|
|
41
|
+
|
|
42
|
+
editor.attachEvent("exec_command_rtlui", function (state) {
|
|
43
|
+
state.returnValue = true;
|
|
44
|
+
obj.Toggle();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
setup();
|
|
48
|
+
try { editor.attachEvent("ready", setup); } catch (e) {}
|
|
49
|
+
try { editor.attachEvent("aftersethtml", setup); } catch (e) {}
|
|
50
|
+
setTimeout(setup, 0);
|
|
51
|
+
|
|
52
|
+
editor.setRtlUserInterface = function (on) { return apply(!!on); };
|
|
53
|
+
editor.toggleRtlUserInterface = function () { return apply(!applied); };
|
|
54
|
+
editor.isRtlUserInterface = function () { return applied; };
|
|
55
|
+
editor.getRtlUiCss = function () { return css(); };
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
obj.Toggle = function () { return apply(!applied); };
|
|
59
|
+
|
|
60
|
+
function setup() {
|
|
61
|
+
injectStyles();
|
|
62
|
+
if (config.rtlUserInterface === true) apply(true);
|
|
63
|
+
else if (config.rtlUserInterface === false) apply(false);
|
|
64
|
+
else apply(detect());
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// "auto": follow the document's own direction.
|
|
68
|
+
function detect() {
|
|
69
|
+
try {
|
|
70
|
+
if (typeof editor.getBaseDirection === "function" && editor.getBaseDirection() === "rtl") return true;
|
|
71
|
+
var ed = editor.getEditable();
|
|
72
|
+
if (ed) {
|
|
73
|
+
var doc = ed.ownerDocument;
|
|
74
|
+
var win = doc && (doc.defaultView || doc.parentWindow);
|
|
75
|
+
if (win && win.getComputedStyle(ed).direction === "rtl") return true;
|
|
76
|
+
}
|
|
77
|
+
} catch (e) {}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// The editor shell lives in the HOST document, not the editable iframe, so
|
|
82
|
+
// the class and the stylesheet both belong there.
|
|
83
|
+
function shell() {
|
|
84
|
+
try {
|
|
85
|
+
var ed = editor.getEditable();
|
|
86
|
+
if (!ed) return null;
|
|
87
|
+
// The iframe's frameElement walks us back out to the host page.
|
|
88
|
+
var win = ed.ownerDocument.defaultView;
|
|
89
|
+
var node = (win && win.frameElement) ? win.frameElement : ed;
|
|
90
|
+
while (node && node.classList && !node.classList.contains("richtexteditor")) node = node.parentNode;
|
|
91
|
+
return (node && node.classList) ? node : null;
|
|
92
|
+
} catch (e) { return null; }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function apply(on) {
|
|
96
|
+
var el = shell();
|
|
97
|
+
// Set the flag only once the change has actually landed. Setting it
|
|
98
|
+
// first meant isRtlUserInterface() reported success when the shell could
|
|
99
|
+
// not be resolved (the editor not yet built, or a host that reparents
|
|
100
|
+
// it) -- a state that reads as "mirrored" while nothing is mirrored.
|
|
101
|
+
if (!el) return applied;
|
|
102
|
+
if (on) el.setAttribute("dir", "rtl");
|
|
103
|
+
else el.removeAttribute("dir");
|
|
104
|
+
applied = !!on;
|
|
105
|
+
return applied;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function css() {
|
|
109
|
+
return [
|
|
110
|
+
// Flexbox does the bulk of the work once direction is set.
|
|
111
|
+
'.richtexteditor[dir="rtl"]{direction:rtl;}',
|
|
112
|
+
// Menus and dropdown panels open from the correct edge.
|
|
113
|
+
'.richtexteditor[dir="rtl"] rte-dropdown,',
|
|
114
|
+
'.richtexteditor[dir="rtl"] rte-submenu,',
|
|
115
|
+
'.richtexteditor[dir="rtl"] rte-floatpanel{direction:rtl;text-align:right;}',
|
|
116
|
+
'.richtexteditor[dir="rtl"] rte-menuitem{flex-direction:row-reverse;}',
|
|
117
|
+
// Mirror the theme's remaining PHYSICAL spacing.
|
|
118
|
+
'.richtexteditor[dir="rtl"] rte-menutext{margin-left:0;margin-right:3px;}',
|
|
119
|
+
'.richtexteditor[dir="rtl"] rte-menuarrow{margin-right:0;margin-left:4px;}',
|
|
120
|
+
'.richtexteditor[dir="rtl"] rte-ribbon-group-right{margin-left:2px;margin-right:5px;}',
|
|
121
|
+
'.richtexteditor[dir="rtl"] rte-toolbar-arrowbutton{padding-right:0;padding-left:12px;}',
|
|
122
|
+
'.richtexteditor[dir="rtl"] rte-toolbar-dropdown-input{padding-left:0;padding-right:3px;}',
|
|
123
|
+
'.richtexteditor[dir="rtl"] rte-dialog-line-target rte-dialog-input-label{padding-left:0;padding-right:20px;}',
|
|
124
|
+
// margin-*:auto is a push-to-the-far-edge idiom; the edge changes.
|
|
125
|
+
'.richtexteditor[dir="rtl"] .rte-find-replace-all{margin-left:0;margin-right:auto;}',
|
|
126
|
+
'.richtexteditor[dir="rtl"] .rte-gallery-browser-footer-text{margin-right:0;margin-left:auto;}',
|
|
127
|
+
// Status bar and tag list read from the right.
|
|
128
|
+
'.richtexteditor[dir="rtl"] rte-bottom,',
|
|
129
|
+
'.richtexteditor[dir="rtl"] rte-taglist,',
|
|
130
|
+
'.richtexteditor[dir="rtl"] rte-textcounter{direction:rtl;}',
|
|
131
|
+
// Resize grip moves to the opposite corner.
|
|
132
|
+
'.richtexteditor[dir="rtl"] rte-resizecorner{transform:scaleX(-1);}',
|
|
133
|
+
// ONLY the icons that encode reading order flip. Bold/italic/align
|
|
134
|
+
// glyphs deliberately do not.
|
|
135
|
+
'.richtexteditor[dir="rtl"] [class*="indent"] svg,',
|
|
136
|
+
'.richtexteditor[dir="rtl"] [class*="outdent"] svg,',
|
|
137
|
+
'.richtexteditor[dir="rtl"] rte-menuarrow svg{transform:scaleX(-1);}'
|
|
138
|
+
].join("\n");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function injectStyles() {
|
|
142
|
+
// Host document: this styles the editor chrome, not the content.
|
|
143
|
+
var doc = document;
|
|
144
|
+
if (doc.getElementById("rte-rtl-ui-styles")) return;
|
|
145
|
+
var st = doc.createElement("style");
|
|
146
|
+
st.id = "rte-rtl-ui-styles";
|
|
147
|
+
st.appendChild(doc.createTextNode(css()));
|
|
148
|
+
(doc.head || doc.documentElement).appendChild(st);
|
|
149
|
+
}
|
|
150
|
+
}
|