| |
| |
| |
| (function () { |
| 'use strict'; |
|
|
| var SITE = 'https://mindx.pythai.net'; |
| var HUB = 'https://huggingface.co/datasets/PYTHAI/mindX-docs/blob/main/docs/'; |
| var HERE = { THESIS: 1, MANIFESTO: 1, NAV: 1 }; |
| var LABEL = { THESIS: 'Thesis', MANIFESTO: 'Manifesto', NAV: 'Navigation' }; |
|
|
| var index = { docs: [] }, byName = {}, audio = {}; |
| var tierFilter = 'all', query = ''; |
|
|
| function $(id) { return document.getElementById(id); } |
| function el(tag, cls, text) { |
| var e = document.createElement(tag); |
| if (cls) e.className = cls; |
| if (text != null) e.textContent = String(text); |
| return e; |
| } |
| function link(href, text, cls, internal) { |
| var a = el('a', cls, text); |
| a.href = href; |
| if (internal) a.target = '_self'; else a.rel = 'noopener'; |
| return a; |
| } |
| function nameOf(path) { return path.replace(/\.md$/i, ''); } |
| function mins(sec) { var m = Math.round(sec / 60); return m + ' min'; } |
| function fmt(n) { return Number(n).toLocaleString('en-US'); } |
| function clear(n) { while (n.firstChild) n.removeChild(n.firstChild); } |
| function slug(s) { |
| return String(s).toLowerCase().trim().replace(/[^\w\s-]/g, '').replace(/\s+/g, '-'); |
| } |
|
|
| |
| function player(name) { |
| var a = audio[name]; if (!a) return null; |
| var card = el('div', 'card player'); |
| card.appendChild(el('span', 'kind', 'listen · ' + mins(a.seconds) + ' · ' + a.parts.length + ' parts')); |
| card.appendChild(el('h3', null, LABEL[name])); |
| var d = byName[name]; |
| if (d) card.appendChild(el('p', 'muted', d.title)); |
| var au = document.createElement('audio'); |
| au.controls = true; au.preload = 'none'; |
| var part = 0; |
| var status = el('div', 'part-status'); |
| function load(i, play) { |
| part = i; au.src = a.parts[i].file; |
| status.textContent = 'part ' + (i + 1) + ' of ' + a.parts.length + ' · ' + mins(a.parts[i].seconds); |
| Array.prototype.forEach.call(list.children, function (b, j) { b.classList.toggle('on', j === i); }); |
| if (play) au.play().catch(function () {}); |
| } |
| au.addEventListener('ended', function () { if (part + 1 < a.parts.length) load(part + 1, true); }); |
| card.appendChild(au); |
| var list = el('div', 'parts'); |
| a.parts.forEach(function (p, i) { |
| var b = el('button', 'chip', (i + 1) + ' · ' + mins(p.seconds)); |
| b.type = 'button'; |
| b.addEventListener('click', function () { load(i, true); }); |
| list.appendChild(b); |
| }); |
| card.appendChild(status); |
| card.appendChild(list); |
| var actions = el('div', 'actions'); |
| actions.appendChild(link('docs.html?doc=' + name, 'read along ↓', 'more', true)); |
| actions.lastChild.addEventListener('click', function (e) { e.preventDefault(); open(name, true); }); |
| actions.appendChild(link(a.listen_url, 'listen on mindx.pythai.net ↗', 'more')); |
| actions.appendChild(link(SITE + '/doc/' + name, 'read on mindx.pythai.net ↗', 'more')); |
| card.appendChild(actions); |
| card.appendChild(el('p', 'fine', 'Read by ' + a.voice.replace('piper:', 'piper · ') + ', rendered on my node from exactly these bytes · Ogg Opus')); |
| load(0, false); |
| return card; |
| } |
|
|
| |
| function renderLibrary() { |
| var ul = $('lib-list'); clear(ul); |
| var q = query.toLowerCase(), shown = 0; |
| var docs = index.docs.slice().sort(function (a, b) { |
| var ta = a.tier === 'public' ? 0 : a.tier === 'member' ? 1 : 2, tb = b.tier === 'public' ? 0 : b.tier === 'member' ? 1 : 2; |
| return ta - tb || a.path.localeCompare(b.path); |
| }); |
| docs.forEach(function (d) { |
| if (tierFilter !== 'all' && d.tier !== tierFilter) return; |
| if (q && (d.title + ' ' + d.path).toLowerCase().indexOf(q) < 0) return; |
| shown++; |
| if (shown > 400) return; |
| var li = el('li', 'lib-item tier-' + d.tier); |
| var name = nameOf(d.path); |
| var a = HERE[name] |
| ? link('docs.html?doc=' + name, d.title, 'lib-title', true) |
| : link(d.url, d.title, 'lib-title'); |
| if (HERE[name]) a.addEventListener('click', function (e) { e.preventDefault(); open(name, true); }); |
| li.appendChild(a); |
| var meta = el('span', 'lib-meta', d.path + ' · ' + fmt(d.words) + ' words'); |
| li.appendChild(meta); |
| li.appendChild(el('span', 'tag ' + (d.tier === 'public' ? 'ok' : d.tier === 'member' ? 'warn' : 'dim'), d.tier)); |
| ul.appendChild(li); |
| }); |
| if (shown > 400) ul.appendChild(el('li', 'lib-more', (shown - 400) + ' more. Refine the search.')); |
| if (!shown) ul.appendChild(el('li', 'lib-more', 'Nothing matches.')); |
| var c = index.counts || {}; |
| $('lib-count').textContent = fmt(index.docs.length) + ' documents · ' + (c.public || 0) + ' public'; |
| } |
|
|
| |
| function rewrite(root, current) { |
| Array.prototype.slice.call(root.querySelectorAll('a[href]')).forEach(function (a) { |
| var href = a.getAttribute('href') || ''; |
| if (/^(https?:|mailto:)/i.test(href)) { a.target = '_blank'; a.rel = 'noopener'; return; } |
| if (href.charAt(0) === '#') { |
| a.target = '_self'; |
| a.addEventListener('click', function (e) { |
| var t = document.getElementById('h-' + slug(decodeURIComponent(href.slice(1)))); |
| if (t) { e.preventDefault(); t.scrollIntoView({ behavior: 'smooth', block: 'start' }); } |
| }); |
| return; |
| } |
| var u; try { u = new URL(href, 'https://x.invalid/docs/'); } catch (e) { u = null; } |
| var path = u ? u.pathname : ''; |
| var md = /\.md$/i.test(path); |
| if (md && path.indexOf('/docs/') === 0) { |
| var name = decodeURIComponent(path.slice(6).replace(/\.md$/i, '')); |
| if (HERE[name]) { |
| a.href = 'docs.html?doc=' + name; a.target = '_self'; |
| a.addEventListener('click', function (e) { e.preventDefault(); open(name, true); }); |
| } else { |
| a.href = SITE + '/doc/' + name; a.target = '_blank'; a.rel = 'noopener'; |
| var d = byName[name]; |
| a.title = d ? d.title + ' · ' + d.tier + ' · on mindx.pythai.net' : 'on mindx.pythai.net'; |
| a.classList.add('ext-doc'); |
| } |
| return; |
| } |
| if (md) { |
| a.href = SITE + '/doc/' + path.split('/').pop().replace(/\.md$/i, ''); |
| a.target = '_blank'; a.rel = 'noopener'; a.classList.add('ext-doc'); |
| return; |
| } |
| |
| var span = el('span', 'src-ref', a.textContent); |
| span.title = 'in mindX’s source: ' + path.replace(/^\/+/, ''); |
| a.parentNode.replaceChild(span, a); |
| }); |
| Array.prototype.slice.call(root.querySelectorAll('img')).forEach(function (img) { |
| if (!/^https:/i.test(img.getAttribute('src') || '')) img.remove(); |
| }); |
| Array.prototype.slice.call(root.querySelectorAll('h1,h2,h3,h4')).forEach(function (h) { |
| h.id = 'h-' + slug(h.textContent); |
| }); |
| } |
|
|
| function hashHex(buf) { |
| return crypto.subtle.digest('SHA-256', buf).then(function (d) { |
| return Array.prototype.map.call(new Uint8Array(d), function (b) { return ('0' + b.toString(16)).slice(-2); }).join(''); |
| }); |
| } |
|
|
| function meta(name, d, verdict) { |
| var m = $('doc-meta'); clear(m); |
| var row = el('div', 'meta-row'); |
| row.appendChild(el('span', 'tag ' + (d.tier === 'public' ? 'ok' : 'dim'), d.tier)); |
| row.appendChild(el('span', 'fine', d.path + ' · ' + fmt(d.words) + ' words')); |
| if (verdict) row.appendChild(el('span', 'fine ' + (verdict.ok ? 'ok-text' : 'bad-text'), verdict.text)); |
| m.appendChild(row); |
| var actions = el('div', 'actions'); |
| actions.appendChild(link(SITE + '/doc/' + name, 'read on mindx.pythai.net ↗', 'btn small')); |
| if (audio[name]) { |
| var l = link('#listen', 'listen here', 'btn small', true); |
| l.addEventListener('click', function (e) { e.preventDefault(); $('listen').scrollIntoView({ behavior: 'smooth' }); }); |
| actions.appendChild(l); |
| actions.appendChild(link(audio[name].listen_url, 'listen on mindx.pythai.net ↗', 'btn small')); |
| } |
| if (HERE[name]) actions.appendChild(link(HUB + name + '.md', 'Markdown on the Hub ↗', 'btn small')); |
| m.appendChild(actions); |
| } |
|
|
| function open(name, scroll) { |
| var d = byName[name] || byName.NAV; name = nameOf(d.path); |
| try { history.replaceState(null, '', 'docs.html?doc=' + encodeURIComponent(name)); } catch (e) {} |
| var body = $('doc-body'); |
| if (!HERE[name]) { |
| meta(name, d, null); clear(body); |
| body.appendChild(el('h1', null, d.title)); |
| body.appendChild(el('p', null, 'This document is ' + d.tier + '-tier. It is on my node, behind the same gate as everything else there.')); |
| body.appendChild(link(d.url, 'open it on mindx.pythai.net ↗', 'btn')); |
| return; |
| } |
| meta(name, d, { ok: true, text: 'checking sha256…' }); |
| clear(body); body.appendChild(el('p', 'muted', 'Loading ' + LABEL[name] + '…')); |
| fetch('docs/' + name + '.md', { cache: 'no-cache' }).then(function (r) { |
| if (!r.ok) throw new Error(r.status); |
| return r.arrayBuffer(); |
| }).then(function (buf) { |
| var text = new TextDecoder('utf-8').decode(buf); |
| var html = window.DOMPurify.sanitize(window.marked.parse(text), { USE_PROFILES: { html: true } }); |
| clear(body); |
| var frag = document.createElement('div'); frag.innerHTML = html; |
| rewrite(frag, name); |
| while (frag.firstChild) body.appendChild(frag.firstChild); |
| if (scroll) $('reader').scrollIntoView({ behavior: 'smooth', block: 'start' }); |
| return (window.crypto && crypto.subtle) ? hashHex(buf).then(function (hex) { |
| meta(name, d, hex === d.sha256 |
| ? { ok: true, text: 'sha256 ' + hex.slice(0, 12) + '… matches the map' } |
| : { ok: false, text: 'sha256 ' + hex.slice(0, 12) + '… differs from the map' }); |
| }) : meta(name, d, null); |
| }).catch(function () { |
| clear(body); |
| body.appendChild(el('p', null, 'This copy did not load.')); |
| body.appendChild(link(SITE + '/doc/' + name, 'read it on mindx.pythai.net ↗', 'btn')); |
| }); |
| } |
|
|
| |
| $('lib-search').addEventListener('input', function (e) { query = e.target.value; renderLibrary(); }); |
| Array.prototype.forEach.call(document.querySelectorAll('#lib-chips .chip'), function (b) { |
| b.addEventListener('click', function () { |
| tierFilter = b.getAttribute('data-tier'); |
| Array.prototype.forEach.call(document.querySelectorAll('#lib-chips .chip'), function (x) { x.classList.toggle('on', x === b); }); |
| renderLibrary(); |
| }); |
| }); |
|
|
| Promise.all([ |
| fetch('docs/index.json').then(function (r) { return r.json(); }).catch(function () { return { docs: [] }; }), |
| fetch('audio/index.json').then(function (r) { return r.json(); }).catch(function () { return {}; }) |
| ]).then(function (res) { |
| index = res[0]; audio = res[1]; |
| index.docs.forEach(function (d) { byName[nameOf(d.path)] = d; }); |
| var box = $('players'); |
| ['THESIS', 'MANIFESTO'].forEach(function (n) { var p = player(n); if (p) box.appendChild(p); }); |
| renderLibrary(); |
| var want = (new URLSearchParams(location.search).get('doc') || '').toUpperCase(); |
| open(HERE[want] ? want : 'THESIS', false); |
| if (location.hash === '#listen') $('listen').scrollIntoView(); |
| }); |
| })(); |
|
|