PageSourceSearch

https://undervalued.ai/static/js/search-auth.js?v=2.208

js undervalued.ai collected 2026-10-05 15:09:09 UTC 3,991 bytes, 122 lines download raw bytes

1// static/js/search-auth.js — lock badges and click gating, fetchless using pre-attached flags
2(function () {
3  const ICONS = (window.constants && window.constants.ICONS) || {};
4
5  function isLoggedIn() {
6    return document.documentElement.classList.contains('user-logged-in');
7  }
8
9  function readAuthRequiredFromEl(el) {
10    const raw = el?.getAttribute('data-auth-required');
11    return raw === '1' ? true : raw === '0' ? false : null;
12  }
13
14  function decorateLocks(instance) {
15    const container = instance?.elements?.searchResults;
16    if (!container) return;
17
18    const loggedIn = isLoggedIn();
19    instance._authLockMap = instance._authLockMap || Object.create(null);
20
21    for (const el of container.children) {
22      const sym = String(el.dataset?.symbol || '').toUpperCase();
23      const badge = el.querySelector('.auth-lock-badge');
24
25      // Prefer DOM attribute, fallback to any cached map entry, else default to false
26      let needsAuth = readAuthRequiredFromEl(el);
27      if (needsAuth === null) needsAuth = !!instance._authLockMap[sym];
28
29      const shouldShow = !loggedIn && !!needsAuth;
30
31      if (shouldShow && !badge && ICONS.LOCK) {
32        el.insertAdjacentHTML('beforeend', `<span class="auth-lock-badge" aria-label="Login required">${ICONS.LOCK}</span>`);
33      } else if (!shouldShow && badge) {
34        badge.remove();
35      }
36
37      // Keep cache updated to support click gating
38      instance._authLockMap[sym] = !!needsAuth;
39    }
40  }
41
42  function patchPrototype() {
43    if (typeof StockSearch === 'undefined') return;
44    if (StockSearch.prototype.__authPatched) return;
45
46    // Decorate after rendering, no network
47    const prevShowResults = StockSearch.prototype.showResults;
48    StockSearch.prototype.showResults = function (matches, q) {
49      prevShowResults.call(this, matches, q);
50      decorateLocks(this);
51    };
52
53    // Gate clicks purely from stock.auth_required or DOM attributes
54    const prevSelect = StockSearch.prototype.selectStock;
55    StockSearch.prototype.selectStock = function (stock) {
56      try {
57        const sym = String(stock?.symbol || '').toUpperCase();
58        let lockedFlag = !!stock?.auth_required;
59
60        if (!('auth_required' in (stock || {}))) {
61          const el = this.elements?.searchResults?.querySelector(`[data-symbol="${sym}"]`);
62          const v = readAuthRequiredFromEl(el);
63          // fallback to cache if attribute missing
64          lockedFlag = v === null ? !!(this._authLockMap && this._authLockMap[sym]) : v;
65        }
66
67        if (!isLoggedIn() && lockedFlag) {
68          window.openModal?.('authModal', 'signup');
69          return;
70        }
71      } catch {}
72      prevSelect.call(this, stock);
73    };
74
75    StockSearch.prototype.__authPatched = true;
76  }
77
78  function installResultsObserver() {
79    const inst = window.stockSearchInstance;
80    const container = inst?.elements?.searchResults;
81    if (!inst || !container || inst.__authObs) return;
82
83    const obs = new MutationObserver(() => decorateLocks(inst));
84    obs.observe(container, { childList: true });
85    inst.__authObs = obs;
86
87    // First paint
88    decorateLocks(inst);
89  }
90
91  function installLoginObserver() {
92    try {
93      const root = document.documentElement;
94      const obs = new MutationObserver(() => {
95        const inst = window.stockSearchInstance;
96        if (inst) decorateLocks(inst);
97      });
98      obs.observe(root, { attributes: true, attributeFilter: ['class'] });
99    } catch {}
100  }
101
102  // Boot
103  document.addEventListener('DOMContentLoaded', () => {
104    patchPrototype();
105
106    // Wait until StockSearch instance exists, then attach observer
107    const t = setInterval(() => {
108      if (window.stockSearchInstance) {
109        clearInterval(t);
110        installResultsObserver();
111      }
112    }, 30);
113
114    installLoginObserver();
115  });
116
117  // public hook for any future explicit refresh
118  window.refreshAuthLocks = function () {
119    const inst = window.stockSearchInstance;
120    if (inst) decorateLocks(inst);
121  };
122})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.