@richscripts/richtexteditor 2.5.4 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -431,11 +431,33 @@ function RTE_Plugin_AccessibilityChecker() {
431
431
  var tables = tag === "table" ? [node] : (node.querySelectorAll ? node.querySelectorAll("table") : []);
432
432
  for (var tableIndex = 0; tableIndex < tables.length; tableIndex++) {
433
433
  var table = tables[tableIndex];
434
- 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")) {
435
457
  issues.push({
436
458
  code: "table-missing-header",
437
459
  severity: "warning",
438
- 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.",
439
461
  path: path + ".table[" + tableIndex + "]",
440
462
  _target: table
441
463
  });
@@ -466,7 +488,13 @@ function RTE_Plugin_AccessibilityChecker() {
466
488
  target.parentNode.replaceChild(replacement, target);
467
489
  }
468
490
  else if (issue.code === "table-missing-header") {
469
- 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);
470
498
  }
471
499
  else if (issue.code === "language-of-parts") {
472
500
  markLanguageRuns(target, options && options.lang ? options.lang : issue._lang, issue._script);
@@ -551,6 +579,35 @@ function RTE_Plugin_AccessibilityChecker() {
551
579
  }
552
580
  }
553
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
+
554
611
  function copyAttributes(source, target) {
555
612
  if (!source || !target || !source.attributes) return;
556
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