← back to 1960swallpaper

public/sku-redact.js

106 lines

// _shared/sku-redact.js — DISPLAY-ONLY vendor scrub for product identifiers.
//
// Why: customer-facing card meta (.ven), list rows (.r-sku) and the detail modal
// ([data-d-brand]) render the product's sku || handle. For vendor-imported rows
// that identifier carries a 3rd-party vendor name baked into the slug, e.g.
//   "wolfgordonwallcovering_dwwg_dnuv-512-jpg", "...-jeffrey-stevens",
//   "dwk-31477-koroseal-arte-type-2-...", "agra-okra-arte-international".
// That violates the hard rule "DW vendor names NEVER in customer-facing UI".
//
// The sku/handle DATA stays intact (the sample-order flow + product links depend
// on them) — this only sanitizes what is SHOWN. When a displayed identifier
// segment carries a non-house vendor token, that segment is dropped; the DW brand
// label and any price segment are kept. House brands (Hollywood, Phillipe Romano,
// Retro Walls, Designer Wallcoverings, Maison Lustre) are never touched.
//
// Runs on load + MutationObserver because cards hydrate client-side and the grid
// infinite-scrolls; the detail modal updates an existing node's text on open.
// Idempotent (only writes when the text actually changes) so the observer can't loop.
//
// Deploy like corner-nav.js: copied into each site's public/, loaded via
//   <script src="/sku-redact.js" defer></script>
// Canonical source is this file; keep per-site public/ copies in sync.
(function () {
  // 3rd-party vendor tokens (mirrors LEAD_VENDOR in _shared/api-vendor-redact.js).
  var VENDORS = [
    'wolf gordon', 'nina campbell', 'jeffrey stevens', 'versace',
    'arte international', 'lee jofa modern', 'lee jofa', 'innovations usa',
    'innovations', 'koroseal', 'schumacher', 'candice olson', 'thibaut',
    'maya romanoff', 'scalamandre', 'dedar', 'carnegie', 'cole and son',
    'kravet', 'clarke and clarke', 'brunschwig', 'gp j baker', 'sandberg',
    'designers guild', 'graham and brown', 'harlequin', 'andrew martin',
    'ronald redding', 'blithfield', 'coordonne', 'fentucci', 'sister parish',
    'phillip jeffries', 'fromental', 'westport', 'breegan', 'les ensembliers',
    'roger thomas', 'stacy garcia'
  ];
  function norm(s) { return String(s == null ? '' : s).toLowerCase().replace(/[^a-z0-9]+/g, ''); }
  var VEN_NORM = VENDORS.map(norm).filter(Boolean);

  // True if the identifier text carries a 3rd-party vendor token.
  function hasVendor(idPart) {
    var n = norm(idPart);
    if (!n) return false;
    for (var i = 0; i < VEN_NORM.length; i++) {
      if (n.indexOf(VEN_NORM[i]) >= 0) return true;
    }
    return false;
  }

  // "Brand · id · price" → drop only the segment(s) carrying a vendor token.
  // The first segment (brand) and clean segments (e.g. price) are kept.
  function cleanMeta(text) {
    if (text.indexOf('·') < 0) {        // no "·" separator: bare value
      return hasVendor(text) ? '' : text;
    }
    var parts = text.split('·');
    var out = [];
    for (var i = 0; i < parts.length; i++) {
      var seg = parts[i].trim();
      if (i === 0 || !hasVendor(seg)) out.push(seg);
    }
    return out.join(' · ');
  }

  function scrubEl(el) {
    if (!el || el.nodeType !== 1) return;
    var t = el.textContent;
    if (!t) return;
    // Bare-identifier cell (list view): replace a vendor-y value with an em dash.
    if (el.classList && el.classList.contains('r-sku')) {
      if (hasVendor(t)) { if (el.textContent !== '—') el.textContent = '—'; }
      return;
    }
    // Brand/meta line: drop vendor-y segments, keep brand + price.
    var cleaned = cleanMeta(t);
    if (cleaned !== t) el.textContent = cleaned;
  }

  var SEL = '.ven, .r-sku, [data-d-brand], .d-brand';
  function scrubAll(root) {
    if (!root || !root.querySelectorAll) return;
    if (root.matches && root.matches(SEL)) scrubEl(root);
    var els = root.querySelectorAll(SEL);
    for (var i = 0; i < els.length; i++) scrubEl(els[i]);
  }

  function boot() {
    scrubAll(document);
    if (!window.MutationObserver) return;
    var mo = new MutationObserver(function (muts) {
      for (var i = 0; i < muts.length; i++) {
        var m = muts[i];
        if (m.type === 'characterData') {
          var p = m.target && m.target.parentElement;
          if (p && p.closest) { var host = p.closest(SEL); if (host) scrubEl(host); }
        } else {
          for (var j = 0; j < m.addedNodes.length; j++) scrubAll(m.addedNodes[j]);
        }
      }
    });
    mo.observe(document.body, { childList: true, subtree: true, characterData: true });
  }

  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot);
  else boot();
})();