@richscripts/richtexteditor 2.4.1 → 2.5.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/package.json +4 -2
- package/richtexteditor/all_plugins.js +2861 -935
- package/richtexteditor/all_plugins.min.js +58 -58
- package/richtexteditor/plugins/accessibilitychecker.js +186 -1
- package/richtexteditor/plugins/aitoolkit.js +1 -1
- package/richtexteditor/plugins/all_plugins.js +2861 -935
- package/richtexteditor/plugins/all_plugins.min.js +58 -58
- package/richtexteditor/plugins/crdt-engine.js +2201 -198
- package/richtexteditor/plugins/crdt-engine.min.js +5 -5
- package/richtexteditor/plugins/gapcursor.js +365 -0
- package/richtexteditor/plugins/imageeditor.js +30 -3
- package/richtexteditor/plugins/importstyles.js +312 -0
- package/richtexteditor/plugins/insertcode.js +81 -4
- package/richtexteditor/plugins/insertemoji.js +302 -229
- package/richtexteditor/plugins/insertgallery.js +34 -6
- package/richtexteditor/plugins/keyboarda11y.js +27 -1
- package/richtexteditor/plugins/multiroot.js +269 -0
- package/richtexteditor/plugins/revisionhistory.js +5 -0
- package/richtexteditor/plugins/sanitizer.js +24 -0
- package/richtexteditor/plugins/textpartlanguage.js +504 -0
- package/richtexteditor/plugins/trackedchanges.js +716 -689
- package/richtexteditor/plugins/wordexport.js +1 -1
- package/richtexteditor/rte-config.js +2 -2
- package/richtexteditor/rte.js +55 -51
- package/richtexteditor/rte_theme_default.css +151 -83
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
|
|
2
|
+
|
|
3
|
+
// 2026-08-15 Import styles from the page's own stylesheets.
|
|
4
|
+
//
|
|
5
|
+
// The four Quick Styles dropdowns (inlineStyles / paragraphStyles / imageStyles
|
|
6
|
+
// / linkStyles) have always been hand-configured, and the shipped defaults are
|
|
7
|
+
// placeholders — "my-cls-mark", "my-cls-quote" — that mean nothing in a real
|
|
8
|
+
// integration. Every team wiring this editor into an existing design system
|
|
9
|
+
// therefore retypes their own class list into config before the dropdowns are
|
|
10
|
+
// worth opening. TinyMCE removes that step with `importcss`; this is our
|
|
11
|
+
// equivalent.
|
|
12
|
+
//
|
|
13
|
+
// Design notes:
|
|
14
|
+
// - OPT-IN (`importStyles = true`). Silently rewriting the Styles menu out of
|
|
15
|
+
// whatever CSS happens to be on the page would be a surprising default, and
|
|
16
|
+
// on a utility-CSS site it would be a catastrophic one.
|
|
17
|
+
// - The ELEMENT QUALIFIER decides the bucket: `img.hero` is an image style,
|
|
18
|
+
// `a.cta` a link style, `p.lede` a paragraph style. An unqualified `.foo`
|
|
19
|
+
// cannot be placed, so it goes in both the inline and paragraph menus —
|
|
20
|
+
// the author picks by what they have selected.
|
|
21
|
+
// - Cross-origin stylesheets throw a SecurityError on `.cssRules`, and it is
|
|
22
|
+
// thrown on ACCESS, not returned as null. One un-caught sheet from a CDN
|
|
23
|
+
// kills the whole scan, which is the classic way this feature fails.
|
|
24
|
+
// - `@media` / `@supports` blocks hold their own `cssRules`; a design system's
|
|
25
|
+
// responsive variants live in there, so the walk recurses.
|
|
26
|
+
// - There is a HARD CAP. A Tailwind or Bootstrap page exposes thousands of
|
|
27
|
+
// atomic classes, and a dropdown with 4,000 entries is not a feature. When
|
|
28
|
+
// the cap truncates, it says so via console + `getImportedStyles().truncated`
|
|
29
|
+
// rather than quietly showing the first hundred as if that were all of them.
|
|
30
|
+
RTE_DefaultConfig.plugin_importstyles = RTE_Plugin_ImportStyles;
|
|
31
|
+
|
|
32
|
+
// Master switch.
|
|
33
|
+
if (typeof RTE_DefaultConfig.importStyles === "undefined") RTE_DefaultConfig.importStyles = false;
|
|
34
|
+
|
|
35
|
+
// Which stylesheets to read. null = all readable ones. A string/RegExp is
|
|
36
|
+
// matched against the sheet's href ("site.css", /theme/), a function gets the
|
|
37
|
+
// CSSStyleSheet.
|
|
38
|
+
if (typeof RTE_DefaultConfig.importStylesSheets === "undefined") RTE_DefaultConfig.importStylesSheets = null;
|
|
39
|
+
|
|
40
|
+
// Classes never imported: the editor's own chrome, and the other editors'
|
|
41
|
+
// (a page migrating from CKEditor/TinyMCE still has their CSS loaded).
|
|
42
|
+
if (typeof RTE_DefaultConfig.importStylesExclude === "undefined") {
|
|
43
|
+
RTE_DefaultConfig.importStylesExclude = /^(rte[-_]|ck[-_]|cke[-_]|mce[-_]|tox[-_]|js[-_]|is[-_]|has[-_])/i;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Per-class predicate. Return false to skip, a string to override the label.
|
|
47
|
+
// function (className, selectorText, bucket) {}
|
|
48
|
+
if (typeof RTE_DefaultConfig.importStylesFilter === "undefined") RTE_DefaultConfig.importStylesFilter = null;
|
|
49
|
+
|
|
50
|
+
// Replace the configured styles instead of appending to them.
|
|
51
|
+
if (typeof RTE_DefaultConfig.importStylesReplace === "undefined") RTE_DefaultConfig.importStylesReplace = false;
|
|
52
|
+
|
|
53
|
+
// Ceiling per dropdown.
|
|
54
|
+
if (typeof RTE_DefaultConfig.importStylesLimit === "undefined") RTE_DefaultConfig.importStylesLimit = 100;
|
|
55
|
+
|
|
56
|
+
function RTE_Plugin_ImportStyles() {
|
|
57
|
+
var obj = this;
|
|
58
|
+
var config, editor;
|
|
59
|
+
var imported = null;
|
|
60
|
+
var scannedDocs = [];
|
|
61
|
+
|
|
62
|
+
obj.PluginName = "ImportStyles";
|
|
63
|
+
|
|
64
|
+
obj.InitConfig = function (argconfig) { config = argconfig; };
|
|
65
|
+
|
|
66
|
+
obj.InitEditor = function (argeditor) {
|
|
67
|
+
editor = argeditor;
|
|
68
|
+
|
|
69
|
+
// Public API — usable even with importStyles off, so an integrator can
|
|
70
|
+
// inspect what WOULD be imported before switching it on.
|
|
71
|
+
editor.importStyles = function (options) { return run(options || {}); };
|
|
72
|
+
editor.getImportedStyles = function () { return imported; };
|
|
73
|
+
editor.scanStylesheetClasses = function (options) {
|
|
74
|
+
activeOptions = options || {};
|
|
75
|
+
try { return collect(); } finally { activeOptions = {}; }
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if (!config.importStyles) return;
|
|
79
|
+
|
|
80
|
+
// The host document's sheets are ready now; the editing document's are
|
|
81
|
+
// not, so scan again once it exists. The dropdown panels read config
|
|
82
|
+
// when they OPEN, so a later scan still lands.
|
|
83
|
+
run({});
|
|
84
|
+
try { editor.attachEvent("ready", function () { run({}); }); } catch (e) {}
|
|
85
|
+
setTimeout(function () { run({}); }, 0);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// ---- buckets ---------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
var IMAGE_TAGS = { img: 1, figure: 1, picture: 1 };
|
|
91
|
+
var LINK_TAGS = { a: 1 };
|
|
92
|
+
var INLINE_TAGS = { span: 1, strong: 1, b: 1, em: 1, i: 1, code: 1, mark: 1, small: 1, sub: 1, sup: 1, abbr: 1, q: 1, cite: 1 };
|
|
93
|
+
var BLOCK_TAGS = { p: 1, div: 1, h1: 1, h2: 1, h3: 1, h4: 1, h5: 1, h6: 1, blockquote: 1, li: 1, ul: 1, ol: 1, pre: 1, section: 1, article: 1, aside: 1, figcaption: 1, td: 1, th: 1, table: 1 };
|
|
94
|
+
|
|
95
|
+
var TARGETS = [
|
|
96
|
+
{ key: "imageStyles", tags: IMAGE_TAGS },
|
|
97
|
+
{ key: "linkStyles", tags: LINK_TAGS },
|
|
98
|
+
{ key: "inlineStyles", tags: INLINE_TAGS },
|
|
99
|
+
{ key: "paragraphStyles", tags: BLOCK_TAGS }
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
// The last class in the selector is the one the author would apply; the
|
|
103
|
+
// element qualifier immediately before it says what it applies TO.
|
|
104
|
+
// "article .card p.lede:hover" -> tag "p", class "lede".
|
|
105
|
+
var SELECTOR_RE = /^([a-zA-Z][\w-]*)?\.([\w-]+)$/;
|
|
106
|
+
|
|
107
|
+
function parseSelector(selector) {
|
|
108
|
+
// Only the rightmost compound matters; strip pseudo-classes/elements
|
|
109
|
+
// and attribute filters, which are states of a class, not new classes.
|
|
110
|
+
var simple = String(selector)
|
|
111
|
+
.replace(/\[[^\]]*\]/g, "")
|
|
112
|
+
.replace(/::?[\w-]+(\([^)]*\))?/g, "")
|
|
113
|
+
.trim();
|
|
114
|
+
var parts = simple.split(/[\s>+~]+/);
|
|
115
|
+
var last = parts[parts.length - 1] || "";
|
|
116
|
+
// A compound like "p.lede.wide" has no single applicable class.
|
|
117
|
+
if ((last.match(/\./g) || []).length !== 1) return null;
|
|
118
|
+
var m = last.match(SELECTOR_RE);
|
|
119
|
+
if (!m) return null;
|
|
120
|
+
return { tag: (m[1] || "").toLowerCase(), cls: m[2] };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function bucketsFor(tag) {
|
|
124
|
+
if (!tag) return ["inlineStyles", "paragraphStyles"]; // unqualified: offer both
|
|
125
|
+
for (var i = 0; i < TARGETS.length; i++) if (TARGETS[i].tags[tag]) return [TARGETS[i].key];
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ---- stylesheet walk -------------------------------------------------
|
|
130
|
+
|
|
131
|
+
// The editor's own CSS must never reach the Styles menu, and the reliable
|
|
132
|
+
// axis for that is ORIGIN, not class name. A first cut filtered by class
|
|
133
|
+
// prefix and imported 77 classes — the theme's `richtexteditor`/`colordiv`,
|
|
134
|
+
// and 1,788 rules of `aitoolkit.css` — because plugin CSS does not agree on
|
|
135
|
+
// one prefix. Where a sheet came from is unambiguous:
|
|
136
|
+
// - anything served out of the editor's own asset folder,
|
|
137
|
+
// - any <style> the editor injected (its nodes carry rte-/__rte ids),
|
|
138
|
+
// - any anonymous <style> inside the EDITING document, which is only ever
|
|
139
|
+
// editor-injected — a site's content CSS arrives there via contentCssUrl
|
|
140
|
+
// and therefore has an href.
|
|
141
|
+
function isEditorSheet(sheet, isEditingDoc) {
|
|
142
|
+
var href = sheet.href || "";
|
|
143
|
+
if (/\/richtexteditor\//i.test(href)) return true;
|
|
144
|
+
var node = sheet.ownerNode;
|
|
145
|
+
var id = (node && node.id) || "";
|
|
146
|
+
if (/^(rte[-_]|__rte)/i.test(id)) return true;
|
|
147
|
+
if (isEditingDoc && !href) return true;
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Options passed to editor.importStyles() win over config. The editor copies
|
|
152
|
+
// config at construction time, so mutating the caller's config object later
|
|
153
|
+
// does NOT reach this plugin — an API that quietly ignored its arguments
|
|
154
|
+
// would be worse than no API at all.
|
|
155
|
+
var activeOptions = {};
|
|
156
|
+
function setting(name) {
|
|
157
|
+
return (activeOptions && name in activeOptions) ? activeOptions[name] : config["importStyles" + name.charAt(0).toUpperCase() + name.slice(1)];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function wantSheet(sheet, isEditingDoc) {
|
|
161
|
+
if (isEditorSheet(sheet, isEditingDoc)) return false;
|
|
162
|
+
var want = setting("sheets");
|
|
163
|
+
if (!want) return true;
|
|
164
|
+
var href = sheet.href || "";
|
|
165
|
+
if (typeof want === "function") return !!want(sheet);
|
|
166
|
+
if (want instanceof RegExp) return want.test(href);
|
|
167
|
+
return href.indexOf(String(want)) >= 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function excluded(cls) {
|
|
171
|
+
var ex = setting("exclude");
|
|
172
|
+
if (!ex) return false;
|
|
173
|
+
if (ex instanceof RegExp) return ex.test(cls);
|
|
174
|
+
if (typeof ex === "function") return !!ex(cls);
|
|
175
|
+
return cls.indexOf(String(ex)) === 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function walkRules(rules, out) {
|
|
179
|
+
if (!rules) return;
|
|
180
|
+
for (var i = 0; i < rules.length; i++) {
|
|
181
|
+
var rule = rules[i];
|
|
182
|
+
// @media / @supports carry their own rule list.
|
|
183
|
+
if (rule.cssRules && !rule.selectorText) { walkRules(rule.cssRules, out); continue; }
|
|
184
|
+
if (!rule.selectorText) continue;
|
|
185
|
+
var selectors = rule.selectorText.split(",");
|
|
186
|
+
for (var s = 0; s < selectors.length; s++) {
|
|
187
|
+
var parsed = parseSelector(selectors[s]);
|
|
188
|
+
if (!parsed || excluded(parsed.cls)) continue;
|
|
189
|
+
out.push({ cls: parsed.cls, tag: parsed.tag, selector: selectors[s].trim() });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function documents() {
|
|
195
|
+
var docs = [];
|
|
196
|
+
if (typeof document !== "undefined") docs.push({ doc: document, editing: false });
|
|
197
|
+
try {
|
|
198
|
+
var d = editor.getDocument && editor.getDocument();
|
|
199
|
+
if (d && d !== document) docs.push({ doc: d, editing: true });
|
|
200
|
+
} catch (e) {}
|
|
201
|
+
return docs;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function collect() {
|
|
205
|
+
var found = [];
|
|
206
|
+
var unreadable = 0;
|
|
207
|
+
var docs = documents();
|
|
208
|
+
for (var d = 0; d < docs.length; d++) {
|
|
209
|
+
var sheets = docs[d].doc.styleSheets || [];
|
|
210
|
+
for (var i = 0; i < sheets.length; i++) {
|
|
211
|
+
var sheet = sheets[i];
|
|
212
|
+
if (!wantSheet(sheet, docs[d].editing)) continue;
|
|
213
|
+
var rules = null;
|
|
214
|
+
try {
|
|
215
|
+
// Throws SecurityError for a cross-origin sheet. This must
|
|
216
|
+
// not abort the scan — a single CDN font sheet would
|
|
217
|
+
// otherwise silently empty the whole Styles menu.
|
|
218
|
+
rules = sheet.cssRules || sheet.rules;
|
|
219
|
+
} catch (e) { unreadable++; continue; }
|
|
220
|
+
walkRules(rules, found);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return { classes: found, unreadableSheets: unreadable, documents: docs.length };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// "my-cls-large-center" -> "Large Center"; "lede" -> "Lede".
|
|
227
|
+
function humanize(cls) {
|
|
228
|
+
var text = String(cls).replace(/^(my[-_]cls[-_]|u[-_]|c[-_])/i, "").replace(/[-_]+/g, " ").trim();
|
|
229
|
+
if (!text) text = cls;
|
|
230
|
+
return text.replace(/\b\w/g, function (ch) { return ch.toUpperCase(); });
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function alreadyListed(list, value) {
|
|
234
|
+
for (var i = 0; i < list.length; i++) if (list[i] && list[i][1] === value) return true;
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ---- apply -----------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
function run(options) {
|
|
241
|
+
activeOptions = options || {};
|
|
242
|
+
var scan = collect();
|
|
243
|
+
var limit = Math.max(0, parseInt(setting("limit"), 10) || 0) || Infinity;
|
|
244
|
+
var replace = !!setting("replace");
|
|
245
|
+
var filter = setting("filter");
|
|
246
|
+
|
|
247
|
+
var buckets = {};
|
|
248
|
+
var truncated = {};
|
|
249
|
+
for (var t = 0; t < TARGETS.length; t++) buckets[TARGETS[t].key] = [];
|
|
250
|
+
|
|
251
|
+
var seen = {};
|
|
252
|
+
for (var i = 0; i < scan.classes.length; i++) {
|
|
253
|
+
var item = scan.classes[i];
|
|
254
|
+
var targets = bucketsFor(item.tag);
|
|
255
|
+
for (var b = 0; b < targets.length; b++) {
|
|
256
|
+
var key = targets[b];
|
|
257
|
+
var dedupeKey = key + "|" + item.cls;
|
|
258
|
+
if (seen[dedupeKey]) continue;
|
|
259
|
+
|
|
260
|
+
var label = humanize(item.cls);
|
|
261
|
+
if (typeof filter === "function") {
|
|
262
|
+
var verdict = filter(item.cls, item.selector, key);
|
|
263
|
+
if (verdict === false) continue;
|
|
264
|
+
if (typeof verdict === "string" && verdict) label = verdict;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
seen[dedupeKey] = true;
|
|
268
|
+
if (buckets[key].length >= limit) { truncated[key] = (truncated[key] || 0) + 1; continue; }
|
|
269
|
+
// Two-element form = apply as a class (the three-element form is
|
|
270
|
+
// for inline CSS); this is what the dropdowns already expect.
|
|
271
|
+
buckets[key].push([label, item.cls]);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
var applied = {};
|
|
276
|
+
for (var k = 0; k < TARGETS.length; k++) {
|
|
277
|
+
var name = TARGETS[k].key;
|
|
278
|
+
var incoming = buckets[name];
|
|
279
|
+
if (replace) {
|
|
280
|
+
config[name] = incoming;
|
|
281
|
+
} else {
|
|
282
|
+
if (!config[name]) config[name] = [];
|
|
283
|
+
for (var j = 0; j < incoming.length; j++) {
|
|
284
|
+
if (!alreadyListed(config[name], incoming[j][1])) config[name].push(incoming[j]);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
applied[name] = incoming.length;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// A cap that hides work is a lie about coverage. Say what was dropped.
|
|
291
|
+
var dropped = 0;
|
|
292
|
+
for (var key2 in truncated) if (truncated.hasOwnProperty(key2)) dropped += truncated[key2];
|
|
293
|
+
if (dropped) {
|
|
294
|
+
try {
|
|
295
|
+
console.warn("[RichTextEditor] importStyles: " + dropped + " class(es) omitted — " +
|
|
296
|
+
"importStylesLimit is " + limit + " per dropdown. Raise the limit or narrow " +
|
|
297
|
+
"importStylesSheets / importStylesFilter.");
|
|
298
|
+
} catch (e) {}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
imported = {
|
|
302
|
+
applied: applied,
|
|
303
|
+
truncated: truncated,
|
|
304
|
+
droppedCount: dropped,
|
|
305
|
+
unreadableSheets: scan.unreadableSheets,
|
|
306
|
+
documentsScanned: scan.documents,
|
|
307
|
+
replaced: !!replace
|
|
308
|
+
};
|
|
309
|
+
activeOptions = {};
|
|
310
|
+
return imported;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -248,6 +248,72 @@ function RTE_Plugin_InsertCode() {
|
|
|
248
248
|
|
|
249
249
|
obj.DoShowDialog = function () {
|
|
250
250
|
|
|
251
|
+
// Capture the caret BEFORE the dialog opens. The dialog focuses its own
|
|
252
|
+
// textarea, which clears the editor's selection - so by the time Insert
|
|
253
|
+
// is clicked there is no caret left to insert at, and the code block was
|
|
254
|
+
// appended as a new root paragraph instead of landing where the user was
|
|
255
|
+
// typing. Same failure mode as the 2026-05-08 image-upload fix in core.
|
|
256
|
+
var savedRange = null;
|
|
257
|
+
try {
|
|
258
|
+
var edoc = editor.getDocument();
|
|
259
|
+
var esel = edoc.defaultView.getSelection();
|
|
260
|
+
if (esel && esel.rangeCount) savedRange = esel.getRangeAt(0).cloneRange();
|
|
261
|
+
} catch (e) { savedRange = null; }
|
|
262
|
+
|
|
263
|
+
function restoreCaret() {
|
|
264
|
+
if (!savedRange) return false;
|
|
265
|
+
try {
|
|
266
|
+
var edoc = editor.getDocument();
|
|
267
|
+
var esel = edoc.defaultView.getSelection();
|
|
268
|
+
esel.removeAllRanges();
|
|
269
|
+
esel.addRange(savedRange);
|
|
270
|
+
return true;
|
|
271
|
+
} catch (e) { return false; }
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// A <div class="dp-highlighter"> dropped at a caret INSIDE a <p> is
|
|
275
|
+
// invalid nesting. It looks right in the live DOM, but the moment the
|
|
276
|
+
// saved HTML is parsed again the browser hoists the div out and tears the
|
|
277
|
+
// paragraph in two -- 3 blocks become 5, so the document a customer
|
|
278
|
+
// reloads is not the one they saved. Split the paragraph ourselves and
|
|
279
|
+
// place the block between the halves, which round-trips byte-stable.
|
|
280
|
+
function insertCodeBlock(html) {
|
|
281
|
+
if (!restoreCaret()) return false;
|
|
282
|
+
var edoc = editor.getDocument();
|
|
283
|
+
var esel = edoc.defaultView.getSelection();
|
|
284
|
+
if (!esel || !esel.rangeCount) return false;
|
|
285
|
+
var range = esel.getRangeAt(0);
|
|
286
|
+
var n = range.startContainer, block = null;
|
|
287
|
+
while (n && n !== edoc.body) {
|
|
288
|
+
if (n.nodeType === 1 && /^(P|H[1-6]|LI|TD|TH|DIV|BLOCKQUOTE)$/.test(n.tagName)) { block = n; break; }
|
|
289
|
+
n = n.parentNode;
|
|
290
|
+
}
|
|
291
|
+
var holder = edoc.createElement("div");
|
|
292
|
+
holder.innerHTML = html;
|
|
293
|
+
var node = holder.firstChild;
|
|
294
|
+
if (!node) return false;
|
|
295
|
+
// DIV/LI/TD/BLOCKQUOTE may legally contain the block: insert in place.
|
|
296
|
+
if (!block || !/^(P|H[1-6])$/.test(block.tagName)) {
|
|
297
|
+
range.deleteContents();
|
|
298
|
+
range.insertNode(node);
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
range.deleteContents();
|
|
302
|
+
var tail = range.cloneRange();
|
|
303
|
+
tail.setEndAfter(block.lastChild || block);
|
|
304
|
+
var tailFrag = tail.extractContents();
|
|
305
|
+
block.parentNode.insertBefore(node, block.nextSibling);
|
|
306
|
+
var tailBlock = edoc.createElement(block.tagName);
|
|
307
|
+
tailBlock.appendChild(tailFrag);
|
|
308
|
+
if (tailBlock.textContent.replace(/^\s+|\s+$/g, "") !== "" || tailBlock.querySelector("*")) {
|
|
309
|
+
block.parentNode.insertBefore(tailBlock, node.nextSibling);
|
|
310
|
+
}
|
|
311
|
+
if (block.textContent.replace(/^\s+|\s+$/g, "") === "" && !block.querySelector("img,table")) {
|
|
312
|
+
block.parentNode.removeChild(block);
|
|
313
|
+
}
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
251
317
|
var dialoginner = editor.createDialog(editor.getLangText("insertcode"), "rte-dialog-insertcode");
|
|
252
318
|
|
|
253
319
|
var div2 = __Append(dialoginner, "div", "position:relative;text-align:center;");
|
|
@@ -300,12 +366,23 @@ function RTE_Plugin_InsertCode() {
|
|
|
300
366
|
|
|
301
367
|
var tag = textarea.previousSibling
|
|
302
368
|
|
|
303
|
-
|
|
304
|
-
|
|
369
|
+
// insertHTML honours the caret; insertRootParagraph always
|
|
370
|
+
// appended at document level, which is why the block never
|
|
371
|
+
// landed at the focus position.
|
|
372
|
+
if (insertCodeBlock('<div class="dp-highlighter">' + tag.innerHTML + "</div>")) {
|
|
373
|
+
// placed at the caret, at block level
|
|
374
|
+
} else {
|
|
375
|
+
var p = editor.insertRootParagraph();
|
|
376
|
+
p.innerHTML = '<div class="dp-highlighter">' + tag.innerHTML + "</div>";
|
|
377
|
+
}
|
|
305
378
|
}
|
|
306
379
|
else {
|
|
307
|
-
|
|
308
|
-
|
|
380
|
+
if (restoreCaret()) {
|
|
381
|
+
editor.insertText(textarea.value);
|
|
382
|
+
} else {
|
|
383
|
+
var p = editor.insertRootParagraph();
|
|
384
|
+
p.innerText = textarea.value;
|
|
385
|
+
}
|
|
309
386
|
}
|
|
310
387
|
|
|
311
388
|
editor.focus();
|