@richscripts/richtexteditor 2.5.3 → 2.6.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.
@@ -0,0 +1,303 @@
1
+ /*
2
+ * RichTextEditor — Watchdog (rte-watchdog.js)
3
+ * ------------------------------------------------------------------
4
+ * 2026-08-30. Optional, un-obfuscated companion to rte.js: keeps an editor alive
5
+ * across an unexpected error, and — more importantly — keeps the author's text.
6
+ *
7
+ * Why this exists. CKEditor 5 ships `Watchdog`; nothing here did. The gap is not
8
+ * a feature-list checkbox, it is the worst five seconds a writer can have: an
9
+ * uncaught exception leaves the editing surface half-dead — keystrokes land but
10
+ * commands throw, or the iframe stops accepting input — and the page looks fine,
11
+ * so nobody reloads until an hour of writing is already gone.
12
+ *
13
+ * What it does:
14
+ * - snapshots the content on an interval and after every change, so there is
15
+ * always a recent known-good document to restore;
16
+ * - notices an uncaught error or rejection that came from the editor's own
17
+ * code, and re-checks the editing surface before believing it;
18
+ * - destroys and recreates the instance, then puts the snapshot back;
19
+ * - refuses to do that forever. Restarting into the same crash is a loop that
20
+ * eats the document a little more each pass, so after `crashNumberLimit`
21
+ * crashes without a quiet period in between it stops, reports
22
+ * "crashedPermanently", and holds the last snapshot for the host page to
23
+ * save somewhere it trusts.
24
+ *
25
+ * What it deliberately does NOT do: guess. An error thrown by unrelated page code
26
+ * is none of its business — restarting the editor because an analytics script
27
+ * threw would destroy the selection, the undo stack and any in-progress dialog
28
+ * for no reason. Only errors whose stack names an editor script, or a genuinely
29
+ * unresponsive editing surface, count as a crash. `getDiagnostics()` reports the
30
+ * errors it saw and ignored, so a real crash that was filtered out is findable
31
+ * rather than invisible.
32
+ *
33
+ * var wd = new RichTextEditorWatchdog();
34
+ * wd.on("restart", function (info) { console.log("recovered", info.crashes); });
35
+ * wd.on("crashedPermanently", function (info) { saveDraft(info.data); });
36
+ * wd.create(document.getElementById("host"), RTE_CreateConfig());
37
+ * wd.editor // the live instance — re-read it after a restart
38
+ * wd.getData() // last known-good HTML, valid even while crashed
39
+ *
40
+ * Loads after rte-config.js + rte.js. Un-obfuscated on purpose: recovery policy
41
+ * is something an integrator should be able to read and change.
42
+ */
43
+ (function (global) {
44
+ "use strict";
45
+
46
+ var STATE_INITIALIZING = "initializing";
47
+ var STATE_READY = "ready";
48
+ var STATE_CRASHED = "crashed";
49
+ var STATE_PERMANENT = "crashedPermanently";
50
+ var STATE_DESTROYED = "destroyed";
51
+
52
+ function RichTextEditorWatchdog(options) {
53
+ if (!(this instanceof RichTextEditorWatchdog)) return new RichTextEditorWatchdog(options);
54
+ options = options || {};
55
+
56
+ this.state = STATE_INITIALIZING;
57
+ this.editor = null;
58
+ this.crashes = [];
59
+
60
+ this._host = null;
61
+ this._config = null;
62
+ this._data = "";
63
+ this._listeners = {};
64
+ this._ignoredErrors = [];
65
+ this._timer = null;
66
+ this._destroyed = false;
67
+
68
+ // How many crashes without a quiet stretch before giving up. CKEditor's
69
+ // default is 3 and the reasoning holds: one crash is an accident, three
70
+ // in a row is a bug that restarting will not fix.
71
+ this._crashNumberLimit = typeof options.crashNumberLimit === "number" ? options.crashNumberLimit : 3;
72
+ // A crash this long after the previous one starts the count again.
73
+ this._minimumNonErrorTimePeriod = typeof options.minimumNonErrorTimePeriod === "number"
74
+ ? options.minimumNonErrorTimePeriod : 5000;
75
+ this._saveInterval = typeof options.saveInterval === "number" ? options.saveInterval : 5000;
76
+
77
+ // Swap these to supervise something other than a plain `new RichTextEditor`
78
+ // (a framework wrapper, a headless instance, a preconfigured factory).
79
+ this._creator = typeof options.creator === "function"
80
+ ? options.creator
81
+ : function (host, config) { return new global.RichTextEditor(host, config); };
82
+ this._destructor = typeof options.destructor === "function"
83
+ ? options.destructor
84
+ : function (editor) { if (editor && typeof editor.destroy === "function") editor.destroy(); };
85
+
86
+ // Which scripts count as "ours". An error is only a crash if its stack
87
+ // names one of them — see the comment at the top of the file.
88
+ this._ownScriptPattern = options.ownScriptPattern instanceof RegExp
89
+ ? options.ownScriptPattern
90
+ : /(^|[\/\\])(rte|rte-headless|rte-watchdog|all_plugins)([.\-][\w.]*)?\.js|RTE_Plugin_|RichTextEditor/;
91
+
92
+ var self = this;
93
+ this._onError = function (event) { self._handleError(event && event.error, event && event.message, event); };
94
+ this._onRejection = function (event) {
95
+ var reason = event && event.reason;
96
+ self._handleError(reason instanceof Error ? reason : null, String(reason && reason.message || reason || ""), event);
97
+ };
98
+ global.addEventListener("error", this._onError);
99
+ global.addEventListener("unhandledrejection", this._onRejection);
100
+ }
101
+
102
+ var proto = RichTextEditorWatchdog.prototype;
103
+
104
+ // ------------------------------------------------------------------ events
105
+
106
+ proto.on = function (name, callback) {
107
+ if (typeof callback !== "function") return this;
108
+ (this._listeners[name] = this._listeners[name] || []).push(callback);
109
+ return this;
110
+ };
111
+
112
+ proto.off = function (name, callback) {
113
+ var list = this._listeners[name];
114
+ if (!list) return this;
115
+ for (var i = list.length - 1; i >= 0; i--) if (list[i] === callback) list.splice(i, 1);
116
+ return this;
117
+ };
118
+
119
+ proto._emit = function (name, payload) {
120
+ var list = (this._listeners[name] || []).slice();
121
+ for (var i = 0; i < list.length; i++) {
122
+ // A throwing listener must not take down the recovery it was told about.
123
+ try { list[i].call(this, payload); } catch (e) {}
124
+ }
125
+ };
126
+
127
+ // ------------------------------------------------------------- lifecycle
128
+
129
+ proto.create = function (host, config) {
130
+ if (this._destroyed) throw new Error("This watchdog has been destroyed.");
131
+ this._host = host || this._host;
132
+ this._config = config || this._config;
133
+ return this._start();
134
+ };
135
+
136
+ proto._start = function () {
137
+ var self = this;
138
+ this.editor = this._creator(this._host, this._config);
139
+ this.state = STATE_READY;
140
+
141
+ // Snapshot on change AND on a timer. Change alone misses the case that
142
+ // matters most — a crash mid-edit that stops change events firing — and
143
+ // the timer alone can be up to `saveInterval` stale.
144
+ try {
145
+ this.editor.attachEvent("change", function () { self._snapshot(); });
146
+ } catch (e) {}
147
+ this._snapshot();
148
+ if (this._saveInterval > 0) {
149
+ this._timer = global.setInterval(function () { self._snapshot(); }, this._saveInterval);
150
+ }
151
+
152
+ this._emit("ready", { editor: this.editor });
153
+ return this.editor;
154
+ };
155
+
156
+ proto.destroy = function () {
157
+ if (this._destroyed) return;
158
+ this._destroyed = true;
159
+ this.state = STATE_DESTROYED;
160
+ if (this._timer) { global.clearInterval(this._timer); this._timer = null; }
161
+ global.removeEventListener("error", this._onError);
162
+ global.removeEventListener("unhandledrejection", this._onRejection);
163
+ try { this._destructor(this.editor); } catch (e) {}
164
+ this.editor = null;
165
+ this._emit("destroy", {});
166
+ };
167
+
168
+ // ------------------------------------------------------------------- data
169
+
170
+ proto.getData = function () { return this._data; };
171
+
172
+ proto._snapshot = function () {
173
+ if (!this.editor) return;
174
+ try {
175
+ var html = this.editor.getHTMLCode();
176
+ // A crashing editor can start returning "" or null. Overwriting a good
177
+ // snapshot with that is how a recovery feature loses the document it
178
+ // exists to protect, so empty never replaces non-empty.
179
+ if (typeof html === "string" && (html || !this._data)) this._data = html;
180
+ } catch (e) {}
181
+ };
182
+
183
+ proto.getDiagnostics = function () {
184
+ return {
185
+ state: this.state,
186
+ crashes: this.crashes.slice(),
187
+ ignoredErrors: this._ignoredErrors.slice(),
188
+ dataLength: this._data.length
189
+ };
190
+ };
191
+
192
+ // ------------------------------------------------------------------ crash
193
+
194
+ // Is the editing surface still usable? An error is only half the evidence:
195
+ // plenty of exceptions are thrown by a feature and leave editing perfectly
196
+ // fine, and restarting for those costs the author their selection and undo
197
+ // history for nothing.
198
+ proto.isEditorResponsive = function () {
199
+ var editor = this.editor;
200
+ if (!editor) return false;
201
+ try {
202
+ var editable = editor.getEditable();
203
+ if (!editable || !editable.ownerDocument) return false;
204
+ // The iframe was removed, or its document was replaced under us.
205
+ if (!editable.ownerDocument.defaultView) return false;
206
+ if (!editable.ownerDocument.body || !editable.ownerDocument.body.contains(editable)) {
207
+ if (editable !== editable.ownerDocument.body) return false;
208
+ }
209
+ // Still attached to the page the host element lives in?
210
+ var frame = editable.ownerDocument.defaultView.frameElement;
211
+ if (frame && !frame.ownerDocument.documentElement.contains(frame)) return false;
212
+ if (typeof editor.getHTMLCode !== "function") return false;
213
+ editor.getHTMLCode();
214
+ return true;
215
+ }
216
+ catch (e) { return false; }
217
+ };
218
+
219
+ proto._isOwnError = function (error, message) {
220
+ var stack = (error && error.stack) || "";
221
+ var where = stack + " " + (message || "") + " " + ((error && error.fileName) || "");
222
+ return this._ownScriptPattern.test(where);
223
+ };
224
+
225
+ proto._handleError = function (error, message, event) {
226
+ if (this._destroyed || this.state === STATE_PERMANENT) return;
227
+
228
+ var mine = this._isOwnError(error, message);
229
+ // An error from elsewhere still gets a health check — a crash can surface
230
+ // as a foreign stack (a framework's error boundary, a rethrow) — but it
231
+ // only counts if the editing surface is genuinely broken.
232
+ var responsive = this.isEditorResponsive();
233
+ if (!mine && responsive) {
234
+ this._ignoredErrors.push({ message: String(message || (error && error.message) || ""), at: Date.now() });
235
+ if (this._ignoredErrors.length > 50) this._ignoredErrors.shift();
236
+ return;
237
+ }
238
+ if (mine && responsive) {
239
+ // Ours, but the editor still works: record it, do not restart.
240
+ this._ignoredErrors.push({
241
+ message: String(message || (error && error.message) || ""),
242
+ at: Date.now(), own: true, restarted: false
243
+ });
244
+ if (this._ignoredErrors.length > 50) this._ignoredErrors.shift();
245
+ this._emit("error", { error: error, message: message, restarting: false, event: event });
246
+ return;
247
+ }
248
+
249
+ this._crash(error, message, event);
250
+ };
251
+
252
+ proto._crash = function (error, message, event) {
253
+ var now = Date.now();
254
+ var last = this.crashes.length ? this.crashes[this.crashes.length - 1].at : 0;
255
+ // A crash after a long quiet stretch is a new incident, not a loop.
256
+ if (last && (now - last) > this._minimumNonErrorTimePeriod) this.crashes = [];
257
+
258
+ this.crashes.push({ at: now, message: String(message || (error && error.message) || "") });
259
+ this.state = STATE_CRASHED;
260
+ this._emit("error", { error: error, message: message, restarting: true, event: event });
261
+
262
+ if (this.crashes.length > this._crashNumberLimit) {
263
+ this.state = STATE_PERMANENT;
264
+ if (this._timer) { global.clearInterval(this._timer); this._timer = null; }
265
+ this._emit("crashedPermanently", { data: this._data, crashes: this.crashes.slice() });
266
+ return;
267
+ }
268
+
269
+ this.restart();
270
+ };
271
+
272
+ proto.restart = function () {
273
+ if (this._destroyed) return null;
274
+ var data = this._data;
275
+
276
+ if (this._timer) { global.clearInterval(this._timer); this._timer = null; }
277
+ try { this._destructor(this.editor); } catch (e) {}
278
+ this.editor = null;
279
+
280
+ // The old instance leaves its chrome in the host element; a fresh editor
281
+ // built on top of that would stack two toolbars.
282
+ try { if (this._host) this._host.innerHTML = ""; } catch (e) {}
283
+
284
+ try {
285
+ this._start();
286
+ }
287
+ catch (e) {
288
+ this.state = STATE_PERMANENT;
289
+ this._emit("crashedPermanently", { data: data, crashes: this.crashes.slice(), error: e });
290
+ return null;
291
+ }
292
+
293
+ if (data) {
294
+ try { this.editor.setHTMLCode(data); this._data = data; } catch (e) {}
295
+ }
296
+ this._emit("restart", { editor: this.editor, crashes: this.crashes.length, data: data });
297
+ return this.editor;
298
+ };
299
+
300
+ global.RichTextEditorWatchdog = RichTextEditorWatchdog;
301
+ if (global.RichTextEditor) global.RichTextEditor.Watchdog = RichTextEditorWatchdog;
302
+ if (typeof module !== "undefined" && module.exports) module.exports = RichTextEditorWatchdog;
303
+ })(typeof window !== "undefined" ? window : this);
@@ -0,0 +1,351 @@
1
+ /*
2
+ * RichTextEditor — Web Component (rte-webcomponent.js)
3
+ * ------------------------------------------------------------------
4
+ * 2026-08-30. Optional, un-obfuscated companion to rte.js: registers
5
+ * <rich-text-editor> as a custom element.
6
+ *
7
+ * Why. We ship first-class React, Vue, Angular and Svelte bindings and a
8
+ * headless core — and nothing at all for the pages that use none of those.
9
+ * TinyMCE ships <tinymce-editor> for exactly that audience: Rails and Laravel
10
+ * templates, Blazor, Django, plain HTML, htmx, and any framework whose bindings
11
+ * we will never write. One custom element covers all of them, because every
12
+ * framework knows how to render an unknown tag with attributes.
13
+ *
14
+ * <script src="richtexteditor/rte-config.js"></script>
15
+ * <script src="richtexteditor/rte.js"></script>
16
+ * <script src="richtexteditor/rte-webcomponent.js"></script>
17
+ *
18
+ * <form method="post">
19
+ * <rich-text-editor name="body" value="<p>Hello</p>"></rich-text-editor>
20
+ * <button>Save</button>
21
+ * </form>
22
+ *
23
+ * That form posts `body=<p>Hello</p>` with no glue code — the element is
24
+ * form-associated, so the browser submits it like a <textarea>. `required`,
25
+ * form reset and `form.elements.body` work for the same reason.
26
+ *
27
+ * Attributes: value, name, toolbar, height, width, readonly, disabled,
28
+ * required, shadow, config (JSON)
29
+ * Properties: .value, .editor, .ready (a Promise), .form, .name
30
+ * Events: input, change (both bubble and cross a shadow boundary)
31
+ * Methods: focus(), checkValidity(), reportValidity()
32
+ *
33
+ * Design notes:
34
+ * - LIGHT DOM by default. A shadow root would encapsulate the chrome away from
35
+ * the page's own theme overrides, which integrators legitimately rely on,
36
+ * and it is the more surprising default. `shadow` opts in, and then
37
+ * plugins/shadowdom.js copies the editor's stylesheets across the boundary —
38
+ * without that plugin a shadowed instance renders a 43,000-pixel toolbar,
39
+ * measured, so this element refuses to enable `shadow` silently if the
40
+ * plugin is absent: it warns and stays in the light DOM rather than
41
+ * rendering something visibly broken.
42
+ * - `value` is a PROPERTY first. Reading the attribute back after the user
43
+ * types is the classic custom-element bug — the attribute holds the initial
44
+ * value only, exactly like <input value>, and the property holds the truth.
45
+ * - The editor is created in connectedCallback and destroyed in
46
+ * disconnectedCallback, because SPA routers move elements in and out of the
47
+ * DOM constantly and a leaked iframe per navigation is a real memory bug.
48
+ * Moving an element (rather than removing it) re-creates it with the value
49
+ * preserved.
50
+ */
51
+ (function (global) {
52
+ "use strict";
53
+
54
+ if (!global.customElements || !global.HTMLElement) return;
55
+ if (global.customElements.get("rich-text-editor")) return;
56
+
57
+ var OBSERVED = ["value", "readonly", "disabled", "height", "width", "toolbar", "name"];
58
+
59
+ function RichTextEditorElement() {
60
+ var self = Reflect.construct(HTMLElement, [], RichTextEditorElement);
61
+ self._editor = null;
62
+ self._value = "";
63
+ self._host = null;
64
+ self._ready = null;
65
+ self._resolveReady = null;
66
+ self._isReady = false;
67
+ self._everConnected = false;
68
+ self._internals = null;
69
+ self._fallbackInput = null;
70
+ try { if (self.attachInternals) self._internals = self.attachInternals(); } catch (e) {}
71
+ return self;
72
+ }
73
+
74
+ RichTextEditorElement.prototype = Object.create(HTMLElement.prototype);
75
+ RichTextEditorElement.prototype.constructor = RichTextEditorElement;
76
+ Object.setPrototypeOf(RichTextEditorElement, HTMLElement);
77
+
78
+ Object.defineProperty(RichTextEditorElement, "observedAttributes", {
79
+ get: function () { return OBSERVED; }
80
+ });
81
+ // Makes the browser submit, reset and validate this element like a textarea.
82
+ Object.defineProperty(RichTextEditorElement, "formAssociated", { get: function () { return true; } });
83
+
84
+ var proto = RichTextEditorElement.prototype;
85
+
86
+ // ------------------------------------------------------------- properties
87
+
88
+ Object.defineProperty(proto, "value", {
89
+ get: function () {
90
+ if (this._editor) {
91
+ try { return this._editor.getHTMLCode(); } catch (e) {}
92
+ }
93
+ return this._value;
94
+ },
95
+ set: function (html) {
96
+ this._value = html == null ? "" : String(html);
97
+ if (this._editor) {
98
+ try { this._editor.setHTMLCode(this._value); } catch (e) {}
99
+ }
100
+ this._syncFormValue();
101
+ }
102
+ });
103
+
104
+ Object.defineProperty(proto, "editor", { get: function () { return this._editor; } });
105
+
106
+ // Resolves when the editing surface exists — the only safe moment to call
107
+ // editor APIs from page script.
108
+ //
109
+ // `_isReady` is not redundant with `_ready`: the element becomes ready during
110
+ // connectedCallback, which runs BEFORE any page script can touch `.ready`.
111
+ // Without the flag, the resolve fired into nothing and the promise handed out
112
+ // afterwards never settled — an await that hangs forever. Caught by the first
113
+ // run of the test suite, which simply stopped.
114
+ Object.defineProperty(proto, "ready", {
115
+ get: function () {
116
+ var self = this;
117
+ if (this._isReady) return Promise.resolve(this._editor);
118
+ if (!this._ready) {
119
+ this._ready = new Promise(function (resolve) { self._resolveReady = resolve; });
120
+ }
121
+ return this._ready;
122
+ }
123
+ });
124
+
125
+ Object.defineProperty(proto, "name", {
126
+ get: function () { return this.getAttribute("name") || ""; },
127
+ set: function (v) { this.setAttribute("name", v); }
128
+ });
129
+
130
+ Object.defineProperty(proto, "form", {
131
+ get: function () { return this._internals ? this._internals.form : this.closest("form"); }
132
+ });
133
+
134
+ // ------------------------------------------------------------- lifecycle
135
+
136
+ proto.connectedCallback = function () {
137
+ if (this._editor) return;
138
+ if (!global.RichTextEditor) {
139
+ console.error("<rich-text-editor>: rte.js must be loaded before rte-webcomponent.js.");
140
+ return;
141
+ }
142
+ // Only on the FIRST connect. A re-connect (an SPA router moving the
143
+ // element, a drag between containers) must keep what the user typed —
144
+ // the attribute still holds the ORIGINAL seed, so re-reading it here
145
+ // silently threw the edits away and restored stale text. Found by the
146
+ // unmount/remount check in the test suite.
147
+ if (!this._everConnected && this.hasAttribute("value")) this._value = this.getAttribute("value") || "";
148
+ this._everConnected = true;
149
+
150
+ var mount = this;
151
+ if (this.hasAttribute("shadow")) {
152
+ // Refuse to render something visibly broken: without the shadow-DOM
153
+ // style plugin the toolbar is unstyled and 43,000 px tall.
154
+ if (this._shadowStylingAvailable()) {
155
+ var root = this.shadowRoot || this.attachShadow({ mode: "open" });
156
+ mount = document.createElement("div");
157
+ root.appendChild(mount);
158
+ }
159
+ else {
160
+ console.warn("<rich-text-editor shadow>: plugins/shadowdom.js is not loaded, so the " +
161
+ "editor would render unstyled inside a shadow root. Staying in the light DOM.");
162
+ }
163
+ }
164
+
165
+ this._host = document.createElement("div");
166
+ mount.appendChild(this._host);
167
+
168
+ var config = this._buildConfig();
169
+ this._editor = new global.RichTextEditor(this._host, config);
170
+ this._bind();
171
+ this._syncFormValue();
172
+ this._waitForReady();
173
+ };
174
+
175
+ proto.disconnectedCallback = function () {
176
+ // An SPA router unmounts and remounts constantly; a leaked iframe per
177
+ // navigation is a real memory bug, not a theoretical one.
178
+ this._value = this.value;
179
+ if (this._editor) {
180
+ try { if (typeof this._editor.destroy === "function") this._editor.destroy(); } catch (e) {}
181
+ this._editor = null;
182
+ }
183
+ if (this._host && this._host.parentNode) this._host.parentNode.removeChild(this._host);
184
+ this._host = null;
185
+ this._ready = null;
186
+ this._resolveReady = null;
187
+ this._isReady = false;
188
+ };
189
+
190
+ proto.attributeChangedCallback = function (name, oldValue, newValue) {
191
+ if (oldValue === newValue) return;
192
+ if (name === "value") {
193
+ // Like <input value>, the attribute seeds the initial value; once the
194
+ // user has typed, the property is the truth and setting the attribute
195
+ // again is an explicit reset by the page.
196
+ this.value = newValue || "";
197
+ }
198
+ else if (name === "readonly" || name === "disabled") {
199
+ this._applyReadOnly();
200
+ }
201
+ else if (name === "name") {
202
+ this._syncFormValue();
203
+ }
204
+ else if (this._editor) {
205
+ // height / width / toolbar need a rebuild; they are construction-time
206
+ // config, and pretending otherwise would silently do nothing.
207
+ var value = this.value;
208
+ this.disconnectedCallback();
209
+ this._value = value;
210
+ this.connectedCallback();
211
+ }
212
+ };
213
+
214
+ // Called by the browser when the containing form is reset.
215
+ proto.formResetCallback = function () {
216
+ this.value = this.getAttribute("value") || "";
217
+ };
218
+
219
+ proto.formDisabledCallback = function () { this._applyReadOnly(); };
220
+
221
+ // -------------------------------------------------------------- behaviour
222
+
223
+ proto.focus = function () {
224
+ try {
225
+ if (this._editor && this._editor.getEditable) {
226
+ var editable = this._editor.getEditable();
227
+ if (editable && editable.ownerDocument && editable.ownerDocument.defaultView) {
228
+ editable.ownerDocument.defaultView.focus();
229
+ editable.focus();
230
+ return;
231
+ }
232
+ }
233
+ }
234
+ catch (e) {}
235
+ HTMLElement.prototype.focus.call(this);
236
+ };
237
+
238
+ proto.checkValidity = function () { return this._internals ? this._internals.checkValidity() : true; };
239
+ proto.reportValidity = function () { return this._internals ? this._internals.reportValidity() : true; };
240
+
241
+ proto._buildConfig = function () {
242
+ var config = global.RTE_CreateConfig ? global.RTE_CreateConfig() : {};
243
+ var raw = this.getAttribute("config");
244
+ if (raw) {
245
+ try {
246
+ var extra = JSON.parse(raw);
247
+ for (var k in extra) if (Object.prototype.hasOwnProperty.call(extra, k)) config[k] = extra[k];
248
+ }
249
+ catch (e) {
250
+ console.error("<rich-text-editor>: the config attribute is not valid JSON.", e);
251
+ }
252
+ }
253
+ if (this.hasAttribute("toolbar")) config.toolbar = this.getAttribute("toolbar");
254
+ if (this.hasAttribute("height")) config.height = this.getAttribute("height");
255
+ if (this.hasAttribute("width")) config.width = this.getAttribute("width");
256
+ if (this._isReadOnly()) config.readOnly = true;
257
+ if (this._value) config.value = this._value;
258
+ return config;
259
+ };
260
+
261
+ proto._isReadOnly = function () {
262
+ return this.hasAttribute("readonly") || this.hasAttribute("disabled")
263
+ || !!(this._internals && this._internals.form && this._internals.form.disabled);
264
+ };
265
+
266
+ proto._applyReadOnly = function () {
267
+ if (!this._editor) return;
268
+ var readOnly = this._isReadOnly();
269
+ try {
270
+ if (typeof this._editor.setReadOnly === "function") this._editor.setReadOnly(readOnly);
271
+ else this._editor.readOnly = readOnly;
272
+ }
273
+ catch (e) {}
274
+ };
275
+
276
+ proto._shadowStylingAvailable = function () {
277
+ return !!(global.RTE_DefaultConfig && global.RTE_DefaultConfig.plugin_shadowdom);
278
+ };
279
+
280
+ proto._bind = function () {
281
+ var self = this;
282
+ var emit = function (type) {
283
+ self._syncFormValue();
284
+ // composed:true so a listener outside a shadow boundary still hears it.
285
+ self.dispatchEvent(new Event(type, { bubbles: true, composed: true }));
286
+ };
287
+ try {
288
+ this._editor.attachEvent("change", function () { emit("input"); });
289
+ }
290
+ catch (e) {}
291
+ try {
292
+ this._editor.attachEvent("blur", function () { emit("change"); });
293
+ }
294
+ catch (e) {}
295
+ };
296
+
297
+ proto._waitForReady = function () {
298
+ var self = this;
299
+ var tries = 0;
300
+ var timer = setInterval(function () {
301
+ var alive = false;
302
+ try { alive = !!(self._editor && self._editor.getEditable()); } catch (e) {}
303
+ if (alive || ++tries > 200) {
304
+ clearInterval(timer);
305
+ if (self._value) { try { self._editor.setHTMLCode(self._value); } catch (e) {} }
306
+ self._applyReadOnly();
307
+ self._syncFormValue();
308
+ self._isReady = true;
309
+ if (self._resolveReady) self._resolveReady(self._editor);
310
+ }
311
+ }, 25);
312
+ };
313
+
314
+ // Form participation. ElementInternals is the modern path; the hidden input
315
+ // keeps older browsers submitting the right thing instead of nothing at all.
316
+ proto._syncFormValue = function () {
317
+ var name = this.getAttribute("name");
318
+ var value = this.value;
319
+ if (this._internals && this._internals.setFormValue) {
320
+ try { this._internals.setFormValue(name ? value : null); } catch (e) {}
321
+ this._syncValidity(value);
322
+ return;
323
+ }
324
+ if (!name) return;
325
+ if (!this._fallbackInput) {
326
+ this._fallbackInput = document.createElement("input");
327
+ this._fallbackInput.type = "hidden";
328
+ this.appendChild(this._fallbackInput);
329
+ }
330
+ this._fallbackInput.name = name;
331
+ this._fallbackInput.value = value;
332
+ };
333
+
334
+ proto._syncValidity = function (value) {
335
+ if (!this._internals || !this._internals.setValidity) return;
336
+ var required = this.hasAttribute("required");
337
+ // "Empty" for a rich-text field means no text and no media — an empty
338
+ // <p> is what the editor puts in a blank document, and treating that as
339
+ // filled makes `required` meaningless.
340
+ var empty = !String(value || "").replace(/<[^>]*>/g, "").replace(/&nbsp;|\s/g, "")
341
+ && !/<(img|table|iframe|video|audio|hr)\b/i.test(value || "");
342
+ try {
343
+ if (required && empty) this._internals.setValidity({ valueMissing: true }, "Please fill out this field.", this._host || this);
344
+ else this._internals.setValidity({});
345
+ }
346
+ catch (e) {}
347
+ };
348
+
349
+ global.customElements.define("rich-text-editor", RichTextEditorElement);
350
+ global.RichTextEditorElement = RichTextEditorElement;
351
+ })(typeof window !== "undefined" ? window : this);
@@ -0,0 +1 @@
1
+ @media(max-width:768px),(pointer:coarse){.richtexteditor.rte-mobile rte-toolbar.rte-toolbar-mobile{display:flex!important;align-items:center;flex-wrap:nowrap!important;gap:2px;overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;white-space:nowrap;-webkit-overflow-scrolling:touch;touch-action:pan-x;scrollbar-width:none;position:sticky;top:0;z-index:5;padding:2px 4px;background:#fff;background-clip:padding-box}.richtexteditor.rte-mobile rte-toolbar.rte-toolbar-mobile::-webkit-scrollbar{display:none}.richtexteditor.rte-mobile rte-toolbar.rte-toolbar-mobile rte-toolbar-group{display:inline-flex;align-items:center;flex:0 0 auto;min-width:0}.richtexteditor.rte-mobile rte-toolbar-button,.richtexteditor.rte-mobile rte-toolbar-splitbutton,.richtexteditor.rte-mobile rte-toolbar-dropdown,.richtexteditor.rte-mobile rte-toolbar-arrowbutton{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-width:44px;min-height:44px;width:auto;height:auto;padding:10px;border-radius:8px;color:var(--rte-icon-color, #5f6368);background-clip:padding-box;transition:background-color .12s ease,border-color .12s ease,color .12s ease,box-shadow .12s ease}.richtexteditor.rte-mobile rte-toolbar-splitbutton-direct,.richtexteditor.rte-mobile rte-toolbar-splitbutton-dropdown{display:inline-flex;align-items:center;align-self:stretch;justify-content:center;min-height:24px}.richtexteditor.rte-mobile rte-toolbar-button svg,.richtexteditor.rte-mobile rte-toolbar-splitbutton svg,.richtexteditor.rte-mobile rte-toolbar-dropdown svg,.richtexteditor.rte-mobile rte-toolbar-arrowbutton svg{display:block!important;flex:0 0 auto;width:20px!important;height:20px!important;margin:0!important;align-self:center!important;color:currentColor;pointer-events:none}.richtexteditor.rte-mobile rte-toolbar-button:active,.richtexteditor.rte-mobile rte-toolbar-splitbutton:active,.richtexteditor.rte-mobile rte-toolbar-dropdown:active,.richtexteditor.rte-mobile rte-toolbar-arrowbutton:active{color:#1559d6;background-color:#f3f8ff;border-color:#2563eb24}.richtexteditor.rte-mobile rte-toolbar-button.rte-command-active,.richtexteditor.rte-mobile rte-toolbar-splitbutton.rte-command-active,.richtexteditor.rte-mobile rte-toolbar-dropdown.rte-command-active,.richtexteditor.rte-mobile rte-toolbar-arrowbutton.rte-command-active{color:#1559d6;fill:currentColor;background-color:#edf6ff;border-color:#2563eb38;box-shadow:inset 0 0 0 1px #2563eb1f}.richtexteditor.rte-mobile rte-toolbar-button:focus-visible,.richtexteditor.rte-mobile rte-toolbar-splitbutton:focus-visible,.richtexteditor.rte-mobile rte-toolbar-dropdown:focus-visible,.richtexteditor.rte-mobile rte-toolbar-arrowbutton:focus-visible{outline:none;border-color:#2563eb;box-shadow:0 0 0 2px #2563eb2e;background-color:#eef4ff}.richtexteditor.rte-mobile rte-dropdown-panel,.richtexteditor.rte-mobile rte-popup{position:fixed!important;inset:auto 0 0!important;box-sizing:border-box;width:100vw;max-height:min(60vh,calc(100vh - 24px));border-top-left-radius:8px;border-top-right-radius:8px;border:1px solid #e2e8f0;border-bottom:none;background:#fff;background-clip:padding-box;box-shadow:0 -12px 32px #0f172a1f;padding:10px 8px 22px;overflow-y:auto;overscroll-behavior:contain;z-index:2147483647}.richtexteditor.rte-mobile rte-floattoolbar,.richtexteditor.rte-mobile rte-floatparagraph{display:none!important}.richtexteditor.rte-mobile~.aitoolkit-dialog,body.rte-mobile .aitoolkit-dialog{left:8px!important;right:8px!important;width:auto!important;max-width:none!important}.richtexteditor.rte-mobile~.aitoolkit-review-drawer,body.rte-mobile .aitoolkit-review-drawer,.richtexteditor.rte-mobile~.comments-sidebar,body.rte-mobile .comments-sidebar{inset:auto 0 0!important;box-sizing:border-box;width:auto!important;max-height:min(55vh,calc(100vh - 24px));border-top-left-radius:8px;border-top-right-radius:8px;border-top:1px solid #e2e8f0;background-clip:padding-box;overflow:hidden}.richtexteditor.rte-mobile iframe,.richtexteditor.rte-mobile .rte-editable{font-size:16px}.rte-dark.richtexteditor.rte-mobile rte-toolbar.rte-toolbar-mobile{background:var(--rte-surface-raised, #1e293b)}.rte-dark.richtexteditor.rte-mobile rte-toolbar-button,.rte-dark.richtexteditor.rte-mobile rte-toolbar-splitbutton,.rte-dark.richtexteditor.rte-mobile rte-toolbar-dropdown,.rte-dark.richtexteditor.rte-mobile rte-toolbar-arrowbutton{color:var(--rte-icon-color, #cbd5e1)}.rte-dark.richtexteditor.rte-mobile rte-toolbar-button.rte-command-active,.rte-dark.richtexteditor.rte-mobile rte-toolbar-splitbutton.rte-command-active,.rte-dark.richtexteditor.rte-mobile rte-toolbar-dropdown.rte-command-active,.rte-dark.richtexteditor.rte-mobile rte-toolbar-arrowbutton.rte-command-active{color:#bfdbfe;background-color:#1e3a5f;border-color:#60a5fa75;box-shadow:inset 0 0 0 1px #60a5fa3d}@media(prefers-color-scheme:dark){.richtexteditor.rte-mobile:not(.rte-light) rte-toolbar.rte-toolbar-mobile{background:var(--rte-surface-raised, #1e293b)}.richtexteditor.rte-mobile:not(.rte-light) rte-toolbar-button,.richtexteditor.rte-mobile:not(.rte-light) rte-toolbar-splitbutton,.richtexteditor.rte-mobile:not(.rte-light) rte-toolbar-dropdown,.richtexteditor.rte-mobile:not(.rte-light) rte-toolbar-arrowbutton{color:var(--rte-icon-color, #cbd5e1)}.richtexteditor.rte-mobile:not(.rte-light) rte-toolbar-button.rte-command-active,.richtexteditor.rte-mobile:not(.rte-light) rte-toolbar-splitbutton.rte-command-active,.richtexteditor.rte-mobile:not(.rte-light) rte-toolbar-dropdown.rte-command-active,.richtexteditor.rte-mobile:not(.rte-light) rte-toolbar-arrowbutton.rte-command-active{color:#bfdbfe;background-color:#1e3a5f;border-color:#60a5fa75;box-shadow:inset 0 0 0 1px #60a5fa3d}}}.rte-force-mobile .richtexteditor rte-toolbar.rte-toolbar-mobile{display:flex!important}.rte-force-mobile .richtexteditor rte-toolbar.rte-toolbar-desktop{display:none!important}
@@ -1099,7 +1099,7 @@ rte-control-selected-corner {
1099
1099
 
1100
1100
 
1101
1101
  rte-dialog-float {
1102
- position: absolute;
1102
+ position: fixed;
1103
1103
  display: flex;
1104
1104
  align-items: center;
1105
1105
  justify-content: center;