@richscripts/richtexteditor 2.4.2 → 2.5.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 +2 -2
- package/richtexteditor/all_plugins.js +2837 -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 +2837 -935
- package/richtexteditor/plugins/all_plugins.min.js +58 -58
- 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/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 +63 -55
- package/richtexteditor/rte_theme_default.css +172 -107
- package/richtexteditor/runtime/richtexteditor_content.css +21 -14
|
@@ -232,7 +232,14 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
232
232
|
function repairIssue(issueIndex, options) {
|
|
233
233
|
if (!lastResult || !lastResult.issues || !lastResult.issues[issueIndex]) return runAudit();
|
|
234
234
|
var ctor = window.RichTextEditor;
|
|
235
|
-
|
|
235
|
+
// An issue carrying `_target` came from the DOM audit and is addressed
|
|
236
|
+
// by a DOM node, not by a JSON path. Handing it to the structured-content
|
|
237
|
+
// repairer looks like it works — no error, a document comes back — but
|
|
238
|
+
// that repairer cannot resolve the node and returns the input unchanged,
|
|
239
|
+
// so the fix silently does nothing.
|
|
240
|
+
var issue = lastResult.issues[issueIndex];
|
|
241
|
+
if (issue._target || lastResult.source === "dom") return repairDomIssue(issue, options || {});
|
|
242
|
+
if (!ctor || typeof ctor.repairAccessibilityIssue !== "function") return repairDomIssue(issue, options || {});
|
|
236
243
|
var nextDocument = ctor.repairAccessibilityIssue(editor.getJSON(), lastResult.issues[issueIndex], options || {});
|
|
237
244
|
editor.setJSON(nextDocument);
|
|
238
245
|
selectedIssueIndex = issueIndex;
|
|
@@ -264,6 +271,101 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
264
271
|
editor.focus();
|
|
265
272
|
}
|
|
266
273
|
|
|
274
|
+
// ---- WCAG 3.1.2 Language of Parts -----------------------------------
|
|
275
|
+
//
|
|
276
|
+
// A passage in a language other than the document's must be marked, or a
|
|
277
|
+
// screen reader pronounces it with the wrong phoneme set. Detecting
|
|
278
|
+
// "French inside English" is a language-identification problem we are not
|
|
279
|
+
// going to pretend to solve; detecting a different WRITING SYSTEM is
|
|
280
|
+
// deterministic, and it catches the cases that actually break speech
|
|
281
|
+
// synthesis outright (Hebrew, Arabic, Greek, Cyrillic, CJK in a Latin
|
|
282
|
+
// document, and the reverse). We flag only what we can be certain of —
|
|
283
|
+
// a checker that cries wolf gets switched off.
|
|
284
|
+
var SCRIPTS = [
|
|
285
|
+
{ name: "Hebrew", lang: "he", re: /[-]/g },
|
|
286
|
+
{ name: "Arabic", lang: "ar", re: /[-ۿݐ-ݿﭐ-﷿ﹰ-]/g },
|
|
287
|
+
{ name: "Greek", lang: "el", re: /[Ͱ-Ͽἀ-]/g },
|
|
288
|
+
{ name: "Cyrillic", lang: "ru", re: /[Ѐ-ӿ]/g },
|
|
289
|
+
{ name: "Han", lang: "zh", re: /[㐀-䶿一-鿿]/g },
|
|
290
|
+
{ name: "Kana", lang: "ja", re: /[-ゟ゠-ヿ]/g },
|
|
291
|
+
{ name: "Hangul", lang: "ko", re: /[가-ᄀ-ᇿ]/g },
|
|
292
|
+
{ name: "Devanagari", lang: "hi", re: /[ऀ-ॿ]/g },
|
|
293
|
+
{ name: "Thai", lang: "th", re: /[-]/g },
|
|
294
|
+
{ name: "Latin", lang: "en", re: /[A-Za-zÀ-ɏ]/g }
|
|
295
|
+
];
|
|
296
|
+
|
|
297
|
+
// Below this, a "run" is more likely a stray glyph, a maths symbol or a
|
|
298
|
+
// single borrowed character than a passage worth annotating.
|
|
299
|
+
var MIN_SCRIPT_RUN = 4;
|
|
300
|
+
|
|
301
|
+
// Every script present in the text with enough characters to be a passage.
|
|
302
|
+
//
|
|
303
|
+
// Deliberately NOT "the dominant script": a foreign phrase is by definition
|
|
304
|
+
// the minority of its paragraph, so picking the most common script finds
|
|
305
|
+
// the document's own language and reports nothing. "A Hebrew proverb,
|
|
306
|
+
// שלום עליכם, appears mid-sentence" is 40 Latin characters and 9 Hebrew —
|
|
307
|
+
// the 9 are the entire point.
|
|
308
|
+
function scriptsIn(text) {
|
|
309
|
+
var found = [];
|
|
310
|
+
for (var i = 0; i < SCRIPTS.length; i++) {
|
|
311
|
+
var m = String(text).match(SCRIPTS[i].re);
|
|
312
|
+
if (m && m.length >= MIN_SCRIPT_RUN) found.push(SCRIPTS[i]);
|
|
313
|
+
}
|
|
314
|
+
return found;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function documentScript() {
|
|
318
|
+
var editable = editor.getEditable ? editor.getEditable() : null;
|
|
319
|
+
var code = (editable && editable.getAttribute("lang")) ||
|
|
320
|
+
(typeof document !== "undefined" && document.documentElement.getAttribute("lang")) || "en";
|
|
321
|
+
code = String(code).toLowerCase().split("-")[0];
|
|
322
|
+
for (var i = 0; i < SCRIPTS.length; i++) if (SCRIPTS[i].lang === code) return SCRIPTS[i];
|
|
323
|
+
// An unlisted language tag (nl, pt, sv…) is Latin-script; defaulting to
|
|
324
|
+
// Latin keeps us from flagging every English word in a Dutch document.
|
|
325
|
+
return SCRIPTS[SCRIPTS.length - 1];
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function hasLangAncestor(node, editable) {
|
|
329
|
+
var n = (node && node.nodeType === 3) ? node.parentNode : node;
|
|
330
|
+
while (n && n !== editable && n.nodeType === 1) {
|
|
331
|
+
if (n.hasAttribute && n.hasAttribute("lang")) return true;
|
|
332
|
+
n = n.parentNode;
|
|
333
|
+
}
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function collectUnmarkedLanguageRuns(block, editable, path, issues) {
|
|
338
|
+
var docScript = documentScript();
|
|
339
|
+
var doc = block.ownerDocument;
|
|
340
|
+
if (!doc || !doc.createTreeWalker) return;
|
|
341
|
+
var walker = doc.createTreeWalker(block, 4 /* SHOW_TEXT */, null, false);
|
|
342
|
+
var textNode, seen = {};
|
|
343
|
+
while ((textNode = walker.nextNode())) {
|
|
344
|
+
var value = textNode.nodeValue || "";
|
|
345
|
+
if (!value.replace(/\s+/g, "")) continue;
|
|
346
|
+
if (hasLangAncestor(textNode, editable)) continue;
|
|
347
|
+
var scripts = scriptsIn(value);
|
|
348
|
+
for (var s = 0; s < scripts.length; s++) {
|
|
349
|
+
var script = scripts[s];
|
|
350
|
+
if (script === docScript) continue;
|
|
351
|
+
// One issue per script per block: a paragraph with six Hebrew
|
|
352
|
+
// words is one thing to fix, not six.
|
|
353
|
+
if (seen[script.name]) continue;
|
|
354
|
+
seen[script.name] = true;
|
|
355
|
+
issues.push({
|
|
356
|
+
code: "language-of-parts",
|
|
357
|
+
severity: "warning",
|
|
358
|
+
message: script.name + " text is not marked with a language. Select the phrase and set its " +
|
|
359
|
+
"language so assistive technology pronounces it correctly (WCAG 3.1.2).",
|
|
360
|
+
path: path + ".lang",
|
|
361
|
+
_target: block,
|
|
362
|
+
_lang: script.lang,
|
|
363
|
+
_script: script.name
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
267
369
|
function auditDomAccessibility() {
|
|
268
370
|
var editable = editor.getEditable ? editor.getEditable() : null;
|
|
269
371
|
var issues = [];
|
|
@@ -333,6 +435,8 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
333
435
|
});
|
|
334
436
|
}
|
|
335
437
|
}
|
|
438
|
+
|
|
439
|
+
collectUnmarkedLanguageRuns(node, editable, path, issues);
|
|
336
440
|
}
|
|
337
441
|
|
|
338
442
|
return { document: null, issues: issues, valid: !issues.length, source: "dom" };
|
|
@@ -358,12 +462,74 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
358
462
|
else if (issue.code === "table-missing-header") {
|
|
359
463
|
promoteFirstTableRow(target);
|
|
360
464
|
}
|
|
465
|
+
else if (issue.code === "language-of-parts") {
|
|
466
|
+
markLanguageRuns(target, options && options.lang ? options.lang : issue._lang, issue._script);
|
|
467
|
+
}
|
|
361
468
|
|
|
362
469
|
selectedIssueIndex = 0;
|
|
363
470
|
scheduleEditorChange();
|
|
364
471
|
return runAudit();
|
|
365
472
|
}
|
|
366
473
|
|
|
474
|
+
// Wrap the offending script's runs in <span lang> — the same markup
|
|
475
|
+
// textpartlanguage.js produces, so the two features agree on one shape and
|
|
476
|
+
// the author can retarget the result from the Language dropdown afterwards.
|
|
477
|
+
//
|
|
478
|
+
// Only the matching characters (plus the spaces and punctuation BETWEEN
|
|
479
|
+
// them) are wrapped: wrapping the whole text node would relabel the
|
|
480
|
+
// surrounding English as Hebrew, which is a worse lie than leaving it
|
|
481
|
+
// unmarked.
|
|
482
|
+
function markLanguageRuns(block, lang, scriptName) {
|
|
483
|
+
if (!block || !lang) return;
|
|
484
|
+
var script = null;
|
|
485
|
+
for (var i = 0; i < SCRIPTS.length; i++) if (SCRIPTS[i].name === scriptName) script = SCRIPTS[i];
|
|
486
|
+
if (!script) return;
|
|
487
|
+
var doc = block.ownerDocument;
|
|
488
|
+
var editable = editor.getEditable ? editor.getEditable() : null;
|
|
489
|
+
var rtl = (lang === "he" || lang === "ar" || lang === "fa" || lang === "ur");
|
|
490
|
+
|
|
491
|
+
var walker = doc.createTreeWalker(block, 4 /* SHOW_TEXT */, null, false);
|
|
492
|
+
var pending = [], n;
|
|
493
|
+
while ((n = walker.nextNode())) {
|
|
494
|
+
if (hasLangAncestor(n, editable)) continue;
|
|
495
|
+
pending.push(n);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
for (var t = 0; t < pending.length; t++) {
|
|
499
|
+
var node = pending[t];
|
|
500
|
+
var value = node.nodeValue || "";
|
|
501
|
+
// Character classes are per-char; build a run matcher that also
|
|
502
|
+
// absorbs the neutral characters sitting inside a passage.
|
|
503
|
+
var runRe = new RegExp("(?:" + script.re.source + "|[\\s.,;:!?'\"()\\u2010-\\u2027\\u00AB\\u00BB\\u201C\\u201D])*" +
|
|
504
|
+
script.re.source +
|
|
505
|
+
"(?:" + script.re.source + "|[\\s.,;:!?'\"()\\u2010-\\u2027\\u00AB\\u00BB\\u201C\\u201D])*", "g");
|
|
506
|
+
var frag = doc.createDocumentFragment();
|
|
507
|
+
var last = 0, m, wrapped = false;
|
|
508
|
+
while ((m = runRe.exec(value))) {
|
|
509
|
+
var text = m[0];
|
|
510
|
+
// Trim the neutrals the greedy match pulled in at the edges;
|
|
511
|
+
// they belong to the surrounding sentence, not the phrase.
|
|
512
|
+
var lead = text.length - text.replace(/^[\s.,;:!?'"()‐-‧«»“”]+/, "").length;
|
|
513
|
+
var trail = text.length - text.replace(/[\s.,;:!?'"()‐-‧«»“”]+$/, "").length;
|
|
514
|
+
var start = m.index + lead;
|
|
515
|
+
var end = m.index + text.length - trail;
|
|
516
|
+
if (end - start < MIN_SCRIPT_RUN) continue;
|
|
517
|
+
if (start > last) frag.appendChild(doc.createTextNode(value.slice(last, start)));
|
|
518
|
+
var span = doc.createElement("span");
|
|
519
|
+
span.setAttribute("lang", lang);
|
|
520
|
+
if (rtl) span.setAttribute("dir", "rtl");
|
|
521
|
+
span.appendChild(doc.createTextNode(value.slice(start, end)));
|
|
522
|
+
frag.appendChild(span);
|
|
523
|
+
last = end;
|
|
524
|
+
wrapped = true;
|
|
525
|
+
if (runRe.lastIndex === m.index) runRe.lastIndex++; // zero-width guard
|
|
526
|
+
}
|
|
527
|
+
if (!wrapped) continue;
|
|
528
|
+
if (last < value.length) frag.appendChild(doc.createTextNode(value.slice(last)));
|
|
529
|
+
node.parentNode.replaceChild(frag, node);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
367
533
|
function promoteFirstTableRow(table) {
|
|
368
534
|
var firstRow = table && table.rows && table.rows.length ? table.rows[0] : null;
|
|
369
535
|
if (!firstRow) return;
|
|
@@ -512,6 +678,25 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
512
678
|
return;
|
|
513
679
|
}
|
|
514
680
|
|
|
681
|
+
if (issue.code === "language-of-parts") {
|
|
682
|
+
var langRow = detail.ownerDocument.createElement("div");
|
|
683
|
+
langRow.className = "rte-a11y-row";
|
|
684
|
+
var mark = detail.ownerDocument.createElement("button");
|
|
685
|
+
mark.type = "button";
|
|
686
|
+
mark.className = "rte-a11y-button";
|
|
687
|
+
mark.innerText = "Mark as " + issue._script;
|
|
688
|
+
mark.onclick = function () { repairIssue(selectedIssueIndex, { lang: issue._lang }); };
|
|
689
|
+
var look = detail.ownerDocument.createElement("button");
|
|
690
|
+
look.type = "button";
|
|
691
|
+
look.className = "rte-a11y-button rte-a11y-button-secondary";
|
|
692
|
+
look.innerText = "Focus issue";
|
|
693
|
+
look.onclick = function () { focusIssue(issue); };
|
|
694
|
+
langRow.appendChild(mark);
|
|
695
|
+
langRow.appendChild(look);
|
|
696
|
+
detail.appendChild(langRow);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
|
|
515
700
|
detail.innerHTML += '<div class="rte-a11y-detail-copy">This issue is visible here, but does not have an automatic repair action yet.</div>';
|
|
516
701
|
}
|
|
517
702
|
|
|
@@ -39,7 +39,7 @@ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
|
|
|
39
39
|
|
|
40
40
|
if (!RTE_DefaultConfig.svgCode_aiassist) {
|
|
41
41
|
RTE_DefaultConfig.svgCode_aiassist = '<svg viewBox="0 0 24 24" width="20" height="20" fill="none" aria-hidden="true"><defs><linearGradient id="rte-ai-robot-shell" x1="5" y1="6" x2="19" y2="18" gradientUnits="userSpaceOnUse"><stop stop-color="#1fb6ff"/><stop offset=".52" stop-color="#2563eb"/><stop offset="1" stop-color="#7c3aed"/></linearGradient><linearGradient id="rte-ai-robot-glow" x1="8" y1="10" x2="16" y2="15" gradientUnits="userSpaceOnUse"><stop stop-color="#fef9c3"/><stop offset="1" stop-color="#fde68a"/></linearGradient></defs><path d="M12 2.4v2.4" stroke="#f59e0b" stroke-width="1.7" stroke-linecap="round"/><circle cx="12" cy="2.4" r="1" fill="#fbbf24"/><rect x="5.1" y="6.1" width="13.8" height="11.2" rx="3.4" fill="url(#rte-ai-robot-shell)" stroke="#1e40af" stroke-width="1.2"/><path d="M8.2 6.1V5.8A1.8 1.8 0 0110 4h4a1.8 1.8 0 011.8 1.8v.3" stroke="#93c5fd" stroke-width="1.2" stroke-linecap="round"/><rect x="7.7" y="9.2" width="8.6" height="4.8" rx="2.4" fill="#0f172a" opacity=".26"/><circle cx="10" cy="11.6" r="1.15" fill="url(#rte-ai-robot-glow)"/><circle cx="14" cy="11.6" r="1.15" fill="url(#rte-ai-robot-glow)"/><path d="M9.4 14.9c.8.55 1.66.83 2.6.83s1.8-.28 2.6-.83" stroke="#ffffff" stroke-width="1.45" stroke-linecap="round"/><path d="M8.1 19.2l1-2.1M15.9 19.2l-1-2.1" stroke="#2563eb" stroke-width="1.5" stroke-linecap="round"/><path d="M18.8 6.4l.55-1.25.55 1.25 1.25.55-1.25.55-.55 1.25-.55-1.25-1.25-.55z" fill="#fef08a"/></svg>';
|
|
42
|
-
RTE_DefaultConfig.svgCode_aiassist = '<svg viewBox="
|
|
42
|
+
RTE_DefaultConfig.svgCode_aiassist = '<svg viewBox="1.78 0.3 21.8 21.8" width="20" height="20" fill="none" aria-hidden="true"><defs><linearGradient id="rte-ai-bot-v5-shell" x1="3" y1="5" x2="21" y2="20" gradientUnits="userSpaceOnUse"><stop stop-color="#22d3ee"/><stop offset=".52" stop-color="#2563eb"/><stop offset="1" stop-color="#4338ca"/></linearGradient><linearGradient id="rte-ai-bot-v5-visor" x1="7" y1="8" x2="17" y2="15" gradientUnits="userSpaceOnUse"><stop stop-color="#f8fafc"/><stop offset="1" stop-color="#bfdbfe"/></linearGradient></defs><rect x="2.6" y="5.2" width="16.4" height="16" rx="4.8" fill="url(#rte-ai-bot-v5-shell)" stroke="#1e40af" stroke-width="1.6"/><rect x="5.9" y="9.1" width="9.8" height="6.8" rx="3.4" fill="url(#rte-ai-bot-v5-visor)"/><circle cx="8.6" cy="12.5" r="1.35" fill="#0f172a"/><circle cx="13" cy="12.5" r="1.35" fill="#0f172a"/><path d="M8.3 18.2c.9.7 1.8 1 2.5 1s1.6-.3 2.5-1" stroke="#eff6ff" stroke-width="1.7" stroke-linecap="round"/><path d="M19.6 .4l1.25 2.7 2.7 1.25-2.7 1.25-1.25 2.7-1.25-2.7-2.7-1.25 2.7-1.25z" fill="#fbbf24"/></svg>';
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
if (!RTE_DefaultConfig.svgCode_aiassist_open_dialog) {
|