← Commit history

adom-table: add mark= (cell emphasis) and {key} interpolation in heading=

Ray ·d730a8e445 ·1mo ago ·parent 69b8086
Both come from converting adom/repo-ownership's bespoke ownership frame to this widget, and both are general rather than specific to that page. mark="warn:rule:PR ONLY|ASK; accent:rule:^merge:" colours cells whose value matches a pattern. The widget HTML-escapes every cell, which is right, but it also flattened that page's red PR-ONLY stop-signal into ordinary grey text - on a guardrail page the one thing a reader must not miss. mark adds a class only; the cell text stays escaped, and a malformed rule is dropped rather than thrown so one bad pattern cannot blank a table. heading="Regenerated {generated}" interpolates a top-level field of the data envelope, so a table can carry a freshness stamp without its host frame fetching the file a second time just to print one line. An unknown token is left visible rather than silently vanishing. Verified in Chrome against the real widget frame: 288 rows, correct labels, 2 warn cells, 1 accent cell, 4 dimmed dormant rows.
1 file changed +37−3
widgets/adom-table.js+37−3
@@ -503,11 +503,21 @@  *  * Attributes:  *   columns="a,b,c"   restrict and order columns (overrides the data's own order)- *   heading="..."     caption above the table+ *   heading="..."     caption above the table; {key} interpolates a top-level field of the+ *                     data envelope, so heading="Regenerated {generated}" keeps a freshness+ *                     stamp on the page without the host frame having to fetch the file+ *                     itself just to print one line  *   filter            show the filter box (default on; filter="off" hides it)  *   facet="owner"     add a dropdown that filters on one column's distinct values  *   sort="col"        initial sort column; prefix "-" for descending  *   dim="key:value"   render rows matching key=value at reduced opacity (e.g. archived)+ *   mark="style:key:re[; ...]"  colour cells whose value matches a pattern, e.g.+ *                     mark="warn:rule:PR ONLY|ASK; accent:rule:^merge:"+ *                     styles: warn (red) accent (blue) ok (green). The cell text is still+ *                     HTML-escaped - only a class is added - so this cannot inject markup.+ *                     Added because escaping every cell flattened adom/repo-ownership's+ *                     "PR ONLY" stop-signal into ordinary grey text, which on a guardrail+ *                     page is the one thing the reader must not miss.  */ (function () {   'use strict';@@ -536,7 +546,7 @@   }    window.AdomWidget.define('adom-table', {-    attributes: ['columns', 'heading', 'filter', 'facet', 'sort', 'dim'],+    attributes: ['columns', 'heading', 'filter', 'facet', 'sort', 'dim', 'mark'],     loadingText: 'Loading table',     emptyText: 'No data supplied to <adom-table>',     css: [@@ -553,12 +563,23 @@       'th[aria-sort="descending"]:after{content:" \\2193"}',       'td{padding:5px 8px;border-bottom:1px solid var(--line2,#161b22);vertical-align:top}',       'tr.dim td{opacity:.45}',+      'td.mk-warn{color:var(--aw-red,#f85149);font-weight:600}',+      'td.mk-accent{color:var(--aw-blue,#58a6ff);font-weight:600}',+      'td.mk-ok{color:var(--aw-green,#3fb950);font-weight:600}',       'caption{text-align:left;font-weight:600;padding:0 0 8px}'     ].join(''),     render: function (data, ctx) {       var n = normalise(data, this.getAttribute('columns'));       this._n = n;       var heading = this.getAttribute('heading');+      // Substitute BEFORE escaping, and only from the data envelope's own top-level scalars,+      // so an unknown {token} is left visible as written rather than silently vanishing.+      if (heading && data && !Array.isArray(data)) {+        heading = heading.replace(/\{(\w+)\}/g, function (m, k) {+          var v = data[k];+          return (v == null || typeof v === 'object') ? m : String(v);+        });+      }       var showFilter = this.getAttribute('filter') !== 'off';       var facet = this.getAttribute('facet');       var bar = '';@@ -579,6 +600,13 @@       var q = root.querySelector('input'), sel = root.querySelector('select');       var count = root.querySelector('[data-count]');       var dim = (el.getAttribute('dim') || '').split(':');+      // A malformed rule is dropped, never thrown: one bad pattern in an attribute must not+      // blank the whole table, which is the failure mode a guardrail page can least afford.+      var marks = (el.getAttribute('mark') || '').split(';').map(function (rule) {+        var m = rule.trim().match(/^(warn|accent|ok):([^:]+):([\s\S]+)$/);+        if (!m) return null;+        try { return { style: m[1], key: m[2], re: new RegExp(m[3]) }; } catch (e) { return null; }+      }).filter(Boolean);       var s = (el.getAttribute('sort') || '').replace(/^-/, '');       var dir = (el.getAttribute('sort') || '')[0] === '-' ? -1 : 1; @@ -609,7 +637,13 @@         tb.innerHTML = out.map(function (r) {           var isDim = dim.length === 2 && String(r[dim[0]]) === dim[1];           return '<tr' + (isDim ? ' class=dim' : '') + '>' + n.cols.map(function (c) {-            return '<td>' + esc(r[c.key] == null ? '' : r[c.key]) + '</td>';+            var v = r[c.key] == null ? '' : r[c.key], cls = '';+            for (var i = 0; i < marks.length; i++) {+              if (marks[i].key === c.key && marks[i].re.test(String(v))) {+                cls = ' class="mk-' + marks[i].style + '"'; break;+              }+            }+            return '<td' + cls + '>' + esc(v) + '</td>';           }).join('') + '</tr>';         }).join('');         if (count) count.textContent = out.length + ' of ' + n.rows.length;