/* SPDX-License-Identifier: Apache-2.0 * * The documentation reader. * * Renders the project's Markdown in the site's own theme, so the docs can be * read without leaving the page and without a build step. No dependency: the * parser below handles exactly what docs/ actually uses -- headings, fenced * code, tables, lists, quotes, and inline marks. Pulling in a 40 KB Markdown * library to render 29 files would outweigh every other asset here combined. * * Everything is escaped before any tag is emitted. The only HTML that reaches * the page is HTML this file wrote. */ (function () { "use strict"; /* ---- the index ---------------------------------------------------- */ var DOCS = [ { group: "Start here", items: [ ["README.md", "Overview"], ["plan/1.0.md", "The 1.0 definition"], ["licensing.md", "Licensing"] ]}, { group: "Architecture", items: [ ["architecture/README.md", "The system, layer by layer"], ["architecture/boot.md", "Boot"], ["architecture/kernel.md", "Kernel"], ["architecture/syscalls.md", "Syscalls"], ["architecture/filesystem.md", "The VFS"], ["architecture/schfs.md", "SchFS"], ["architecture/terminals.md", "Virtual terminals"], ["architecture/devices.md", "Device model"], ["architecture/usb.md", "USB"], ["architecture/graphics.md", "Graphics"], ["architecture/networking.md", "Networking"], ["architecture/init.md", "init"] ]}, { group: "Guides", items: [ ["guides/building.md", "Building"], ["guides/testing.md", "Testing"], ["guides/layout.md", "Tree layout"], ["guides/adding-a-driver.md", "Adding a driver"] ]}, { group: "Plan", items: [ ["plan/README.md", "What plan/ is for"], ["plan/roadmap.md", "Roadmap"], ["plan/foundations.md", "Foundations"], ["plan/init.md", "schinit"], ["plan/wayland.md", "Wayland"], ["plan/hybrid.md", "Hybrid kernel"], ["plan/schapi.md", "SchAPI"] ]}, { group: "History", items: [ ["history/README.md", "Why history/ exists"], ["history/kernel.md", "Kernel"], ["history/schfs.md", "SchFS"], ["history/moving-to-linux.md", "Moving to Linux"] ]}, { group: "Branding", items: [ ["branding/README.md", "The three marks"] ]} ]; /* ---- helpers ------------------------------------------------------- */ function esc(s) { return s.replace(/&/g, "&").replace(//g, ">"); } /* Resolve a doc-relative link against the doc it appeared in, so "../history/schfs.md" inside architecture/ lands on history/schfs.md. */ function resolve(from, href) { var base = from.split("/").slice(0, -1); var parts = href.split("/"); for (var i = 0; i < parts.length; i++) { if (parts[i] === "." || parts[i] === "") continue; if (parts[i] === "..") base.pop(); else base.push(parts[i]); } return base.join("/"); } /* encodeURIComponent turns every "/" into %2F, which works but makes a shared link unreadable. Slashes are legal in a query value, so keep them. */ function docHref(p) { return "?doc=" + encodeURIComponent(p).replace(/%2F/g, "/"); } function slug(s) { return s.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-"); } /* ---- inline marks --------------------------------------------------- */ /* U+0001 as the placeholder sentinel: it cannot occur in a Markdown source file, so a code span lifted out here can never be confused with document text when it is put back. */ var SENTINEL = "\u0001"; function inline(s, from) { /* Code spans come out first and go back last, so nothing inside a backtick pair is mistaken for emphasis or for a link. */ var spans = []; s = s.replace(/`([^`]+)`/g, function (_, code) { spans.push("" + esc(code) + ""); return SENTINEL + (spans.length - 1) + SENTINEL; }); s = esc(s); s = s.replace(/!\[([^\]]*)\]\(([^)\s]+)[^)]*\)/g, function (_, alt, src) { return '' + alt + ''; }); s = s.replace(/\[([^\]]+)\]\(([^)\s]+)[^)]*\)/g, function (_, text, href) { if (/^https?:/.test(href)) return '' + text + ""; if (href.charAt(0) === "#") return '' + text + ""; if (/\.md($|#)/.test(href)) { var cut = href.indexOf("#"); var hash = cut >= 0 ? href.slice(cut) : ""; var path = cut >= 0 ? href.slice(0, cut) : href; return '' + text + ""; } /* Anything else is a file in the repo rather than a document. Link at it relative to docs/, which is where this page's ../ points. */ return '' + text + ""; }); s = s.replace(/\*\*([^*]+)\*\*/g, "$1"); s = s.replace(/(^|[^*])\*([^*\n]+)\*/g, "$1$2"); var restore = new RegExp(SENTINEL + "(\\d+)" + SENTINEL, "g"); return s.replace(restore, function (_, n) { return spans[+n]; }); } /* ---- blocks --------------------------------------------------------- */ function render(md, from, headings) { md = md.replace(/\r\n?/g, "\n").replace(//g, ""); var L = md.split("\n"), out = [], i = 0, m; function listBlock(ordered) { var tag = ordered ? "ol" : "ul", items = []; while (i < L.length) { var li = L[i].match(ordered ? /^\s*\d+\.\s+(.*)$/ : /^\s*[-*+]\s+(.*)$/); if (!li) { /* an indented continuation line belongs to the item above it */ if (items.length && /^\s+\S/.test(L[i])) { items[items.length - 1] += " " + L[i].trim(); i++; continue; } break; } items.push(li[1]); i++; } out.push("<" + tag + ">"); items.forEach(function (t) { out.push("
  • " + inline(t, from) + "
  • "); }); out.push(""); } function cells(row) { return row.replace(/^\s*\|/, "").replace(/\|\s*$/, "").split("|") .map(function (c) { return c.trim(); }); } while (i < L.length) { var line = L[i]; if (!line.trim()) { i++; continue; } /* fenced code */ if (/^\s*```/.test(line)) { var lang = line.replace(/^\s*```/, "").trim(), buf = []; i++; while (i < L.length && !/^\s*```/.test(L[i])) buf.push(L[i++]); i++; out.push('
    " + esc(buf.join("\n")) + "
    "); continue; } /* table: a pipe row followed by a separator row */ if (/^\s*\|/.test(line) && /^\s*\|?[\s:|-]+$/.test(L[i + 1] || "") && /-/.test(L[i + 1] || "")) { var head = cells(line); i += 2; out.push('
    '); head.forEach(function (c) { out.push(""); }); out.push(""); while (i < L.length && /^\s*\|/.test(L[i])) { out.push(""); cells(L[i++]).forEach(function (c) { out.push(""); }); out.push(""); } out.push("
    " + inline(c, from) + "
    " + inline(c, from) + "
    "); continue; } /* heading */ if ((m = line.match(/^(#{1,6})\s+(.*?)\s*#*$/))) { var lvl = m[1].length, text = m[2], id = slug(text); if (lvl <= 3) headings.push({ level: lvl, text: text, id: id }); out.push("' + inline(text, from) + ""); i++; continue; } /* rule */ if (/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) { out.push("
    "); i++; continue; } /* blockquote */ if (/^\s*>/.test(line)) { var q = []; while (i < L.length && /^\s*>/.test(L[i])) q.push(L[i++].replace(/^\s*>\s?/, "")); out.push("
    " + render(q.join("\n"), from, []) + "
    "); continue; } if (/^\s*[-*+]\s+/.test(line)) { listBlock(false); continue; } if (/^\s*\d+\.\s+/.test(line)) { listBlock(true); continue; } /* paragraph: runs to the next blank line or block opener */ var para = []; while (i < L.length && L[i].trim() && !/^\s*(```|>|#{1,6}\s|[-*+]\s|\d+\.\s|\|)/.test(L[i]) && !/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(L[i])) { para.push(L[i++]); } if (para.length) out.push("

    " + inline(para.join(" "), from) + "

    "); else i++; /* a block opener we do not handle: never spin on it */ } return out.join("\n"); } /* ---- the page ------------------------------------------------------- */ var nav = document.getElementById("docnav"); var article = document.getElementById("doc"); var tocEl = document.getElementById("toc"); function buildNav() { DOCS.forEach(function (g) { var h = document.createElement("p"); h.className = "navgroup"; h.textContent = g.group; nav.appendChild(h); var ul = document.createElement("ul"); g.items.forEach(function (it) { var li = document.createElement("li"); var a = document.createElement("a"); a.href = docHref(it[0]); a.textContent = it[1]; a.dataset.path = it[0]; li.appendChild(a); ul.appendChild(li); }); nav.appendChild(ul); }); } function markCurrent(path) { Array.prototype.forEach.call(nav.querySelectorAll("a"), function (a) { a.classList.toggle("on", a.dataset.path === path); }); } function buildToc(headings) { tocEl.innerHTML = ""; var real = headings.filter(function (h) { return h.level >= 2; }); if (real.length < 2) { tocEl.hidden = true; return; } tocEl.hidden = false; var t = document.createElement("p"); t.className = "navgroup"; t.textContent = "On this page"; tocEl.appendChild(t); var ul = document.createElement("ul"); real.forEach(function (h) { var li = document.createElement("li"); li.className = "lvl" + h.level; var a = document.createElement("a"); a.href = "#" + h.id; a.textContent = h.text; li.appendChild(a); ul.appendChild(li); }); tocEl.appendChild(ul); } function fail(title, body) { article.innerHTML = "

    " + title + "

    " + body; tocEl.hidden = true; } function load(path) { markCurrent(path); document.title = "Docs — SchOS"; article.setAttribute("aria-busy", "true"); fetch("../" + path).then(function (r) { if (!r.ok) throw new Error(r.status + " " + r.statusText); return r.text(); }).then(function (text) { var headings = []; article.innerHTML = render(text, path, headings); buildToc(headings); article.removeAttribute("aria-busy"); if (headings.length) document.title = headings[0].text + " — SchOS docs"; var hash = location.hash.slice(1); var target = hash && document.getElementById(hash); if (target) target.scrollIntoView(); else window.scrollTo(0, 0); }).catch(function (e) { article.removeAttribute("aria-busy"); /* The common case, and worth naming precisely: browsers refuse fetch() on file:// for the same reason they refuse it cross-origin, so this one page needs a server even though nothing here is dynamic. */ if (location.protocol === "file:") { fail("This page needs a local server", "

    Browsers block fetch() on file:// URLs, so the " + "reader cannot load the Markdown. The rest of the site works from a file; " + "this page does not. Serve the repository root:

    " + '
    cd /home/nexoniarz/Dokumenty/Projects/SchOS\n' +
                 "python3 -m http.server 8000
    " + "

    Then open http://localhost:8000/docs/Website/docs.html.

    " + '

    Or read this one directly: ' + esc(path) + "

    "); } else { fail("Could not load that document", "

    " + esc(path) + " — " + esc(e.message) + "

    " + '

    Back to the overview

    '); } }); } function pathFromUrl() { var m = /[?&]doc=([^&#]+)/.exec(location.search); var p = m ? decodeURIComponent(m[1]) : "README.md"; /* Only ever load something that is in the index above. */ var known = DOCS.some(function (g) { return g.items.some(function (it) { return it[0] === p; }); }); return known ? p : "README.md"; } buildNav(); load(pathFromUrl()); /* Intercept in-page navigation so the reader does not reload its own shell. */ document.addEventListener("click", function (ev) { var a = ev.target.closest ? ev.target.closest("a") : null; if (!a || ev.metaKey || ev.ctrlKey || ev.shiftKey || a.target) return; var href = a.getAttribute("href") || ""; if (href.indexOf("?doc=") !== 0) return; ev.preventDefault(); history.pushState(null, "", href); load(pathFromUrl()); }); window.addEventListener("popstate", function () { load(pathFromUrl()); }); })();