mindX / docs.js
Gregory-L's picture
docs: Thesis, Manifesto and Navigation in full (hashed against the map), read aloud, library of every document linked to mindx.pythai.net
9de0642 verified
Raw
History Blame Contribute Delete
11.8 kB
// mindX docs reader: the three public documents in full (bundled, hashed in the browser against
// the map), the Thesis and Manifesto read aloud, and the library of every document linked to its
// page on mindx.pythai.net. Markdown goes through marked, then DOMPurify, then the link rewriter.
(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 }; // bundled in this Space, byte-identical to the node
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, '-');
}
// ---------- listen ----------
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;
}
// ---------- library ----------
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';
}
// ---------- reader ----------
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 Markdown file outside docs/: the node resolves it by name
a.href = SITE + '/doc/' + path.split('/').pop().replace(/\.md$/i, '');
a.target = '_blank'; a.rel = 'noopener'; a.classList.add('ext-doc');
return;
}
// a source file: the active repository is private, so the reference stays as text
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]) { // the library links these straight to the node; this is the fallback view
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'));
});
}
// ---------- boot ----------
$('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();
});
})();