@richscripts/richtexteditor 2.5.3 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -408,7 +408,13 @@ function RTE_Plugin_AccessibilityChecker() {
408
408
  if (headingText) previousHeadingLevel = level;
409
409
  }
410
410
 
411
- var images = node.querySelectorAll ? node.querySelectorAll("img") : [];
411
+ // querySelectorAll only returns DESCENDANTS, so an image that is
412
+ // itself a top-level block — which is exactly where a pasted or
413
+ // dropped image lands in an empty editor — was never checked, and
414
+ // missing alt text went unreported. The table rule below already
415
+ // guards this case; the image rule did not. Measured 2026-08-27:
416
+ // bare top-level img => 0 issues, the same img inside a <p> => 1.
417
+ var images = tag === "img" ? [node] : (node.querySelectorAll ? node.querySelectorAll("img") : []);
412
418
  for (var imageIndex = 0; imageIndex < images.length; imageIndex++) {
413
419
  var image = images[imageIndex];
414
420
  if (!String(image.getAttribute("alt") || "").replace(/^\s+|\s+$/g, "")) {
@@ -425,11 +431,33 @@ function RTE_Plugin_AccessibilityChecker() {
425
431
  var tables = tag === "table" ? [node] : (node.querySelectorAll ? node.querySelectorAll("table") : []);
426
432
  for (var tableIndex = 0; tableIndex < tables.length; tableIndex++) {
427
433
  var table = tables[tableIndex];
428
- if (!table.querySelector("th")) {
434
+ var tableRole = String(table.getAttribute("role") || "").toLowerCase();
435
+ var isLayoutTable = (tableRole === "presentation" || tableRole === "none");
436
+
437
+ // 2026-08-30 A layout table MUST NOT have headers, so demanding
438
+ // them here produced an error the author could never clear —
439
+ // which is how a checker teaches people to ignore it. Presentation
440
+ // tables are held to the opposite rule instead: they must not
441
+ // carry data-table semantics, because <th>/scope/<caption> make a
442
+ // screen reader announce relationships that do not exist
443
+ // (WCAG 1.3.1). See layouttable.js.
444
+ if (isLayoutTable) {
445
+ var semantic = table.querySelector("th, [scope], caption") || table.getAttribute("summary");
446
+ if (semantic) {
447
+ issues.push({
448
+ code: "layout-table-semantics",
449
+ severity: "error",
450
+ 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.",
451
+ path: path + ".table[" + tableIndex + "]",
452
+ _target: table
453
+ });
454
+ }
455
+ }
456
+ else if (!table.querySelector("th")) {
429
457
  issues.push({
430
458
  code: "table-missing-header",
431
459
  severity: "warning",
432
- message: "Table has no header cells. Promote the first row to headers when it describes the columns.",
460
+ message: "Table has no header cells. Promote the first row to headers, or mark it as a layout table when it only positions content.",
433
461
  path: path + ".table[" + tableIndex + "]",
434
462
  _target: table
435
463
  });
@@ -460,7 +488,13 @@ function RTE_Plugin_AccessibilityChecker() {
460
488
  target.parentNode.replaceChild(replacement, target);
461
489
  }
462
490
  else if (issue.code === "table-missing-header") {
463
- promoteFirstTableRow(target);
491
+ // Two valid outcomes, and only the author knows which: the table
492
+ // describes data (give it headers) or only positions it (say so).
493
+ if (options && options.markLayout) markTableAsLayout(target);
494
+ else promoteFirstTableRow(target);
495
+ }
496
+ else if (issue.code === "layout-table-semantics") {
497
+ markTableAsLayout(target);
464
498
  }
465
499
  else if (issue.code === "language-of-parts") {
466
500
  markLanguageRuns(target, options && options.lang ? options.lang : issue._lang, issue._script);
@@ -545,6 +579,35 @@ function RTE_Plugin_AccessibilityChecker() {
545
579
  }
546
580
  }
547
581
 
582
+ // Delegates to layouttable.js when it is loaded so there is ONE definition of
583
+ // what "layout table" means; the fallback keeps the repair working in builds
584
+ // where that plugin was left out of the bundle.
585
+ function markTableAsLayout(table) {
586
+ if (!table) return;
587
+ if (editor && typeof editor.setTableLayoutMode === "function") {
588
+ try { editor.setTableLayoutMode("layout", table); return; } catch (e) {}
589
+ }
590
+ table.setAttribute("role", "presentation");
591
+ table.removeAttribute("summary");
592
+ var caption = table.querySelector("caption");
593
+ if (caption && caption.parentNode === table) caption.parentNode.removeChild(caption);
594
+ var cells = table.querySelectorAll("th,[scope]");
595
+ for (var i = 0; i < cells.length; i++) {
596
+ var cell = cells[i];
597
+ // Tables nest — do not strip the headers of a data table that
598
+ // happens to sit inside a cell of this one.
599
+ if (cell.closest && cell.closest("table") !== table) continue;
600
+ cell.removeAttribute("scope");
601
+ cell.removeAttribute("headers");
602
+ if (String(cell.nodeName || "").toLowerCase() !== "th") continue;
603
+ var td = table.ownerDocument.createElement("td");
604
+ while (cell.firstChild) td.appendChild(cell.firstChild);
605
+ copyAttributes(cell, td);
606
+ td.removeAttribute("scope");
607
+ cell.parentNode.replaceChild(td, cell);
608
+ }
609
+ }
610
+
548
611
  function copyAttributes(source, target) {
549
612
  if (!source || !target || !source.attributes) return;
550
613
  for (var index = 0; index < source.attributes.length; index++) {
@@ -12,7 +12,9 @@ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
12
12
  var existing = document.querySelectorAll("link[rel=stylesheet]");
13
13
  for (var i = 0; i < existing.length; i++) {
14
14
  var href = existing[i].getAttribute("href") || "";
15
- if (/(?:^|\/)aitoolkit\.css(?:\?.*)?$/i.test(href)) return;
15
+ // Matches the minified variant too - a host page that links either one
16
+ // must not get a second copy injected.
17
+ if (/(?:^|\/)aitoolkit(?:\.min)?\.css(?:\?.*)?$/i.test(href)) return;
16
18
  }
17
19
  // Locate our own script tag (aitoolkit.js or all_plugins.js) so we
18
20
  // can resolve aitoolkit.css next to it. Matches a custom mount path.
@@ -26,14 +28,30 @@ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
26
28
  }
27
29
  }
28
30
  var srcAttr = ourScript ? ourScript.getAttribute("src") : "";
29
- var cssHref = srcAttr
30
- ? srcAttr.replace(/[^/]+$/, "aitoolkit.css")
31
- : (((window.RTE_DefaultConfig && window.RTE_DefaultConfig.url_base) || "/richtexteditor") + "/plugins/aitoolkit.css");
32
- if (cssHref.indexOf("?") < 0) cssHref += "?v=20260703a";
31
+ // Prefer the minified stylesheet. aitoolkit.css is 537 KB unminified - larger
32
+ // than the obfuscated rte.js - and every page that loads the AI toolkit was
33
+ // paying for it. The readable .css is still shipped and still the one to edit
34
+ // or override; this only changes what gets fetched by default.
35
+ function resolve(name) {
36
+ return srcAttr
37
+ ? srcAttr.replace(/[^/]+$/, name)
38
+ : (((window.RTE_DefaultConfig && window.RTE_DefaultConfig.url_base) || "/richtexteditor") + "/plugins/" + name);
39
+ }
40
+ function bust(href) { return href.indexOf("?") < 0 ? href + "?v=20260830a" : href; }
41
+
33
42
  var link = document.createElement("link");
34
43
  link.rel = "stylesheet";
35
- link.href = cssHref;
44
+ link.href = bust(resolve("aitoolkit.min.css"));
36
45
  link.setAttribute("data-rte-auto-injected", "aitoolkit");
46
+ // .min.css is newer than .css. An install that predates it - an unpacked older
47
+ // zip, a tier the mirror missed - would otherwise lose ALL AI toolkit styling
48
+ // silently, which is a far worse failure than shipping the larger file. Fall
49
+ // back to the readable stylesheet if the minified one is not there.
50
+ link.onerror = function () {
51
+ if (link.getAttribute("data-rte-css-fallback")) return;
52
+ link.setAttribute("data-rte-css-fallback", "1");
53
+ link.href = bust(resolve("aitoolkit.css"));
54
+ };
37
55
  (document.head || document.documentElement).appendChild(link);
38
56
  })();
39
57