@richscripts/richtexteditor 2.6.0 → 2.6.1
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/package.json +142 -142
- package/richtexteditor/all_plugins.js +1279 -1263
- package/richtexteditor/all_plugins.min.js +35 -35
- package/richtexteditor/plugins/all_plugins.js +1279 -1263
- package/richtexteditor/plugins/all_plugins.min.js +35 -35
- package/richtexteditor/plugins/documentimport.js +1279 -1263
- package/richtexteditor/rte-headless.js +360 -0
- package/richtexteditor/rte.js +2 -2
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* RichTextEditor — Headless API (rte-headless.js)
|
|
3
|
+
* ------------------------------------------------------------------
|
|
4
|
+
* Optional, un-obfuscated companion to rte.js. It lets you run the editor with
|
|
5
|
+
* NO built-in toolbar/chrome and drive everything from your own UI — the same
|
|
6
|
+
* "bring your own interface" model as headless frameworks, but on top of the
|
|
7
|
+
* full RichTextEditor engine (commands, plugins, collaboration, AI, etc.).
|
|
8
|
+
*
|
|
9
|
+
* What you get:
|
|
10
|
+
* - a chrome-less editing surface (toolbar / sub-toolbar / status bar hidden)
|
|
11
|
+
* - run(command, value) -> dispatch any RTE command
|
|
12
|
+
* - isActive(name) / state() -> query active formatting for your buttons
|
|
13
|
+
* - can(command) -> queryCommandEnabled (undo/redo/...)
|
|
14
|
+
* - on("update" | "change" | "selectionchange" | "focus" | "blur", cb)
|
|
15
|
+
* - getHTML / setHTML / getText / setText / getMarkdown / setMarkdown / getJSON
|
|
16
|
+
*
|
|
17
|
+
* Example:
|
|
18
|
+
* var hl = RTEHeadless("#editor", { value: "<p>Hello</p>" });
|
|
19
|
+
* hl.on("update", function (s) {
|
|
20
|
+
* boldBtn.classList.toggle("is-active", s.bold);
|
|
21
|
+
* });
|
|
22
|
+
* boldBtn.addEventListener("mousedown", function (e) {
|
|
23
|
+
* e.preventDefault(); // keep the editor selection
|
|
24
|
+
* hl.run("bold");
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* Loads after rte-config.js + rte.js (+ optional plugins). Ships un-obfuscated
|
|
28
|
+
* on purpose, like rte-config.js — you are meant to read and extend it.
|
|
29
|
+
*/
|
|
30
|
+
(function (global) {
|
|
31
|
+
"use strict";
|
|
32
|
+
|
|
33
|
+
// logical state name -> how to query it on the editor document.
|
|
34
|
+
// "state" uses queryCommandState; "block" matches queryCommandValue("formatBlock").
|
|
35
|
+
var STATE_MAP = {
|
|
36
|
+
bold: { kind: "state", q: "bold" },
|
|
37
|
+
italic: { kind: "state", q: "italic" },
|
|
38
|
+
underline: { kind: "state", q: "underline" },
|
|
39
|
+
strike: { kind: "state", q: "strikethrough" },
|
|
40
|
+
strikethrough: { kind: "state", q: "strikethrough" },
|
|
41
|
+
subscript: { kind: "state", q: "subscript" },
|
|
42
|
+
superscript: { kind: "state", q: "superscript" },
|
|
43
|
+
ul: { kind: "state", q: "insertunorderedlist" },
|
|
44
|
+
unorderedlist: { kind: "state", q: "insertunorderedlist" },
|
|
45
|
+
ol: { kind: "state", q: "insertorderedlist" },
|
|
46
|
+
orderedlist: { kind: "state", q: "insertorderedlist" },
|
|
47
|
+
justifyleft: { kind: "state", q: "justifyleft" },
|
|
48
|
+
justifycenter: { kind: "state", q: "justifycenter" },
|
|
49
|
+
justifyright: { kind: "state", q: "justifyright" },
|
|
50
|
+
justifyfull: { kind: "state", q: "justifyfull" },
|
|
51
|
+
h1: { kind: "block", q: "h1" },
|
|
52
|
+
h2: { kind: "block", q: "h2" },
|
|
53
|
+
h3: { kind: "block", q: "h3" },
|
|
54
|
+
h4: { kind: "block", q: "h4" },
|
|
55
|
+
h5: { kind: "block", q: "h5" },
|
|
56
|
+
h6: { kind: "block", q: "h6" },
|
|
57
|
+
p: { kind: "block", q: "p" },
|
|
58
|
+
paragraph: { kind: "block", q: "p" },
|
|
59
|
+
blockquote: { kind: "block", q: "blockquote" },
|
|
60
|
+
pre: { kind: "block", q: "pre" }
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// keys included in the snapshot returned by state() when no list is given.
|
|
64
|
+
var DEFAULT_STATE_KEYS = [
|
|
65
|
+
"bold", "italic", "underline", "strike", "ul", "ol",
|
|
66
|
+
"justifyleft", "justifycenter", "justifyright",
|
|
67
|
+
"blockquote", "h1", "h2", "h3", "p"
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
function resolveTarget(target) {
|
|
71
|
+
if (typeof target === "string") {
|
|
72
|
+
return (typeof document !== "undefined") ? document.querySelector(target) : null;
|
|
73
|
+
}
|
|
74
|
+
return target || null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function HeadlessController(editor, options) {
|
|
78
|
+
this.editor = editor; // escape hatch: the underlying RichTextEditor
|
|
79
|
+
this._opts = options || {};
|
|
80
|
+
this._handlers = {}; // event name -> [cb]
|
|
81
|
+
this._scheduled = false;
|
|
82
|
+
this._destroyed = false;
|
|
83
|
+
this._wire();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// The contentEditable document we query formatting state against.
|
|
87
|
+
HeadlessController.prototype._doc = function () {
|
|
88
|
+
try {
|
|
89
|
+
var ifr = this.editor && this.editor.iframe;
|
|
90
|
+
if (!ifr) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return ifr.contentDocument || (ifr.contentWindow && ifr.contentWindow.document) || null;
|
|
94
|
+
} catch (e) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
HeadlessController.prototype._wire = function () {
|
|
100
|
+
var self = this;
|
|
101
|
+
// Coalesce bursts into a single "update". setTimeout (not rAF) on purpose:
|
|
102
|
+
// requestAnimationFrame is paused in background/hidden tabs, which would
|
|
103
|
+
// latch _scheduled and silently drop every later update (collab, tests,
|
|
104
|
+
// off-screen editors). A ~16ms timer fires in every context.
|
|
105
|
+
function emitUpdate() {
|
|
106
|
+
if (self._destroyed || self._scheduled) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
self._scheduled = true;
|
|
110
|
+
setTimeout(function () {
|
|
111
|
+
self._scheduled = false;
|
|
112
|
+
if (!self._destroyed) {
|
|
113
|
+
self._emit("update", self.state());
|
|
114
|
+
}
|
|
115
|
+
}, 16);
|
|
116
|
+
}
|
|
117
|
+
this._emitUpdate = emitUpdate;
|
|
118
|
+
|
|
119
|
+
["selectionchange", "change", "focus", "blur"].forEach(function (evt) {
|
|
120
|
+
try {
|
|
121
|
+
self.editor.attachEvent(evt, function (a, b) {
|
|
122
|
+
self._emit(evt, a, b);
|
|
123
|
+
if (evt === "selectionchange" || evt === "change") {
|
|
124
|
+
emitUpdate();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
} catch (e) { /* event surface differs by build; ignore */ }
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Robust fallback: bind native listeners on the editor document so the
|
|
131
|
+
// "update" (active-state) snapshot stays in sync with EVERY caret/selection
|
|
132
|
+
// move and keystroke, even if a given build doesn't route them through the
|
|
133
|
+
// editor's own event hook. Coalesced by emitUpdate's per-frame guard.
|
|
134
|
+
var doc = this._doc();
|
|
135
|
+
if (doc && doc.addEventListener) {
|
|
136
|
+
// Only drive the coalesced "update" snapshot here; the editor's own
|
|
137
|
+
// hook owns the "change" event, so we don't double-emit it.
|
|
138
|
+
this._docListeners = [
|
|
139
|
+
["selectionchange", function () { emitUpdate(); }],
|
|
140
|
+
["mouseup", function () { emitUpdate(); }],
|
|
141
|
+
["keyup", function () { emitUpdate(); }],
|
|
142
|
+
["input", function () { emitUpdate(); }]
|
|
143
|
+
];
|
|
144
|
+
for (var i = 0; i < this._docListeners.length; i++) {
|
|
145
|
+
try { doc.addEventListener(this._docListeners[i][0], this._docListeners[i][1], true); } catch (e) {}
|
|
146
|
+
}
|
|
147
|
+
this._boundDoc = doc;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
HeadlessController.prototype.on = function (name, cb) {
|
|
152
|
+
if (typeof cb === "function") {
|
|
153
|
+
(this._handlers[name] = this._handlers[name] || []).push(cb);
|
|
154
|
+
}
|
|
155
|
+
return this;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
HeadlessController.prototype.off = function (name, cb) {
|
|
159
|
+
var list = this._handlers[name];
|
|
160
|
+
if (list) {
|
|
161
|
+
this._handlers[name] = cb ? list.filter(function (f) { return f !== cb; }) : [];
|
|
162
|
+
}
|
|
163
|
+
return this;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
HeadlessController.prototype._emit = function (name) {
|
|
167
|
+
var list = this._handlers[name];
|
|
168
|
+
if (!list || !list.length) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
var args = Array.prototype.slice.call(arguments, 1);
|
|
172
|
+
for (var i = 0; i < list.length; i++) {
|
|
173
|
+
try {
|
|
174
|
+
list[i].apply(this, args);
|
|
175
|
+
} catch (e) {
|
|
176
|
+
if (global.console) { global.console.error(e); }
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Dispatch any RichTextEditor command (same names the toolbar uses), chainable.
|
|
182
|
+
HeadlessController.prototype.run = function (command, value) {
|
|
183
|
+
try {
|
|
184
|
+
this.editor.execCommand(command, value);
|
|
185
|
+
} catch (e) {
|
|
186
|
+
if (global.console) { global.console.error(e); }
|
|
187
|
+
}
|
|
188
|
+
if (this._emitUpdate) { this._emitUpdate(); }
|
|
189
|
+
return this;
|
|
190
|
+
};
|
|
191
|
+
HeadlessController.prototype.cmd = HeadlessController.prototype.run;
|
|
192
|
+
|
|
193
|
+
HeadlessController.prototype.isActive = function (name) {
|
|
194
|
+
var spec = STATE_MAP[String(name || "").toLowerCase()];
|
|
195
|
+
var doc = this._doc();
|
|
196
|
+
if (!spec || !doc) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
if (spec.kind === "state") {
|
|
201
|
+
return !!doc.queryCommandState(spec.q);
|
|
202
|
+
}
|
|
203
|
+
var v = String(doc.queryCommandValue("formatBlock") || "").toLowerCase().replace(/[<>]/g, "");
|
|
204
|
+
return v === spec.q;
|
|
205
|
+
} catch (e) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
HeadlessController.prototype.can = function (command) {
|
|
211
|
+
var doc = this._doc();
|
|
212
|
+
if (!doc) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
return !!doc.queryCommandEnabled(command);
|
|
217
|
+
} catch (e) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// Snapshot of active formatting for binding a custom toolbar.
|
|
223
|
+
HeadlessController.prototype.state = function (keys) {
|
|
224
|
+
keys = keys || DEFAULT_STATE_KEYS;
|
|
225
|
+
var out = {};
|
|
226
|
+
for (var i = 0; i < keys.length; i++) {
|
|
227
|
+
out[keys[i]] = this.isActive(keys[i]);
|
|
228
|
+
}
|
|
229
|
+
out.canUndo = this.can("undo");
|
|
230
|
+
out.canRedo = this.can("redo");
|
|
231
|
+
return out;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// ---- content I/O (thin pass-throughs to the public editor API) ----
|
|
235
|
+
HeadlessController.prototype.getHTML = function () {
|
|
236
|
+
return this.editor.getHTMLCode ? this.editor.getHTMLCode() : "";
|
|
237
|
+
};
|
|
238
|
+
HeadlessController.prototype.setHTML = function (html) {
|
|
239
|
+
if (this.editor.setHTMLCode) { this.editor.setHTMLCode(html == null ? "" : html); }
|
|
240
|
+
if (this._emitUpdate) { this._emitUpdate(); }
|
|
241
|
+
return this;
|
|
242
|
+
};
|
|
243
|
+
HeadlessController.prototype.getText = function () {
|
|
244
|
+
return this.editor.getText ? this.editor.getText() : "";
|
|
245
|
+
};
|
|
246
|
+
HeadlessController.prototype.setText = function (text) {
|
|
247
|
+
if (this.editor.setText) { this.editor.setText(text == null ? "" : text); }
|
|
248
|
+
if (this._emitUpdate) { this._emitUpdate(); }
|
|
249
|
+
return this;
|
|
250
|
+
};
|
|
251
|
+
HeadlessController.prototype.getMarkdown = function () {
|
|
252
|
+
return this.editor.toMarkdown ? this.editor.toMarkdown() : "";
|
|
253
|
+
};
|
|
254
|
+
HeadlessController.prototype.setMarkdown = function (markdown) {
|
|
255
|
+
if (this.editor.fromMarkdown) { this.editor.fromMarkdown(markdown == null ? "" : markdown); }
|
|
256
|
+
if (this._emitUpdate) { this._emitUpdate(); }
|
|
257
|
+
return this;
|
|
258
|
+
};
|
|
259
|
+
HeadlessController.prototype.getJSON = function () {
|
|
260
|
+
return this.editor.getJSON ? this.editor.getJSON() : null; // requires the structured-content bridge
|
|
261
|
+
};
|
|
262
|
+
HeadlessController.prototype.getStatistics = function () {
|
|
263
|
+
return this.editor.getStatistics ? this.editor.getStatistics() : null;
|
|
264
|
+
};
|
|
265
|
+
HeadlessController.prototype.isEmpty = function () {
|
|
266
|
+
return String(this.getText() || "").replace(/\s+/g, "").length === 0;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
HeadlessController.prototype.focus = function () {
|
|
270
|
+
try { this.editor.focus(); } catch (e) { /* ignore */ }
|
|
271
|
+
return this;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
HeadlessController.prototype.destroy = function () {
|
|
275
|
+
this._destroyed = true;
|
|
276
|
+
this._handlers = {};
|
|
277
|
+
if (this._boundDoc && this._docListeners) {
|
|
278
|
+
for (var i = 0; i < this._docListeners.length; i++) {
|
|
279
|
+
try { this._boundDoc.removeEventListener(this._docListeners[i][0], this._docListeners[i][1], true); } catch (e) {}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
this._docListeners = null;
|
|
283
|
+
this._boundDoc = null;
|
|
284
|
+
try { if (this.editor.prepareDestroy) { this.editor.prepareDestroy(); } } catch (e) { /* ignore */ }
|
|
285
|
+
return this;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
var _cssInjected = false;
|
|
289
|
+
function injectHeadlessCss() {
|
|
290
|
+
if (_cssInjected || typeof document === "undefined") {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
_cssInjected = true;
|
|
294
|
+
var css =
|
|
295
|
+
".rte-headless rte-toolbar,.rte-headless rte-subtoolbar,.rte-headless rte-bottom{display:none !important;}" +
|
|
296
|
+
".rte-headless{border:0 !important;background:transparent !important;}";
|
|
297
|
+
var style = document.createElement("style");
|
|
298
|
+
style.setAttribute("data-rte-headless", "");
|
|
299
|
+
style.appendChild(document.createTextNode(css));
|
|
300
|
+
(document.head || document.documentElement).appendChild(style);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Factory: build a chrome-less editor and return a HeadlessController.
|
|
304
|
+
function createHeadless(target, options) {
|
|
305
|
+
options = options || {};
|
|
306
|
+
if (typeof global.RichTextEditor !== "function") {
|
|
307
|
+
throw new Error("rte-headless: RichTextEditor (rte.js) must be loaded before rte-headless.js.");
|
|
308
|
+
}
|
|
309
|
+
var el = resolveTarget(target);
|
|
310
|
+
if (!el) {
|
|
311
|
+
throw new Error("rte-headless: target element not found.");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Start from the standard config so plugins/commands still work, then
|
|
315
|
+
// strip the chrome. A single-space toolbar definition parses to ZERO
|
|
316
|
+
// items (an empty string would fall back to the full toolbar).
|
|
317
|
+
var cfg = (typeof global.RTE_CreateConfig === "function") ? global.RTE_CreateConfig() : {};
|
|
318
|
+
if (options.config) {
|
|
319
|
+
for (var k in options.config) {
|
|
320
|
+
if (Object.prototype.hasOwnProperty.call(options.config, k)) {
|
|
321
|
+
cfg[k] = options.config[k];
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
cfg.toolbar = "headless";
|
|
326
|
+
cfg.toolbar_headless = " ";
|
|
327
|
+
cfg.toolbarMobile = "headless";
|
|
328
|
+
if (options.showStatistics !== true) {
|
|
329
|
+
cfg.showStatistics = false;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
injectHeadlessCss();
|
|
333
|
+
var editor = new global.RichTextEditor(el, cfg);
|
|
334
|
+
if (editor.container && editor.container.classList) {
|
|
335
|
+
editor.container.classList.add("rte-headless");
|
|
336
|
+
}
|
|
337
|
+
if (options.value != null && editor.setHTMLCode) {
|
|
338
|
+
editor.setHTMLCode(options.value);
|
|
339
|
+
}
|
|
340
|
+
return new HeadlessController(editor, options);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ---- exports ----
|
|
344
|
+
if (typeof global !== "undefined") {
|
|
345
|
+
global.RTEHeadless = createHeadless;
|
|
346
|
+
if (typeof global.RichTextEditor === "function") {
|
|
347
|
+
global.RichTextEditor.headless = createHeadless;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
351
|
+
module.exports = {
|
|
352
|
+
RTEHeadless: createHeadless,
|
|
353
|
+
_internals: {
|
|
354
|
+
STATE_MAP: STATE_MAP,
|
|
355
|
+
DEFAULT_STATE_KEYS: DEFAULT_STATE_KEYS,
|
|
356
|
+
HeadlessController: HeadlessController
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
})(typeof window !== "undefined" ? window : this);
|