PageSourceSearch

https://swolecharts.com/static/coin-common.0691b8b84b5c.js

js swolecharts.com collected 2026-10-10 02:43:04 UTC 9,731 bytes, 166 lines download raw bytes

1/* Volume Monitor shared helpers for coin.js (/coin/<BASE>) and coins.js (/coins): formatters, venue registry, poller.
2   Formatters mirror templates/partials/usd.html and the template snippets in coin.html. Exposes window.VMC. */
3(function () {
4  'use strict';
5  var $ = function (s, r) { return (r || document).querySelector(s); };
6  var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); };
7  // Exchanges from the server's venue registry (base.html #vm-venues, monitor/venues_ui.py), in display order.
8  var VENUES = (function () { try { return JSON.parse(document.getElementById('vm-venues').textContent) || []; } catch (e) { return []; } })();
9  // The brand (settings.SITE_NAME, T-292) as base.html prints it in og:site_name.
10  var SITE_NAME = (function () { var m = document.querySelector('meta[property="og:site_name"]'); return (m && m.content) || ''; })();
11  var VN = {}, VFULL = {}, LIQS = {};   // name -> chip text ("BIN") / label ("Binance") / has a liquidation feed
12  VENUES.forEach(function (v) {
13    VN[v.name] = v.short || v.label || v.name;
14    VFULL[v.name] = v.label || v.name;
15    if (Array.isArray(v.caps) && v.caps.indexOf('liqs') >= 0) LIQS[v.name] = 1;
16  });
17  var offset = 0;   // server clock minus local clock, ms
18
19  // ---- formatting -----------------------------------------------------------
20  function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return '&#' + c.charCodeAt(0) + ';'; }); }
21  function usd(v) {
22    if (v == null || isNaN(v)) return '—';
23    var a = Math.abs(v), s = v < 0 ? '-$' : '$';
24    if (a >= 999.5e6) return s + (a / 1e9).toFixed(2) + 'B';
25    if (a >= 999500) return s + (a / 1e6).toFixed(2) + 'M';
26    if (a >= 9999.5) return s + (a / 1e3).toFixed(0) + 'K';
27    return s + Math.round(a).toLocaleString('en-US');
28  }
29  function signedUsd(v) { return v > 0 ? '+' + usd(v) : v < 0 ? usd(v) : '$0'; }
30  function pct(f) { if (f == null) return '—'; var v = f * 100; return (v > 0 ? '+' : v < 0 ? '-' : '') + Math.abs(v).toFixed(2) + '%'; }
31  function fund(f) { if (f == null) return '—'; return (f < 0 ? '-' : '') + Math.abs(f * 100).toFixed(4) + '%'; }
32  function apr(f) { return f == null ? '' : '≈' + Math.round(f * 109500) + '% APR'; }
33  function cls(f) { return f == null ? 'z' : f > 0 ? 'up' : f < 0 ? 'down' : ''; }
34  function price(p) {
35    if (!p) return '—';
36    if (p >= 1000) return p.toLocaleString('en-US', { maximumFractionDigits: 1 });
37    var d = p >= 100 ? 2 : p >= 1 ? 3 : p >= 0.1 ? 4 : p >= 0.01 ? 5 : p >= 0.001 ? 6 : p >= 0.0001 ? 7 : p >= 0.00001 ? 8 : 10;
38    return p.toFixed(d);
39  }
40  function now() { return Date.now() + offset; }
41  function ago(ms) {
42    var d = now() - ms;
43    if (d < 60000) return 'just now';
44    if (d < 5400000) return Math.round(d / 60000) + 'm ago';
45    return Math.round(d / 3600000) + 'h ago';
46  }
47  function hm(ms) { var d = new Date(ms); return ('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2); }
48  // Funding schedule (mirror monitor/templatetags/vm.py fint, iv_rate, countdown, utc).
49  function fint(h) { if (!h || h <= 0) return ''; return h < 1 ? Math.round(h * 60) + 'm' : (+h.toFixed(4)) + 'h'; }
50  function ivRate(p) {
51    var f = p.funding, h = p.funding_h;
52    if (f == null || !h || h <= 0) return '—';
53    var v = f * h / 8;
54    return (v < 0 ? '-' : '') + Math.abs(v * 100).toFixed(h < 4 ? 5 : 4) + '%';
55  }
56  function countdown(next, at) {
57    if (!next || next <= at) return '';
58    var m = Math.floor((next - at) / 60000);
59    if (m < 1) return '<1m';
60    return m >= 60 ? Math.floor(m / 60) + 'h ' + ('0' + (m % 60)).slice(-2) + 'm' : m + 'm';
61  }
62  function utcHM(ms) { var d = new Date(ms); return ('0' + d.getUTCHours()).slice(-2) + ':' + ('0' + d.getUTCMinutes()).slice(-2) + ' UTC'; }
63  function chip(venue, market) {
64    return '<span class="chip ' + esc(venue) + '" title="' + esc((VFULL[venue] || venue) + (market ? ' ' + market : '')) + '">' +
65      esc(VN[venue] || venue) + (market ? ' <span class="m">' + esc(market) + '</span>' : '') + '</span>';
66  }
67  function num(v) { if (v === '' || v == null) return null; var n = parseFloat(v); return isNaN(n) ? null : n; }
68  function live(ok, text) {
69    var el = $('#live'); if (!el) return;
70    el.classList.toggle('ok', ok); el.classList.toggle('bad', !ok);
71    $('span', el).textContent = text;
72  }
73
74  // ---- polling helper ---------------------------------------------------------
75  // A live fetch that never settles must not stop polling (T-663): run(signal) is aborted and the promise rejects
76  // (err.timeout) after ms, so the caller's settle path (in-flight flag, next schedule) always runs.
77  function bounded(ms, run) {
78    var ac = typeof AbortController === 'function' ? new AbortController() : null, t = 0;
79    var cap = new Promise(function (_, no) {
80      t = setTimeout(function () { var e = new Error('timeout'); e.timeout = true; if (ac) ac.abort(); no(e); }, ms);
81    });
82    return Promise.race([Promise.resolve().then(function () { return run(ac ? ac.signal : undefined); }
82), cap])
83      .then(function (v) { clearTimeout(t); return v; }, function (e) { clearTimeout(t); throw e; });
84  }
85  function poller(url, every, onData, onFail, delay) {
86    var timer = null, busy = false, lastRun = delay ? Date.now() - every + delay : 0;
87    function tick() {
88      clearTimeout(timer);
89      if (document.hidden) return;              // resumes on visibilitychange
90      var wait = lastRun + every - Date.now();
91      if (busy || wait > 50) { timer = setTimeout(tick, busy ? every : wait); return; }
92      busy = true; lastRun = Date.now();
93      bounded(Math.max(15000, 2 * every), function (signal) {
94        return fetch(url, { cache: 'no-store', signal: signal }).then(function (r) {
95          if (!r.ok) { var e = new Error(r.status); e.status = r.status; throw e; }
96          return r.json();
97        });
98      }).then(function (d) {
99        if (d.server_ts) offset = d.server_ts - Date.now();
100        onData(d);
101      }).catch(function (e) { try { onFail(e); } catch (x) { } })
102        .then(function () { busy = false; clearTimeout(timer); timer = setTimeout(tick, every); });
103    }
104    document.addEventListener('visibilitychange', function () { if (!document.hidden) tick(); });
105    return tick;
106  }
107
108  window.VMC = { esc: esc, usd: usd, signedUsd: signedUsd, pct: pct, fund: fund, apr: apr, cls: cls, price: price, now: now, ago: ago, hm: hm, fint: fint, ivRate: ivRate, countdown: countdown, utcHM: utcHM, chip: chip, num: num, live: live, poller: poller, bounded: bounded, "$": $, "$$": $$, SITE_NAME: SITE_NAME, VN: VN, VFULL: VFULL, LIQS: LIQS };
109  // Spike feed links (T-545): stars persist only signed in or in device mode, so otherwise a[data-feedlink]
110  // carries them as a shared view (/?w=BTC,SOL, T-514) and the feed still highlights this visit's stars.
111  (function () {
112    var P = window.vmPrefs, S = P && P.stars, links = $$('a[data-feedlink]');
113    if (!S || !links.length) return;
114    function url() {
115      var l = S.list().filter(function (b) { return /^[A-Za-z0-9]{1,20}$/.test(b); }).slice(0, 50);
116      return P.signedIn() || P.device() || !l.length ? '/' : '/?w=' + l.join(',');
117    }
118    function upd() { var u = url(); links.forEach(function (a) { a.setAttribute('href', u); }); }
119    links.forEach(function (a) { a.addEventListener('click', upd); });
120    S.on(upd);
121    upd();
122  })();
123  // Coin-to-coin navigation: character keys only act on focused neighbouring-coin links.
124  (function () {
125    var prev = $('a[data-coinnav="prev"]'), next = $('a[data-coinnav="next"]');
126    if (!prev && !next) return;
127    document.addEventListener('keydown', function (e) {
128      if (e.defaultPrevented || e.isComposing || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) return;
129      var t = e.target, tag = t && t.tagName;
130      if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA' || (t && t.isContentEditable)) return;
131      if ((t !== prev && t !== next) || document.activeElement !== t) return;
132      var a = (e.key === '[' || e.key === 'k') ? prev : (e.key === ']' || e.key === 'j') ? next : null;
133      if (a) { e.preventDefault(); location.href = a.href; }
134    });
135    var x0 = null, y0 = 0;
136    document.addEventListener('touchstart', function (e) {
137      var t = e.touches[0];
138      x0 = e.touches.length === 1 && !(e.target.closest && e.target.closest('table, canvas, input, select, textarea, .scroll, [data-noswipe]')) ? t.clientX : null; y0 = t.clientY;
139    }, { passive: true });
140    document.addEventListener('touchend', function (e) {
141      if (x0 === null) return;
142      var t = e.changedTouches[0], dx = t.clientX - x0, dy = t.clientY - y0; x0 = null;
143      if (Math.abs(dx) < 110 || Math.abs(dy) > Math.abs(dx) / 2) return;
144      var a = dx > 0 ? prev : next;
145      if (a) location.href = a.href;
146    }, { passive: true });
147  })();
148  // "Notify me" quick alerts (T-552): alerts.js (rule creation, manage token, push) loads only on first open.
149  (function () {
150    var nt = $('details[data-notify]');
151    if (!nt) return;
152    nt.addEventListener('toggle', function () {
153      if (!nt.open || nt.getAttribute('data-loaded')) return;
154      nt.setAttribute('data-loaded', '1');
155      var s = document.createElement('script');
156      s.src = nt.getAttribute('data-src');
157      s.onload = function () { if (window.SwoleAlerts) window.SwoleAlerts.quick(nt); };
158      s.onerror = function () {
159        nt.removeAttribute('data-loaded');
160        var m = $('[data-notify-msg]', nt);
161        m.hidden = false; m.className = 'nt-msg err'; m.textContent = 'Could not load alerts. Close and reopen to try again.';
162      };
163      document.head.appendChild(s);
164    });
165  })();
166})();

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.