@richscripts/richtexteditor 2.14.1 → 2.15.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/README.md +173 -10
- package/angular/build-manifest.json +23 -0
- package/angular/index.d.ts +71 -76
- package/angular/index.mjs +2 -0
- package/angular/package.json +4 -0
- package/headless/index.d.ts +3 -1
- package/headless/index.js +72 -16
- package/index.d.ts +33 -54
- package/integrations/initialization-scope.js +27 -0
- package/integrations/native-mount.js +30 -0
- package/integrations/shared.d.ts +23 -1
- package/integrations/shared.js +58 -22
- package/package.json +25 -3
- package/react/index.js +118 -37
- package/react/index.mjs +29 -0
- package/richtexteditor/all_plugins.js +7001 -1078
- package/richtexteditor/all_plugins.min.js +62 -59
- package/richtexteditor/lang/emoji/LICENSE-CLDR.txt +41 -0
- package/richtexteditor/lang/emoji/rte-emoji-de.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-es.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-fr.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-it.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-ja.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-ko.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-nl.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-pt-pt.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-pt.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-ru.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-zh-Hans.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-zh-Hant.js +3 -0
- package/richtexteditor/lang/rte-lang-af.js +1 -0
- package/richtexteditor/lang/rte-lang-ar.js +1 -0
- package/richtexteditor/lang/rte-lang-bg.js +1 -0
- package/richtexteditor/lang/rte-lang-bn.js +1 -0
- package/richtexteditor/lang/rte-lang-bs.js +1 -0
- package/richtexteditor/lang/rte-lang-ca.js +1 -0
- package/richtexteditor/lang/rte-lang-cs.js +1 -0
- package/richtexteditor/lang/rte-lang-cy.js +1 -0
- package/richtexteditor/lang/rte-lang-da.js +1 -0
- package/richtexteditor/lang/rte-lang-de.js +1017 -2
- package/richtexteditor/lang/rte-lang-el.js +1 -0
- package/richtexteditor/lang/rte-lang-en.js +1 -0
- package/richtexteditor/lang/rte-lang-es.js +1023 -2
- package/richtexteditor/lang/rte-lang-et.js +1 -0
- package/richtexteditor/lang/rte-lang-fa.js +1 -0
- package/richtexteditor/lang/rte-lang-fi.js +1 -0
- package/richtexteditor/lang/rte-lang-fil.js +1 -0
- package/richtexteditor/lang/rte-lang-fj.js +1 -0
- package/richtexteditor/lang/rte-lang-fr.js +1023 -2
- package/richtexteditor/lang/rte-lang-ga.js +1 -0
- package/richtexteditor/lang/rte-lang-gu.js +1 -0
- package/richtexteditor/lang/rte-lang-he.js +1 -0
- package/richtexteditor/lang/rte-lang-hi.js +1 -0
- package/richtexteditor/lang/rte-lang-hr.js +1 -0
- package/richtexteditor/lang/rte-lang-ht.js +1 -0
- package/richtexteditor/lang/rte-lang-hu.js +1 -0
- package/richtexteditor/lang/rte-lang-id.js +1 -0
- package/richtexteditor/lang/rte-lang-is.js +1 -0
- package/richtexteditor/lang/rte-lang-it.js +1023 -2
- package/richtexteditor/lang/rte-lang-ja.js +1023 -2
- package/richtexteditor/lang/rte-lang-kn.js +1 -0
- package/richtexteditor/lang/rte-lang-ko.js +1023 -2
- package/richtexteditor/lang/rte-lang-lt.js +1 -0
- package/richtexteditor/lang/rte-lang-lv.js +1 -0
- package/richtexteditor/lang/rte-lang-mg.js +1 -0
- package/richtexteditor/lang/rte-lang-mi.js +1 -0
- package/richtexteditor/lang/rte-lang-ml.js +1 -0
- package/richtexteditor/lang/rte-lang-mr.js +1 -0
- package/richtexteditor/lang/rte-lang-ms.js +1 -0
- package/richtexteditor/lang/rte-lang-mt.js +1 -0
- package/richtexteditor/lang/rte-lang-mww.js +1 -0
- package/richtexteditor/lang/rte-lang-nb.js +1 -0
- package/richtexteditor/lang/rte-lang-nl.js +1023 -2
- package/richtexteditor/lang/rte-lang-otq.js +1 -0
- package/richtexteditor/lang/rte-lang-pa.js +1 -0
- package/richtexteditor/lang/rte-lang-pl.js +1 -0
- package/richtexteditor/lang/rte-lang-pt-pt.js +1023 -2
- package/richtexteditor/lang/rte-lang-pt.js +1023 -2
- package/richtexteditor/lang/rte-lang-ro.js +1 -0
- package/richtexteditor/lang/rte-lang-ru.js +1023 -2
- package/richtexteditor/lang/rte-lang-sk.js +1 -0
- package/richtexteditor/lang/rte-lang-sl.js +1 -0
- package/richtexteditor/lang/rte-lang-sm.js +1 -0
- package/richtexteditor/lang/rte-lang-sr-Cyrl.js +1 -0
- package/richtexteditor/lang/rte-lang-sr-Latn.js +1 -0
- package/richtexteditor/lang/rte-lang-sv.js +1 -0
- package/richtexteditor/lang/rte-lang-sw.js +1 -0
- package/richtexteditor/lang/rte-lang-ta.js +1 -0
- package/richtexteditor/lang/rte-lang-te.js +1 -0
- package/richtexteditor/lang/rte-lang-th.js +1 -0
- package/richtexteditor/lang/rte-lang-tlh-Latn.js +1 -0
- package/richtexteditor/lang/rte-lang-tlh-Piqd.js +1 -0
- package/richtexteditor/lang/rte-lang-to.js +1 -0
- package/richtexteditor/lang/rte-lang-tr.js +1 -0
- package/richtexteditor/lang/rte-lang-ty.js +1 -0
- package/richtexteditor/lang/rte-lang-uk.js +1 -0
- package/richtexteditor/lang/rte-lang-ur.js +1 -0
- package/richtexteditor/lang/rte-lang-vi.js +1 -0
- package/richtexteditor/lang/rte-lang-yua.js +1 -0
- package/richtexteditor/lang/rte-lang-zh-Hans.js +1017 -2
- package/richtexteditor/lang/rte-lang-zh-Hant.js +1017 -2
- package/richtexteditor/plugins/a11yenhance.js +2 -1
- package/richtexteditor/plugins/a11yenhance.min.js +1 -1
- package/richtexteditor/plugins/accessibilitychecker.js +252 -60
- package/richtexteditor/plugins/accessibilitychecker.min.js +1 -1
- package/richtexteditor/plugins/aitoolkit.js +966 -303
- package/richtexteditor/plugins/aitoolkit.min.js +7 -7
- package/richtexteditor/plugins/all_plugins.js +7001 -1078
- package/richtexteditor/plugins/all_plugins.min.js +62 -59
- package/richtexteditor/plugins/autocorrect.js +66 -31
- package/richtexteditor/plugins/autocorrect.min.js +1 -1
- package/richtexteditor/plugins/autoembed.js +17 -0
- package/richtexteditor/plugins/autoembed.min.js +1 -1
- package/richtexteditor/plugins/blocktypes.js +18 -9
- package/richtexteditor/plugins/blocktypes.min.js +1 -1
- package/richtexteditor/plugins/bookmarkcard.js +12 -5
- package/richtexteditor/plugins/bookmarkcard.min.js +2 -2
- package/richtexteditor/plugins/changecase.js +72 -19
- package/richtexteditor/plugins/changecase.min.js +1 -1
- package/richtexteditor/plugins/charlimit.js +62 -2
- package/richtexteditor/plugins/charlimit.min.js +2 -2
- package/richtexteditor/plugins/comments.js +458 -59
- package/richtexteditor/plugins/comments.min.js +4 -3
- package/richtexteditor/plugins/contentminimap.js +25 -6
- package/richtexteditor/plugins/contentminimap.min.js +1 -1
- package/richtexteditor/plugins/crossreference.js +1 -1
- package/richtexteditor/plugins/crossreference.min.js +1 -1
- package/richtexteditor/plugins/dialoga11y.js +26 -4
- package/richtexteditor/plugins/dialoga11y.min.js +1 -1
- package/richtexteditor/plugins/dictation.js +1 -1
- package/richtexteditor/plugins/documentimport.js +220 -51
- package/richtexteditor/plugins/documentimport.min.js +4 -4
- package/richtexteditor/plugins/documentoutline.js +47 -8
- package/richtexteditor/plugins/documentoutline.min.js +1 -1
- package/richtexteditor/plugins/docxexport.js +204 -32
- package/richtexteditor/plugins/docxexport.min.js +2 -2
- package/richtexteditor/plugins/draghandle.js +267 -3
- package/richtexteditor/plugins/draghandle.min.js +1 -1
- package/richtexteditor/plugins/emailtoolkit.js +28 -14
- package/richtexteditor/plugins/emailtoolkit.min.js +1 -1
- package/richtexteditor/plugins/emojiautocomplete.js +110 -56
- package/richtexteditor/plugins/emojiautocomplete.min.js +1 -1
- package/richtexteditor/plugins/footnotes.js +65 -11
- package/richtexteditor/plugins/footnotes.min.js +1 -1
- package/richtexteditor/plugins/formatpainter.js +10 -0
- package/richtexteditor/plugins/formatpainter.min.js +1 -1
- package/richtexteditor/plugins/formattingmarks.js +6 -6
- package/richtexteditor/plugins/formattingmarks.min.js +1 -1
- package/richtexteditor/plugins/gapcursor.js +60 -0
- package/richtexteditor/plugins/gapcursor.min.js +1 -1
- package/richtexteditor/plugins/ghostcomplete.js +1 -1
- package/richtexteditor/plugins/html2pdf.js +1 -1
- package/richtexteditor/plugins/html2pdf.min.js +1 -1
- package/richtexteditor/plugins/imageeditor.js +12 -6
- package/richtexteditor/plugins/imageeditor.min.js +1 -1
- package/richtexteditor/plugins/importstyles.js +1 -1
- package/richtexteditor/plugins/inlinecode.js +25 -0
- package/richtexteditor/plugins/inlinecode.min.js +1 -1
- package/richtexteditor/plugins/insertcode.js +21 -3
- package/richtexteditor/plugins/insertcode.min.js +4 -4
- package/richtexteditor/plugins/insertemoji.js +112 -15
- package/richtexteditor/plugins/insertemoji.min.js +1 -1
- package/richtexteditor/plugins/insertgallery.js +42 -36
- package/richtexteditor/plugins/insertgallery.min.js +1 -1
- package/richtexteditor/plugins/inserttemplate.js +33 -17
- package/richtexteditor/plugins/inserttemplate.min.js +1 -1
- package/richtexteditor/plugins/keyboarda11y.js +66 -3
- package/richtexteditor/plugins/keyboarda11y.min.js +1 -1
- package/richtexteditor/plugins/layouttable.js +20 -0
- package/richtexteditor/plugins/layouttable.min.js +1 -1
- package/richtexteditor/plugins/linkchecker.js +29 -9
- package/richtexteditor/plugins/linkchecker.min.js +1 -1
- package/richtexteditor/plugins/localdraft.js +51 -7
- package/richtexteditor/plugins/localdraft.min.js +1 -1
- package/richtexteditor/plugins/markdownexport.js +159 -0
- package/richtexteditor/plugins/markdownexport.min.js +2 -2
- package/richtexteditor/plugins/mathdialog.js +11 -5
- package/richtexteditor/plugins/mathdialog.min.js +1 -1
- package/richtexteditor/plugins/mentionplugin.js +88 -25
- package/richtexteditor/plugins/mentionplugin.min.js +1 -1
- package/richtexteditor/plugins/mermaiddiagram.js +9 -3
- package/richtexteditor/plugins/mermaiddiagram.min.js +2 -2
- package/richtexteditor/plugins/multilevellist.js +4 -0
- package/richtexteditor/plugins/multilevellist.min.js +1 -1
- package/richtexteditor/plugins/pdfexport.js +235 -35
- package/richtexteditor/plugins/pdfexport.min.js +12 -12
- package/richtexteditor/plugins/permanentpen.js +3 -3
- package/richtexteditor/plugins/permanentpen.min.js +1 -1
- package/richtexteditor/plugins/readabilitystats.js +25 -11
- package/richtexteditor/plugins/readabilitystats.min.js +1 -1
- package/richtexteditor/plugins/readaloud.js +29 -15
- package/richtexteditor/plugins/readaloud.min.js +1 -1
- package/richtexteditor/plugins/restrictedediting.js +166 -0
- package/richtexteditor/plugins/restrictedediting.min.js +2 -2
- package/richtexteditor/plugins/revisionhistory.js +169 -37
- package/richtexteditor/plugins/revisionhistory.min.js +3 -3
- package/richtexteditor/plugins/rubyannotation.js +19 -9
- package/richtexteditor/plugins/rubyannotation.min.js +1 -1
- package/richtexteditor/plugins/sanitizer.js +62 -3
- package/richtexteditor/plugins/sanitizer.min.js +1 -1
- package/richtexteditor/plugins/slashcommand.js +30 -16
- package/richtexteditor/plugins/slashcommand.min.js +2 -2
- package/richtexteditor/plugins/smartchips.js +36 -22
- package/richtexteditor/plugins/smartchips.min.js +2 -2
- package/richtexteditor/plugins/spellcheck.js +17 -8
- package/richtexteditor/plugins/spellcheck.min.js +2 -2
- package/richtexteditor/plugins/stateinspector.js +19 -9
- package/richtexteditor/plugins/stateinspector.min.js +2 -2
- package/richtexteditor/plugins/structured-content-bridge.js +56 -24
- package/richtexteditor/plugins/structured-content-bridge.min.js +7 -7
- package/richtexteditor/plugins/tableofcontents.js +5 -2
- package/richtexteditor/plugins/tableofcontents.min.js +1 -1
- package/richtexteditor/plugins/tabletools.js +388 -3
- package/richtexteditor/plugins/tabletools.min.js +1 -1
- package/richtexteditor/plugins/textdirection.js +18 -5
- package/richtexteditor/plugins/textdirection.min.js +1 -1
- package/richtexteditor/plugins/textpartlanguage.js +161 -32
- package/richtexteditor/plugins/textpartlanguage.min.js +1 -1
- package/richtexteditor/plugins/todolist.js +90 -6
- package/richtexteditor/plugins/todolist.min.js +1 -1
- package/richtexteditor/plugins/trackedchanges.js +1812 -25
- package/richtexteditor/plugins/trackedchanges.min.js +5 -3
- package/richtexteditor/plugins/wordexport.js +2 -2
- package/richtexteditor/plugins/yjscollab.js +9 -4
- package/richtexteditor/plugins/yjscollab.min.js +1 -1
- package/richtexteditor/plugins/zz_richtextbox_dialog_style.js +22 -13
- package/richtexteditor/plugins/zz_richtextbox_dialog_style.min.js +1 -1
- package/richtexteditor/rte-config.js +130 -50
- package/richtexteditor/rte-headless.js +19 -4
- package/richtexteditor/rte-webcomponent.js +89 -16
- package/richtexteditor/rte.js +132 -52
- package/scripts/build-angular.mjs +97 -0
- package/svelte/index.d.ts +2 -1
- package/svelte/index.js +80 -9
- package/vue/index.js +130 -34
- package/angular/component.ts +0 -297
- package/angular/index.ts +0 -24
|
@@ -14,20 +14,41 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
14
14
|
var refreshTimer = 0;
|
|
15
15
|
var lastResult = null;
|
|
16
16
|
var selectedIssueIndex = -1;
|
|
17
|
+
var destroyed = false;
|
|
17
18
|
|
|
18
19
|
obj.PluginName = "AccessibilityChecker";
|
|
19
20
|
|
|
21
|
+
// 2026-10-18 the panel's words - its chrome, every rule's message and the repairs - read config.text_ keys with
|
|
22
|
+
// English as the fallback ({0} / {1} are filled in). The rule codes stay as identifiers.
|
|
23
|
+
// tools/i18n/a11y-checker-language.mjs
|
|
24
|
+
function aT(key, en, a, b) {
|
|
25
|
+
var s = (config && config["text_" + key]) || en;
|
|
26
|
+
if (a !== undefined) s = s.split("{0}").join(String(a));
|
|
27
|
+
if (b !== undefined) s = s.split("{1}").join(String(b));
|
|
28
|
+
return s;
|
|
29
|
+
}
|
|
30
|
+
|
|
20
31
|
obj.InitConfig = function (argconfig) {
|
|
21
32
|
config = argconfig;
|
|
22
33
|
if (config.accessibilityCheckerEnabled === false) return;
|
|
23
34
|
|
|
24
35
|
if (typeof config.accessibilityCheckerAutoOpen !== "boolean") config.accessibilityCheckerAutoOpen = false;
|
|
25
|
-
config.accessibilityCheckerTitle = config.accessibilityCheckerTitle || "Accessibility";
|
|
26
|
-
config.accessibilityCheckerHint = config.accessibilityCheckerHint || "Review heading structure, image alt text, and table headers without leaving the editor.";
|
|
27
|
-
config.accessibilityCheckerEmptyText = config.accessibilityCheckerEmptyText || "No accessibility issues found in the current document.";
|
|
36
|
+
config.accessibilityCheckerTitle = config.accessibilityCheckerTitle || config.text_a11yc_title || "Accessibility";
|
|
37
|
+
config.accessibilityCheckerHint = config.accessibilityCheckerHint || config.text_a11yc_hint || "Review heading structure, image alt text, and table headers without leaving the editor.";
|
|
38
|
+
config.accessibilityCheckerEmptyText = config.accessibilityCheckerEmptyText || config.text_a11yc_empty || "No accessibility issues found in the current document.";
|
|
28
39
|
|
|
29
40
|
appendToolbarCommand("toolbar_default", "#{accessibilitychecker}");
|
|
30
41
|
appendToolbarCommand("toolbar_full", "#{accessibilitychecker}");
|
|
42
|
+
// 2026-10-03 Build the shell NOW, before the editor creates its iframe. ensureShell() moves the
|
|
43
|
+
// editor container; done in InitEditor it moved the live iframe, which then gets a fresh
|
|
44
|
+
// document - and every plugin that had already bound listeners to the old one went deaf (the
|
|
45
|
+
// slash menu, @mentions and emoji autocomplete, until they moved to editor.bindDocumentEvent;
|
|
46
|
+
// customer plugins binding the document directly still were). Nothing is loaded yet here.
|
|
47
|
+
// InitEditor's own ensureShell() call finds the shell and returns it.
|
|
48
|
+
// tools/keyboard/plugin-listeners.mjs
|
|
49
|
+
if (config.accessibilityCheckerEnabled !== false && config.container && config.container.parentNode) {
|
|
50
|
+
try { ensureShell(); } catch (e) { /* InitEditor builds it */ }
|
|
51
|
+
}
|
|
31
52
|
};
|
|
32
53
|
|
|
33
54
|
obj.InitEditor = function (argeditor) {
|
|
@@ -59,6 +80,14 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
59
80
|
editor.attachEvent("change", function () {
|
|
60
81
|
scheduleRefresh();
|
|
61
82
|
});
|
|
83
|
+
// 2026-10-18 setHTMLCode fires no "change": a document loaded or replaced that way kept the previous audit
|
|
84
|
+
// ("Ready to publish" over a document with six issues). tools/a11y/checker-opens-fresh.mjs
|
|
85
|
+
try { editor.attachEvent("aftersethtml", function () { scheduleRefresh(); }); } catch (e) { /* older cores */ }
|
|
86
|
+
editor.attachEvent("destroy", function () {
|
|
87
|
+
destroyed = true;
|
|
88
|
+
clearTimeout(refreshTimer);
|
|
89
|
+
refreshTimer = 0;
|
|
90
|
+
});
|
|
62
91
|
|
|
63
92
|
ensureShell();
|
|
64
93
|
runAudit();
|
|
@@ -80,7 +109,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
80
109
|
".rte-a11y-shell{display:flex;align-items:stretch;gap:10px;}",
|
|
81
110
|
".rte-a11y-shell>.rte-a11y-host{flex:1 1 auto;min-width:0;}",
|
|
82
111
|
"/* 2026-07-12 accessibility panel precision */",
|
|
83
|
-
".rte-a11y-panel{display:none;flex:0 0 310px;min-width:270px;max-width:min(360px,32vw);border:1px solid rgba(148,163,184,.22);border-radius:8px;background:linear-gradient(180deg,rgba(255,255,255,.98),rgba(248,251,255,.96));box-shadow:0 14px 32px rgba(29,78,216,.1),0 2px 8px rgba(15,23,42,.06);overflow:hidden;color:#172033;font-family:Aptos,'Segoe UI',sans-serif;}",
|
|
112
|
+
".rte-a11y-panel{display:none;flex:0 0 310px;min-width:270px;max-width:min(360px,32vw);border:1px solid rgba(148,163,184,.22);border-radius:8px;background:linear-gradient(180deg,rgba(255,255,255,.98),rgba(248,251,255,.96));box-shadow:0 14px 32px rgba(29,78,216,.1),0 2px 8px rgba(15,23,42,.06);overflow:hidden;color:#172033;font-family:Aptos,'Segoe UI',sans-serif;}.rte-a11y-locked-note{margin:6px 10px;padding:6px 8px;border:1px solid #e3b341;border-radius:6px;background:#fff8e1;color:#5c3d00;font-size:12px;}",
|
|
84
113
|
".rte-a11y-shell.is-open>.rte-a11y-panel{display:flex;flex-direction:column;}",
|
|
85
114
|
".rte-a11y-header{padding:12px 12px 9px 14px;border-bottom:1px solid rgba(148,163,184,.16);background:rgba(255,255,255,.72);}",
|
|
86
115
|
".rte-a11y-kicker{font-size:10px;line-height:1.3;letter-spacing:.08em;text-transform:uppercase;color:#52657e;font-weight:850;}",
|
|
@@ -113,6 +142,17 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
113
142
|
".rte-a11y-button{appearance:none;border:1px solid #1d67ba;border-radius:7px;background:#1d67ba;color:#fff;padding:8px 12px;font-size:12px;font-weight:750;cursor:pointer;box-shadow:0 6px 14px rgba(29,103,186,.16);}",
|
|
114
143
|
".rte-a11y-button-secondary{background:#fff;color:#315277;border-color:rgba(100,116,139,.18);box-shadow:none;}",
|
|
115
144
|
".rte-a11y-button:hover,.rte-a11y-button:focus-visible{transform:translateY(-1px);}",
|
|
145
|
+
".rte-a11y-panel.rte-a11y-dark{background:linear-gradient(180deg,#1e293b,#0f172a);border-color:#334155;color:#e2e8f0;box-shadow:0 18px 40px rgba(0,0,0,.45);}",
|
|
146
|
+
".rte-a11y-dark .rte-a11y-header,.rte-a11y-dark .rte-a11y-toolbar{background:rgba(15,23,42,.6);border-color:#334155;}",
|
|
147
|
+
".rte-a11y-dark .rte-a11y-kicker,.rte-a11y-dark .rte-a11y-copy,.rte-a11y-dark .rte-a11y-count,.rte-a11y-dark .rte-a11y-detail-copy,.rte-a11y-dark .rte-a11y-label{color:#94a3b8;}",
|
|
148
|
+
".rte-a11y-dark .rte-a11y-code{color:#a5b4c8;}",
|
|
149
|
+
".rte-a11y-dark .rte-a11y-title,.rte-a11y-dark .rte-a11y-message,.rte-a11y-dark .rte-a11y-detail-title{color:#f1f5f9;}",
|
|
150
|
+
".rte-a11y-dark .rte-a11y-item,.rte-a11y-dark .rte-a11y-detail,.rte-a11y-dark .rte-a11y-empty{background:rgba(15,23,42,.72);border-color:#334155;color:#e2e8f0;}",
|
|
151
|
+
".rte-a11y-dark .rte-a11y-link,.rte-a11y-dark .rte-a11y-button-secondary{background:#0f172a;color:#bfdbfe;border-color:#475569;}",
|
|
152
|
+
".rte-a11y-dark .rte-a11y-input{background:#0f172a;color:#e2e8f0;border-color:#475569;}",
|
|
153
|
+
".rte-a11y-dark .rte-a11y-badge-warning{background:#422006;color:#fde68a;border-color:#92400e;}",
|
|
154
|
+
".rte-a11y-dark .rte-a11y-badge-error{background:#4c0519;color:#fecdd3;border-color:#9f1239;}",
|
|
155
|
+
".rte-a11y-dark .rte-a11y-locked-note{background:#422006;color:#fde68a;border-color:#92400e;}",
|
|
116
156
|
".rte-a11y-empty{padding:14px;border-radius:8px;background:#fff;color:#52657e;font-size:13px;font-weight:700;line-height:1.55;border:1px dashed rgba(148,163,184,.34);}",
|
|
117
157
|
"@media (max-width: 1420px){.rte-a11y-shell{display:block;}.rte-a11y-panel{margin-top:12px;max-width:none;width:100%;}.rte-a11y-body{max-height:360px;}}"
|
|
118
158
|
].join("");
|
|
@@ -145,16 +185,22 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
145
185
|
panel.className = "rte-a11y-panel";
|
|
146
186
|
panel.setAttribute("aria-label", config.accessibilityCheckerTitle);
|
|
147
187
|
panel.setAttribute("role", "complementary");
|
|
188
|
+
// 2026-10-17 drag / arrow-key resizable (tools/ui/panel-resize.mjs)
|
|
189
|
+
setTimeout(function () { if (editor && typeof editor.makePanelResizable === "function") editor.makePanelResizable(panel, { label: aT("a11yc_resize", "Resize accessibility panel") }); }, 0);
|
|
148
190
|
|
|
149
191
|
var header = hostDoc.createElement("div");
|
|
150
192
|
header.className = "rte-a11y-header";
|
|
151
|
-
header.innerHTML = '<div class="rte-a11y-kicker"
|
|
193
|
+
header.innerHTML = '<div class="rte-a11y-kicker"></div><div class="rte-a11y-title"></div><div class="rte-a11y-copy"></div>';
|
|
194
|
+
header.querySelector(".rte-a11y-kicker").innerText = aT("a11yc_title", "Accessibility");
|
|
152
195
|
header.querySelector(".rte-a11y-title").innerText = config.accessibilityCheckerTitle;
|
|
153
196
|
header.querySelector(".rte-a11y-copy").innerText = config.accessibilityCheckerHint;
|
|
154
197
|
|
|
155
198
|
var toolbar = hostDoc.createElement("div");
|
|
156
199
|
toolbar.className = "rte-a11y-toolbar";
|
|
157
|
-
toolbar.innerHTML = '<div class="rte-a11y-count" data-rte-a11y-count="1"></div><div class="rte-a11y-actions"><button type="button" class="rte-a11y-link" data-rte-a11y-refresh="1"
|
|
200
|
+
toolbar.innerHTML = '<div class="rte-a11y-count" data-rte-a11y-count="1"></div><div class="rte-a11y-actions"><button type="button" class="rte-a11y-link" data-rte-a11y-refresh="1"></button><button type="button" class="rte-a11y-link" data-rte-a11y-close="1"></button></div>';
|
|
201
|
+
toolbar.querySelector("[data-rte-a11y-refresh]").innerText = aT("a11yc_refresh", "Refresh");
|
|
202
|
+
toolbar.querySelector("[data-rte-a11y-close]").innerText = aT("a11yc_hide", "Hide");
|
|
203
|
+
toolbar.querySelector("[data-rte-a11y-close]").setAttribute("aria-label", aT("a11yc_hidearia", "Hide accessibility checker"));
|
|
158
204
|
toolbar.querySelector("[data-rte-a11y-refresh]").onclick = function () { runAudit(); };
|
|
159
205
|
toolbar.querySelector("[data-rte-a11y-close]").onclick = function () { closePanel(); };
|
|
160
206
|
|
|
@@ -183,10 +229,20 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
183
229
|
else openPanel();
|
|
184
230
|
}
|
|
185
231
|
|
|
232
|
+
// 2026-10-17 the panel sits beside the editor's element, so rte-dark could not reach it: mirrored (and
|
|
233
|
+
// rte-auto-dark under a dark system setting). tools/a11y/checker-panel-dark.mjs
|
|
234
|
+
function editorIsDark() {
|
|
235
|
+
try {
|
|
236
|
+
var k = editor.container && editor.container.classList;
|
|
237
|
+
return !!k && (k.contains("rte-dark") || (k.contains("rte-auto-dark") && !k.contains("rte-light") && !!(window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches)));
|
|
238
|
+
} catch (x) { return false; }
|
|
239
|
+
}
|
|
186
240
|
function openPanel() {
|
|
187
241
|
ensureShell();
|
|
242
|
+
if (panel && panel.classList) { if (editorIsDark()) panel.classList.add("rte-a11y-dark"); else panel.classList.remove("rte-a11y-dark"); }
|
|
188
243
|
shell.classList.add("is-open");
|
|
189
|
-
|
|
244
|
+
// audit what is shown now, not what was there at the last change event
|
|
245
|
+
runAudit();
|
|
190
246
|
notifyResize();
|
|
191
247
|
}
|
|
192
248
|
|
|
@@ -217,6 +273,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
217
273
|
}
|
|
218
274
|
|
|
219
275
|
function scheduleRefresh() {
|
|
276
|
+
if (destroyed) return;
|
|
220
277
|
clearTimeout(refreshTimer);
|
|
221
278
|
refreshTimer = setTimeout(function () {
|
|
222
279
|
runAudit();
|
|
@@ -224,6 +281,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
224
281
|
}
|
|
225
282
|
|
|
226
283
|
function runAudit() {
|
|
284
|
+
if (destroyed) return lastResult || { document: null, issues: [], valid: true, source: "dom" };
|
|
227
285
|
ensureShell();
|
|
228
286
|
var ctor = window.RichTextEditor;
|
|
229
287
|
var documentModel = editor.getJSON ? editor.getJSON() : null;
|
|
@@ -236,7 +294,30 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
236
294
|
return lastResult;
|
|
237
295
|
}
|
|
238
296
|
|
|
297
|
+
// 2026-10-17 a locked document stays locked: the fixes edit the DOM directly (neither insertHTML's nor
|
|
298
|
+
// setHTMLCode's lock reaches them) and the checker's button stays enabled in comments-only mode. A repair is
|
|
299
|
+
// refused while the editor is read-only or comments-only, and the panel says so. tools/a11y/checker-repair-locked.mjs
|
|
300
|
+
function checkerLocked() {
|
|
301
|
+
try { return !!((editor.getReadOnly && editor.getReadOnly()) || (editor.getCommentsOnly && editor.getCommentsOnly())); } catch (e) { return false; }
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function showLockedNote() {
|
|
305
|
+
if (!panel) return;
|
|
306
|
+
var note = panel.querySelector(".rte-a11y-locked-note");
|
|
307
|
+
if (!note) {
|
|
308
|
+
note = panel.ownerDocument.createElement("div");
|
|
309
|
+
note.className = "rte-a11y-locked-note";
|
|
310
|
+
note.setAttribute("role", "status");
|
|
311
|
+
panel.insertBefore(note, panel.children[1] || null);
|
|
312
|
+
}
|
|
313
|
+
note.textContent = "";
|
|
314
|
+
var text = (config && config.text_a11ycheckerlocked) || "This document is read-only, so the fix was not applied.";
|
|
315
|
+
setTimeout(function () { note.textContent = text; }, 30);
|
|
316
|
+
note.textContent = text;
|
|
317
|
+
}
|
|
318
|
+
|
|
239
319
|
function repairIssue(issueIndex, options) {
|
|
320
|
+
if (checkerLocked()) { showLockedNote(); return lastResult || runAudit(); }
|
|
240
321
|
if (!lastResult || !lastResult.issues || !lastResult.issues[issueIndex]) return runAudit();
|
|
241
322
|
var ctor = window.RichTextEditor;
|
|
242
323
|
// An issue carrying `_target` came from the DOM audit and is addressed
|
|
@@ -289,16 +370,16 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
289
370
|
// document, and the reverse). We flag only what we can be certain of —
|
|
290
371
|
// a checker that cries wolf gets switched off.
|
|
291
372
|
var SCRIPTS = [
|
|
292
|
-
{ name: "Hebrew", lang: "he", re: /[
|
|
293
|
-
{ name: "Arabic", lang: "ar", re: /[
|
|
294
|
-
{ name: "Greek", lang: "el", re: /[
|
|
295
|
-
{ name: "Cyrillic", lang: "ru", re: /[
|
|
296
|
-
{ name: "Han", lang: "zh", re: /[
|
|
297
|
-
{ name: "Kana", lang: "ja", re: /[
|
|
298
|
-
{ name: "Hangul", lang: "ko", re: /[
|
|
299
|
-
{ name: "Devanagari", lang: "hi", re: /[
|
|
300
|
-
{ name: "Thai", lang: "th", re: /[
|
|
301
|
-
{ name: "Latin", lang: "en", re: /[A-Za-z
|
|
373
|
+
{ name: "Hebrew", lang: "he", re: /[\u0590-\u05ff]/g },
|
|
374
|
+
{ name: "Arabic", lang: "ar", re: /[\u0600-\u06ff\u0750-\u077f\ufb50-\ufdff\ufe70-\ufeff]/g },
|
|
375
|
+
{ name: "Greek", lang: "el", re: /[\u0370-\u03ff\u1f00-\u1fff]/g },
|
|
376
|
+
{ name: "Cyrillic", lang: "ru", re: /[\u0400-\u04ff]/g },
|
|
377
|
+
{ name: "Han", lang: "zh", re: /[\u3400-\u4dbf\u4e00-\u9fff]/g },
|
|
378
|
+
{ name: "Kana", lang: "ja", re: /[\u3040-\u309f\u30a0-\u30ff]/g },
|
|
379
|
+
{ name: "Hangul", lang: "ko", re: /[\uac00-\ud7af\u1100-\u11ff]/g },
|
|
380
|
+
{ name: "Devanagari", lang: "hi", re: /[\u0900-\u097f]/g },
|
|
381
|
+
{ name: "Thai", lang: "th", re: /[\u0e00-\u0e7f]/g },
|
|
382
|
+
{ name: "Latin", lang: "en", re: /[A-Za-z\xc0-\u024f]/g }
|
|
302
383
|
];
|
|
303
384
|
|
|
304
385
|
// Below this, a "run" is more likely a stray glyph, a maths symbol or a
|
|
@@ -362,8 +443,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
362
443
|
issues.push({
|
|
363
444
|
code: "language-of-parts",
|
|
364
445
|
severity: "warning",
|
|
365
|
-
message:
|
|
366
|
-
"language so assistive technology pronounces it correctly (WCAG 3.1.2).",
|
|
446
|
+
message: aT("a11yc_m_lang", "{0} text is not marked with a language. Select the phrase and set its language so assistive technology pronounces it correctly (WCAG 3.1.2).", aT("a11yc_script_" + script.lang, script.name)),
|
|
367
447
|
path: path + ".lang",
|
|
368
448
|
_target: block,
|
|
369
449
|
_lang: script.lang,
|
|
@@ -398,7 +478,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
398
478
|
issues.push({
|
|
399
479
|
code: "heading-empty",
|
|
400
480
|
severity: "warning",
|
|
401
|
-
message: "Heading is empty. Add a concise label or remove the heading.",
|
|
481
|
+
message: aT("a11yc_m_hempty", "Heading is empty. Add a concise label or remove the heading."),
|
|
402
482
|
path: path,
|
|
403
483
|
_target: node
|
|
404
484
|
});
|
|
@@ -407,7 +487,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
407
487
|
issues.push({
|
|
408
488
|
code: "heading-level-skip",
|
|
409
489
|
severity: "warning",
|
|
410
|
-
message: "Heading jumps from H
|
|
490
|
+
message: aT("a11yc_m_hskip", "Heading jumps from H{0} to H{1}. Use the next heading level for a clearer outline.", previousHeadingLevel, level),
|
|
411
491
|
path: path + ".attrs.level",
|
|
412
492
|
_target: node
|
|
413
493
|
});
|
|
@@ -428,7 +508,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
428
508
|
issues.push({
|
|
429
509
|
code: "image-missing-alt",
|
|
430
510
|
severity: "error",
|
|
431
|
-
message: "Image is missing alt text. Describe the image or mark it decorative in your content workflow.",
|
|
511
|
+
message: aT("a11yc_m_alt", "Image is missing alt text. Describe the image or mark it decorative in your content workflow."),
|
|
432
512
|
path: path + ".image[" + imageIndex + "]",
|
|
433
513
|
_target: image
|
|
434
514
|
});
|
|
@@ -452,7 +532,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
452
532
|
issues.push({
|
|
453
533
|
code: "link-empty",
|
|
454
534
|
severity: "error",
|
|
455
|
-
message: "Link has no text, so it is announced only as \"link\". Add link text, an aria-label, or alt text on the image inside it.",
|
|
535
|
+
message: aT("a11yc_m_linkempty", "Link has no text, so it is announced only as \"link\". Add link text, an aria-label, or alt text on the image inside it."),
|
|
456
536
|
path: path + ".link[" + linkIndex + "]",
|
|
457
537
|
_target: link
|
|
458
538
|
});
|
|
@@ -477,7 +557,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
477
557
|
issues.push({
|
|
478
558
|
code: "layout-table-semantics",
|
|
479
559
|
severity: "error",
|
|
480
|
-
message: "Layout table still carries data-table markup (header cells, scope or a caption). A screen reader will announce relationships this table does not have.",
|
|
560
|
+
message: aT("a11yc_m_layouttable", "Layout table still carries data-table markup (header cells, scope or a caption). A screen reader will announce relationships this table does not have."),
|
|
481
561
|
path: path + ".table[" + tableIndex + "]",
|
|
482
562
|
_target: table
|
|
483
563
|
});
|
|
@@ -487,7 +567,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
487
567
|
issues.push({
|
|
488
568
|
code: "table-missing-header",
|
|
489
569
|
severity: "warning",
|
|
490
|
-
message: "Table has no header cells. Promote the first row to headers, or mark it as a layout table when it only positions content.",
|
|
570
|
+
message: aT("a11yc_m_noheader", "Table has no header cells. Promote the first row to headers, or mark it as a layout table when it only positions content."),
|
|
491
571
|
path: path + ".table[" + tableIndex + "]",
|
|
492
572
|
_target: table
|
|
493
573
|
});
|
|
@@ -521,7 +601,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
521
601
|
issues.push({
|
|
522
602
|
code: "table-header-empty",
|
|
523
603
|
severity: "error",
|
|
524
|
-
message: "Header cell is empty, so the column or row it labels is announced with no name. Give it text, or make it a normal cell if it labels nothing.",
|
|
604
|
+
message: aT("a11yc_m_headerempty", "Header cell is empty, so the column or row it labels is announced with no name. Give it text, or make it a normal cell if it labels nothing."),
|
|
525
605
|
path: path + ".table[" + tableIndex + "].th[" + headerIndex + "]",
|
|
526
606
|
_target: headerCell
|
|
527
607
|
});
|
|
@@ -562,10 +642,9 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
562
642
|
issues.push({
|
|
563
643
|
code: "contrast-insufficient",
|
|
564
644
|
severity: "error",
|
|
565
|
-
message: "
|
|
566
|
-
(bg.assumed ? "an assumed white background" : "its background") +
|
|
567
|
-
", below the " +
|
|
568
|
-
(large ? "large text" : "body text") + " (WCAG 1.4.3).",
|
|
645
|
+
message: aT("a11yc_m_contrast_" + (bg.assumed ? "w" : "b") + (large ? "l" : "b"),
|
|
646
|
+
"Text contrast is {0}:1 against " + (bg.assumed ? "an assumed white background" : "its background") +
|
|
647
|
+
", below the {1}:1 needed for " + (large ? "large text" : "body text") + " (WCAG 1.4.3).", ratio.toFixed(2), need),
|
|
569
648
|
path: path + ".contrast[" + cIndex + "]",
|
|
570
649
|
_target: cEl
|
|
571
650
|
});
|
|
@@ -596,7 +675,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
596
675
|
issues.push({
|
|
597
676
|
code: "blockquote-as-indent",
|
|
598
677
|
severity: "warning",
|
|
599
|
-
message: "This looks like indentation, not a quotation: a <blockquote> with a left margin and no quote styling. Screen readers announce it as a quotation. Use the indent button instead.",
|
|
678
|
+
message: aT("a11yc_m_bqindent", "This looks like indentation, not a quotation: a <blockquote> with a left margin and no quote styling. Screen readers announce it as a quotation. Use the indent button instead."),
|
|
600
679
|
path: path + ".blockquote[" + qIndex + "]",
|
|
601
680
|
_target: q
|
|
602
681
|
});
|
|
@@ -616,7 +695,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
616
695
|
issues.push({
|
|
617
696
|
code: "link-ambiguous-text",
|
|
618
697
|
severity: "warning",
|
|
619
|
-
message: "Link text \"
|
|
698
|
+
message: aT("a11yc_m_ambig", "Link text \"{0}\" does not say where it goes. Out of context a screen reader announces only this text.", aName),
|
|
620
699
|
path: path + ".link[" + aIndex + "]",
|
|
621
700
|
_target: aEl
|
|
622
701
|
});
|
|
@@ -645,7 +724,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
645
724
|
issues.push({
|
|
646
725
|
code: "duplicate-id",
|
|
647
726
|
severity: "error",
|
|
648
|
-
message: "id \"
|
|
727
|
+
message: aT("a11yc_m_dupid", "id \"{0}\" is used more than once. Any aria-labelledby, aria-describedby or label pointing at it resolves to only the first element.", dupId),
|
|
649
728
|
path: "document.id[" + dupIndex + "]",
|
|
650
729
|
_target: allWithIds[dupIndex]
|
|
651
730
|
});
|
|
@@ -677,7 +756,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
677
756
|
issues.push({
|
|
678
757
|
code: "paragraph-as-heading",
|
|
679
758
|
severity: "warning",
|
|
680
|
-
message: "
|
|
759
|
+
message: aT("a11yc_m_pheading", "\"{0}\" looks like a heading (bold, large, on its own line) but is a paragraph. Screen-reader users cannot jump to it. Use a heading level instead.", pText),
|
|
681
760
|
path: "document.p[" + pi + "]",
|
|
682
761
|
_target: pEl
|
|
683
762
|
});
|
|
@@ -689,7 +768,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
689
768
|
if (!el || String(el.nodeName).toLowerCase() !== "p") return null;
|
|
690
769
|
var t = getText(el);
|
|
691
770
|
if (/^\d{1,3}[.)]\s+\S/.test(t)) return "ol";
|
|
692
|
-
if (/^[
|
|
771
|
+
if (/^[-*\u2022\u25aa\u25cf]\s+\S/.test(t)) return "ul";
|
|
693
772
|
return null;
|
|
694
773
|
};
|
|
695
774
|
var kids = editable.children;
|
|
@@ -702,7 +781,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
702
781
|
issues.push({
|
|
703
782
|
code: "fake-list",
|
|
704
783
|
severity: "warning",
|
|
705
|
-
message:
|
|
784
|
+
message: aT("a11yc_m_fakelist_" + (kind === "ol" ? "ol" : "ul"), "{0} paragraphs are typed as a " + (kind === "ol" ? "numbered" : "bulleted") + " list. Screen readers do not announce them as a list or say how many items it has. Use the list button instead.", run),
|
|
706
785
|
path: "content[" + ki + "]",
|
|
707
786
|
_target: kids[ki]
|
|
708
787
|
});
|
|
@@ -720,7 +799,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
720
799
|
issues.push({
|
|
721
800
|
code: "image-alt-filename",
|
|
722
801
|
severity: "error",
|
|
723
|
-
message: "Alt text \"
|
|
802
|
+
message: aT("a11yc_m_altfile", "Alt text \"{0}\" is a file name, which describes nothing. Say what the image shows.", alt),
|
|
724
803
|
path: "document.img[" + ii + "]",
|
|
725
804
|
_target: imgs[ii]
|
|
726
805
|
});
|
|
@@ -728,7 +807,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
728
807
|
issues.push({
|
|
729
808
|
code: "image-alt-too-long",
|
|
730
809
|
severity: "warning",
|
|
731
|
-
message: "Alt text is
|
|
810
|
+
message: aT("a11yc_m_altlong", "Alt text is {0} characters. Keep it to a short description; put longer detail in the text or a caption.", alt.length),
|
|
732
811
|
path: "document.img[" + ii + "]",
|
|
733
812
|
_target: imgs[ii]
|
|
734
813
|
});
|
|
@@ -750,7 +829,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
750
829
|
issues.push({
|
|
751
830
|
code: "link-adjacent-duplicate",
|
|
752
831
|
severity: "warning",
|
|
753
|
-
message: "Two links next to each other go to the same place, so it is announced twice. Combine them into one link.",
|
|
832
|
+
message: aT("a11yc_m_adjlinks", "Two links next to each other go to the same place, so it is announced twice. Combine them into one link."),
|
|
754
833
|
path: "document.link[" + li + "]",
|
|
755
834
|
_target: cur
|
|
756
835
|
});
|
|
@@ -766,7 +845,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
766
845
|
issues.push({
|
|
767
846
|
code: "table-missing-caption",
|
|
768
847
|
severity: "warning",
|
|
769
|
-
message: "Data table has no caption. A short caption lets screen-reader users know what the table is before they enter it.",
|
|
848
|
+
message: aT("a11yc_m_nocaption", "Data table has no caption. A short caption lets screen-reader users know what the table is before they enter it."),
|
|
770
849
|
path: "document.table[" + ti + "]",
|
|
771
850
|
_target: tb
|
|
772
851
|
});
|
|
@@ -779,6 +858,13 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
779
858
|
function repairDomIssue(issue, options) {
|
|
780
859
|
if (!issue || !issue._target) return runAudit();
|
|
781
860
|
var target = issue._target;
|
|
861
|
+
// 2026-10-05 a target edited away since the audit: re-audit (the repair threw half-way, leaving the
|
|
862
|
+
// undo step open). tools/review/plugin-review-20261005.mjs
|
|
863
|
+
var liveRoot = editor.getEditable ? editor.getEditable() : null;
|
|
864
|
+
if (!target.parentNode || (liveRoot && !liveRoot.contains(target))) return runAudit();
|
|
865
|
+
// 2026-10-04 every repair is one undo step: the state before it is recorded first, the result after.
|
|
866
|
+
// Undo skipped a repair entirely. tools/a11y/checker-action-repairs.mjs
|
|
867
|
+
commitUndo();
|
|
782
868
|
|
|
783
869
|
if (issue.code === "image-missing-alt") {
|
|
784
870
|
target.setAttribute("alt", String(options.altText || "").replace(/^\s+|\s+$/g, ""));
|
|
@@ -815,11 +901,18 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
815
901
|
target.parentNode.replaceChild(cell, target);
|
|
816
902
|
}
|
|
817
903
|
}
|
|
818
|
-
else if (issue.code === "link-empty") {
|
|
904
|
+
else if (issue.code === "link-empty" || issue.code === "link-ambiguous-text") {
|
|
819
905
|
// Prefer real text: an aria-label is invisible, so a sighted editor
|
|
820
906
|
// cannot see what the link says and it drifts out of date.
|
|
821
907
|
var linkText = String((options && options.linkText) || "").replace(/^\s+|\s+$/g, "");
|
|
822
|
-
|
|
908
|
+
// 2026-10-05 only the link's TEXT is replaced: an image or formatting inside it stays (textContent
|
|
909
|
+
// wiped them). tools/review/plugin-review-20261005.mjs
|
|
910
|
+
if (linkText) {
|
|
911
|
+
var lw = target.ownerDocument.createTreeWalker(target, 4, null, false), ltn, ltexts = [];
|
|
912
|
+
while ((ltn = lw.nextNode())) if (/\S/.test(ltn.data)) ltexts.push(ltn);
|
|
913
|
+
if (!ltexts.length) { if (target.firstChild && target.firstChild.nodeType === 1) target.appendChild(target.ownerDocument.createTextNode(" ")); target.appendChild(target.ownerDocument.createTextNode(linkText)); }
|
|
914
|
+
else { ltexts[0].data = linkText; for (var lti = 1; lti < ltexts.length; lti++) ltexts[lti].data = ""; }
|
|
915
|
+
}
|
|
823
916
|
else {
|
|
824
917
|
var linkLabel = String((options && options.linkLabel) || "").replace(/^\s+|\s+$/g, "");
|
|
825
918
|
if (linkLabel) target.setAttribute("aria-label", linkLabel);
|
|
@@ -829,6 +922,34 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
829
922
|
markLanguageRuns(target, options && options.lang ? options.lang : issue._lang, issue._script);
|
|
830
923
|
}
|
|
831
924
|
|
|
925
|
+
else if (issue.code === "duplicate-id") {
|
|
926
|
+
// 2026-10-04 the first element keeps the id (links and labels already resolve to it); the repeat
|
|
927
|
+
// loses it. tools/a11y/checker-action-repairs.mjs
|
|
928
|
+
target.removeAttribute("id");
|
|
929
|
+
}
|
|
930
|
+
else if (issue.code === "blockquote-as-indent") {
|
|
931
|
+
// the quote goes; its left margin moves onto each block it held (bare text becomes a paragraph)
|
|
932
|
+
var ml = target.style.marginLeft, qd = target.ownerDocument, qp = target.parentNode, run = null;
|
|
933
|
+
while (target.firstChild) {
|
|
934
|
+
var qc = target.firstChild;
|
|
935
|
+
// 2026-10-05 every block tag (a rule or a section was wrapped in a <p>), margins ADD (a 20px
|
|
936
|
+
// paragraph in a 40px quote is 60px), and a space between inline words stays (it was dropped:
|
|
937
|
+
// "Helloworld"). tools/review/plugin-review-20261005.mjs
|
|
938
|
+
var isBlock = qc.nodeType === 1 && /^(P|DIV|H[1-6]|UL|OL|DL|PRE|TABLE|BLOCKQUOTE|FIGURE|ADDRESS|HR|SECTION|ARTICLE|ASIDE|NAV|HEADER|FOOTER|DETAILS|FIELDSET|FORM)$/.test(qc.nodeName);
|
|
939
|
+
if (isBlock) {
|
|
940
|
+
run = null;
|
|
941
|
+
if (ml && qc.nodeName !== "HR") {
|
|
942
|
+
var own = qc.style.marginLeft, ownPx = /^([\d.]+)px$/.exec(own || ""), qPx = /^([\d.]+)px$/.exec(ml);
|
|
943
|
+
qc.style.marginLeft = !own ? ml : (ownPx && qPx ? (parseFloat(ownPx[1]) + parseFloat(qPx[1])) + "px" : "calc(" + ml + " + " + own + ")");
|
|
944
|
+
}
|
|
945
|
+
qp.insertBefore(qc, target); continue;
|
|
946
|
+
}
|
|
947
|
+
if (qc.nodeType === 3 && !/\S/.test(qc.data) && !run) { target.removeChild(qc); continue; }
|
|
948
|
+
if (!run) { run = qd.createElement("p"); if (ml) run.style.marginLeft = ml; qp.insertBefore(run, target); }
|
|
949
|
+
run.appendChild(qc);
|
|
950
|
+
}
|
|
951
|
+
qp.removeChild(target);
|
|
952
|
+
}
|
|
832
953
|
else if (issue.code === "paragraph-as-heading") {
|
|
833
954
|
var lvl = Math.max(1, Math.min(6, parseInt(options && options.targetLevel, 10) || 2));
|
|
834
955
|
var h = target.ownerDocument.createElement("h" + lvl);
|
|
@@ -846,7 +967,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
846
967
|
var node = target;
|
|
847
968
|
while (node && node.nodeName === "P") {
|
|
848
969
|
var t = (node.textContent || "").replace(/^\s+/, "");
|
|
849
|
-
var m = ordered ? /^\d{1,3}[.)]\s+([\s\S]*)$/.exec(t) : /^[
|
|
970
|
+
var m = ordered ? /^\d{1,3}[.)]\s+([\s\S]*)$/.exec(t) : /^[-*\u2022\u25aa\u25cf]\s+([\s\S]*)$/.exec(t);
|
|
850
971
|
if (!m) break;
|
|
851
972
|
var li = d.createElement("li");
|
|
852
973
|
li.textContent = m[1].replace(/\s+$/, "");
|
|
@@ -879,10 +1000,21 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
879
1000
|
target.setAttribute("alt", String((options && options.altText) || "").replace(/^\s+|\s+$/g, ""));
|
|
880
1001
|
}
|
|
881
1002
|
selectedIssueIndex = 0;
|
|
1003
|
+
recordUndo();
|
|
882
1004
|
scheduleEditorChange();
|
|
883
1005
|
return runAudit();
|
|
884
1006
|
}
|
|
885
1007
|
|
|
1008
|
+
// record the repaired document as its own step (notifySelectionChange saves the undo snapshot), then
|
|
1009
|
+
// seal it so the next edit starts a new one
|
|
1010
|
+
function recordUndo() {
|
|
1011
|
+
try { if (editor.recordUndo) editor.recordUndo(); else if (editor.notifySelectionChange) editor.notifySelectionChange(); if (editor.commitBookmark) editor.commitBookmark(); } catch (er) { /* ignore */ }
|
|
1012
|
+
commitUndo();
|
|
1013
|
+
}
|
|
1014
|
+
function commitUndo() {
|
|
1015
|
+
if (typeof editor.commitBookmark === "function") { try { editor.commitBookmark(); } catch (er) { /* ignore */ } }
|
|
1016
|
+
}
|
|
1017
|
+
|
|
886
1018
|
// Wrap the offending script's runs in <span lang> — the same markup
|
|
887
1019
|
// textpartlanguage.js produces, so the two features agree on one shape and
|
|
888
1020
|
// the author can retarget the result from the Language dropdown afterwards.
|
|
@@ -921,8 +1053,8 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
921
1053
|
var text = m[0];
|
|
922
1054
|
// Trim the neutrals the greedy match pulled in at the edges;
|
|
923
1055
|
// they belong to the surrounding sentence, not the phrase.
|
|
924
|
-
var lead = text.length - text.replace(/^[\s.,;:!?'"()
|
|
925
|
-
var trail = text.length - text.replace(/[\s.,;:!?'"()
|
|
1056
|
+
var lead = text.length - text.replace(/^[\s.,;:!?'"()\u2010-\u2027\xab\xbb\u201c\u201d]+/, "").length;
|
|
1057
|
+
var trail = text.length - text.replace(/[\s.,;:!?'"()\u2010-\u2027\xab\xbb\u201c\u201d]+$/, "").length;
|
|
926
1058
|
var start = m.index + lead;
|
|
927
1059
|
var end = m.index + text.length - trail;
|
|
928
1060
|
if (end - start < MIN_SCRIPT_RUN) continue;
|
|
@@ -1092,8 +1224,8 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
1092
1224
|
var count = panel.querySelector("[data-rte-a11y-count]");
|
|
1093
1225
|
if (count) {
|
|
1094
1226
|
count.innerText = issues.length
|
|
1095
|
-
? (issues.length
|
|
1096
|
-
: "Ready to publish";
|
|
1227
|
+
? (issues.length === 1 ? aT("a11yc_count1", "{0} issue to review", 1) : aT("a11yc_countn", "{0} issues to review", issues.length))
|
|
1228
|
+
: aT("a11yc_ready", "Ready to publish");
|
|
1097
1229
|
}
|
|
1098
1230
|
|
|
1099
1231
|
empty.style.display = issues.length ? "none" : "block";
|
|
@@ -1106,11 +1238,12 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
1106
1238
|
button.setAttribute("aria-selected", issueIndex === selectedIssueIndex ? "true" : "false");
|
|
1107
1239
|
button.innerHTML =
|
|
1108
1240
|
'<div class="rte-a11y-item-top">' +
|
|
1109
|
-
'<span class="rte-a11y-badge rte-a11y-badge-' + issue.severity + '"
|
|
1241
|
+
'<span class="rte-a11y-badge rte-a11y-badge-' + issue.severity + '"></span>' +
|
|
1110
1242
|
'<span class="rte-a11y-code">' + issue.code + '</span>' +
|
|
1111
1243
|
"</div>" +
|
|
1112
1244
|
'<div class="rte-a11y-message"></div>';
|
|
1113
1245
|
button.querySelector(".rte-a11y-message").innerText = issue.message;
|
|
1246
|
+
button.querySelector(".rte-a11y-badge").innerText = aT("a11yc_sev_" + issue.severity, issue.severity);
|
|
1114
1247
|
button.onclick = function () {
|
|
1115
1248
|
selectedIssueIndex = issueIndex;
|
|
1116
1249
|
focusIssue(issue);
|
|
@@ -1128,7 +1261,9 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
1128
1261
|
|
|
1129
1262
|
var issues = lastResult && lastResult.issues ? lastResult.issues : [];
|
|
1130
1263
|
if (!issues.length || selectedIssueIndex < 0 || !issues[selectedIssueIndex]) {
|
|
1131
|
-
detail.innerHTML = '<div class="rte-a11y-detail-title"
|
|
1264
|
+
detail.innerHTML = '<div class="rte-a11y-detail-title"></div><div class="rte-a11y-detail-copy"></div>';
|
|
1265
|
+
detail.querySelector(".rte-a11y-detail-title").innerText = aT("a11yc_nofixes", "No fixes pending");
|
|
1266
|
+
detail.querySelector(".rte-a11y-detail-copy").innerText = aT("a11yc_nofixeshint", "Run the checker again after major content changes to keep headings, image descriptions, and tables in shape.");
|
|
1132
1267
|
return;
|
|
1133
1268
|
}
|
|
1134
1269
|
|
|
@@ -1143,14 +1278,68 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
1143
1278
|
detail.appendChild(copy);
|
|
1144
1279
|
|
|
1145
1280
|
if (issue.code === "image-missing-alt") {
|
|
1146
|
-
renderTextRepair("Alt text", "Describe the image for screen-reader users.", function (value) {
|
|
1281
|
+
renderTextRepair(aT("a11yc_f_alt", "Alt text"), aT("a11yc_f_althint", "Describe the image for screen-reader users."), function (value) {
|
|
1147
1282
|
repairIssue(selectedIssueIndex, { altText: value });
|
|
1148
1283
|
});
|
|
1149
1284
|
return;
|
|
1150
1285
|
}
|
|
1151
1286
|
|
|
1287
|
+
// 2026-10-04 Every repair that takes text gets a field here. These repairs existed (repairDomIssue)
|
|
1288
|
+
// but the panel showed only the message for them. tools/a11y/checker-text-repairs.mjs
|
|
1289
|
+
var TEXT_REPAIRS = {
|
|
1290
|
+
"table-missing-caption": [aT("a11yc_f_caption", "Caption"), aT("a11yc_f_captionhint", "Say what the table shows, e.g. \"Sales by quarter\"."), "captionText"],
|
|
1291
|
+
"table-header-empty": [aT("a11yc_f_header", "Header text"), aT("a11yc_f_headerhint", "Name the column or row this cell labels."), "headerText"],
|
|
1292
|
+
"link-empty": [aT("a11yc_f_link", "Link text"), aT("a11yc_f_linkhint", "Say where the link goes."), "linkText"],
|
|
1293
|
+
"link-ambiguous-text": [aT("a11yc_f_link", "Link text"), aT("a11yc_f_linkambighint", "Say where the link goes, not \"click here\"."), "linkText"],
|
|
1294
|
+
"image-alt-filename": [aT("a11yc_f_alt", "Alt text"), aT("a11yc_f_altfilehint", "Describe what the image shows."), "altText"],
|
|
1295
|
+
"image-alt-too-long": [aT("a11yc_f_alt", "Alt text"), aT("a11yc_f_altlonghint", "A short description; keep detail in the text."), "altText"]
|
|
1296
|
+
};
|
|
1297
|
+
if (TEXT_REPAIRS[issue.code]) {
|
|
1298
|
+
var tr = TEXT_REPAIRS[issue.code];
|
|
1299
|
+
renderTextRepair(tr[0], tr[1], function (value) {
|
|
1300
|
+
var o = {}; o[tr[2]] = value;
|
|
1301
|
+
repairIssue(selectedIssueIndex, o);
|
|
1302
|
+
});
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// ...and every repair that needs no typing gets a button (they too showed only the message).
|
|
1307
|
+
// tools/a11y/checker-action-repairs.mjs
|
|
1308
|
+
var ACTION_REPAIRS = {
|
|
1309
|
+
"fake-list": [aT("a11yc_a_list", "Make it a list"), function () { return {}; }],
|
|
1310
|
+
"paragraph-as-heading": [aT("a11yc_a_heading", "Make it a heading"), function () {
|
|
1311
|
+
// one level below the heading before it (H2 when there is none)
|
|
1312
|
+
var before = 1, t = issue._target, all = t && t.ownerDocument ? t.ownerDocument.querySelectorAll("h1,h2,h3,h4,h5,h6") : [];
|
|
1313
|
+
for (var hi = 0; hi < all.length; hi++) if (all[hi].compareDocumentPosition(t) & 4) before = parseInt(all[hi].nodeName.charAt(1), 10);
|
|
1314
|
+
return { targetLevel: Math.min(6, before + 1) };
|
|
1315
|
+
}],
|
|
1316
|
+
"layout-table-semantics": [aT("a11yc_a_layout", "Remove table semantics"), function () { return {}; }],
|
|
1317
|
+
"link-adjacent-duplicate": [aT("a11yc_a_merge", "Merge the links"), function () { return {}; }],
|
|
1318
|
+
"duplicate-id": [aT("a11yc_a_dupid", "Remove the repeated id"), function () { return {}; }],
|
|
1319
|
+
"blockquote-as-indent": [aT("a11yc_a_indent", "Make it indentation"), function () { return {}; }]
|
|
1320
|
+
};
|
|
1321
|
+
if (ACTION_REPAIRS[issue.code]) {
|
|
1322
|
+
var ar = ACTION_REPAIRS[issue.code];
|
|
1323
|
+
var arRow = detail.ownerDocument.createElement("div");
|
|
1324
|
+
arRow.className = "rte-a11y-row";
|
|
1325
|
+
var arDo = detail.ownerDocument.createElement("button");
|
|
1326
|
+
arDo.type = "button";
|
|
1327
|
+
arDo.className = "rte-a11y-button";
|
|
1328
|
+
arDo.innerText = ar[0];
|
|
1329
|
+
arDo.onclick = function () { repairIssue(selectedIssueIndex, ar[1]()); };
|
|
1330
|
+
var arFocus = detail.ownerDocument.createElement("button");
|
|
1331
|
+
arFocus.type = "button";
|
|
1332
|
+
arFocus.className = "rte-a11y-button rte-a11y-button-secondary";
|
|
1333
|
+
arFocus.innerText = aT("a11yc_focus", "Focus issue");
|
|
1334
|
+
arFocus.onclick = function () { focusIssue(issue); };
|
|
1335
|
+
arRow.appendChild(arDo);
|
|
1336
|
+
arRow.appendChild(arFocus);
|
|
1337
|
+
detail.appendChild(arRow);
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1152
1341
|
if (issue.code === "heading-empty") {
|
|
1153
|
-
renderTextRepair("Heading text", "Add a concise heading label before publishing.", function (value) {
|
|
1342
|
+
renderTextRepair(aT("a11yc_f_heading", "Heading text"), aT("a11yc_f_headinghint", "Add a concise heading label before publishing."), function (value) {
|
|
1154
1343
|
repairIssue(selectedIssueIndex, { headingText: value });
|
|
1155
1344
|
});
|
|
1156
1345
|
return;
|
|
@@ -1163,14 +1352,14 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
1163
1352
|
var normalize = detail.ownerDocument.createElement("button");
|
|
1164
1353
|
normalize.type = "button";
|
|
1165
1354
|
normalize.className = "rte-a11y-button";
|
|
1166
|
-
normalize.innerText = "Normalize to H"
|
|
1355
|
+
normalize.innerText = aT("a11yc_normalize", "Normalize to H{0}", suggestedLevel);
|
|
1167
1356
|
normalize.onclick = function () {
|
|
1168
1357
|
repairIssue(selectedIssueIndex, { targetLevel: suggestedLevel });
|
|
1169
1358
|
};
|
|
1170
1359
|
var focus = detail.ownerDocument.createElement("button");
|
|
1171
1360
|
focus.type = "button";
|
|
1172
1361
|
focus.className = "rte-a11y-button rte-a11y-button-secondary";
|
|
1173
|
-
focus.innerText = "Focus issue";
|
|
1362
|
+
focus.innerText = aT("a11yc_focus", "Focus issue");
|
|
1174
1363
|
focus.onclick = function () { focusIssue(issue); };
|
|
1175
1364
|
row.appendChild(normalize);
|
|
1176
1365
|
row.appendChild(focus);
|
|
@@ -1184,14 +1373,14 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
1184
1373
|
var promote = detail.ownerDocument.createElement("button");
|
|
1185
1374
|
promote.type = "button";
|
|
1186
1375
|
promote.className = "rte-a11y-button";
|
|
1187
|
-
promote.innerText = "Promote first row to headers";
|
|
1376
|
+
promote.innerText = aT("a11yc_promote", "Promote first row to headers");
|
|
1188
1377
|
promote.onclick = function () {
|
|
1189
1378
|
repairIssue(selectedIssueIndex, {});
|
|
1190
1379
|
};
|
|
1191
1380
|
var review = detail.ownerDocument.createElement("button");
|
|
1192
1381
|
review.type = "button";
|
|
1193
1382
|
review.className = "rte-a11y-button rte-a11y-button-secondary";
|
|
1194
|
-
review.innerText = "Focus issue";
|
|
1383
|
+
review.innerText = aT("a11yc_focus", "Focus issue");
|
|
1195
1384
|
review.onclick = function () { focusIssue(issue); };
|
|
1196
1385
|
actionRow.appendChild(promote);
|
|
1197
1386
|
actionRow.appendChild(review);
|
|
@@ -1205,12 +1394,12 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
1205
1394
|
var mark = detail.ownerDocument.createElement("button");
|
|
1206
1395
|
mark.type = "button";
|
|
1207
1396
|
mark.className = "rte-a11y-button";
|
|
1208
|
-
mark.innerText = "Mark as " + issue._script;
|
|
1397
|
+
mark.innerText = aT("a11yc_markas", "Mark as {0}", aT("a11yc_script_" + issue._lang, issue._script));
|
|
1209
1398
|
mark.onclick = function () { repairIssue(selectedIssueIndex, { lang: issue._lang }); };
|
|
1210
1399
|
var look = detail.ownerDocument.createElement("button");
|
|
1211
1400
|
look.type = "button";
|
|
1212
1401
|
look.className = "rte-a11y-button rte-a11y-button-secondary";
|
|
1213
|
-
look.innerText = "Focus issue";
|
|
1402
|
+
look.innerText = aT("a11yc_focus", "Focus issue");
|
|
1214
1403
|
look.onclick = function () { focusIssue(issue); };
|
|
1215
1404
|
langRow.appendChild(mark);
|
|
1216
1405
|
langRow.appendChild(look);
|
|
@@ -1218,7 +1407,10 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
1218
1407
|
return;
|
|
1219
1408
|
}
|
|
1220
1409
|
|
|
1221
|
-
|
|
1410
|
+
var noRepair = detail.ownerDocument.createElement("div");
|
|
1411
|
+
noRepair.className = "rte-a11y-detail-copy";
|
|
1412
|
+
noRepair.innerText = aT("a11yc_norepair", "This issue is visible here, but does not have an automatic repair action yet.");
|
|
1413
|
+
detail.appendChild(noRepair);
|
|
1222
1414
|
}
|
|
1223
1415
|
|
|
1224
1416
|
function renderTextRepair(labelText, hintText, apply) {
|
|
@@ -1236,7 +1428,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
1236
1428
|
var button = detail.ownerDocument.createElement("button");
|
|
1237
1429
|
button.type = "button";
|
|
1238
1430
|
button.className = "rte-a11y-button";
|
|
1239
|
-
button.innerText = "Apply fix";
|
|
1431
|
+
button.innerText = aT("a11yc_applyfix", "Apply fix");
|
|
1240
1432
|
button.onclick = function () {
|
|
1241
1433
|
var value = String(input.value || "").replace(/^\s+|\s+$/g, "");
|
|
1242
1434
|
if (!value) return;
|
|
@@ -1245,7 +1437,7 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
1245
1437
|
var focus = detail.ownerDocument.createElement("button");
|
|
1246
1438
|
focus.type = "button";
|
|
1247
1439
|
focus.className = "rte-a11y-button rte-a11y-button-secondary";
|
|
1248
|
-
focus.innerText = "Focus issue";
|
|
1440
|
+
focus.innerText = aT("a11yc_focus", "Focus issue");
|
|
1249
1441
|
focus.onclick = function () {
|
|
1250
1442
|
var issues = lastResult && lastResult.issues ? lastResult.issues : [];
|
|
1251
1443
|
if (issues[selectedIssueIndex]) focusIssue(issues[selectedIssueIndex]);
|