Initial commit: Null DRM Official
Capture, decrypt, and restream toolkit with compiled-in app modules (RTE, TG4, BBC), on-device MITM proxy, streamd control plane, and www. BBC module.yaml is published (clear streams); other module values stay local.
This commit is contained in:
commit
2fa8f2435f
121 changed files with 17802 additions and 0 deletions
163
www/docs.js
Normal file
163
www/docs.js
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
(() => {
|
||||
const OWNER = "nulldrm";
|
||||
const REPO = "Null-DRM-Official";
|
||||
const BRANCH = "main";
|
||||
const API = "/git-api/api/v1/repos/" + OWNER + "/" + REPO;
|
||||
const FORGEJO = "https://git.nulldrm.com/" + OWNER + "/" + REPO;
|
||||
const DEFAULT_DOC = "README.md";
|
||||
const FALLBACK = [
|
||||
"README.md",
|
||||
"quickstart.md",
|
||||
"architecture.md",
|
||||
"modules.md",
|
||||
"capture.md",
|
||||
"streamd.md",
|
||||
"providers/rte.md",
|
||||
"providers/tg4.md",
|
||||
"providers/bbc.md",
|
||||
];
|
||||
// Order the nav the way the docs are meant to be read.
|
||||
const ORDER = FALLBACK;
|
||||
|
||||
const listEl = document.getElementById("docs-list");
|
||||
const contentEl = document.getElementById("docs-content");
|
||||
const statusEl = document.getElementById("docs-status");
|
||||
const titleEl = document.getElementById("docs-title");
|
||||
const sourceEl = document.getElementById("docs-source");
|
||||
function currentFile() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const f = (params.get("f") || DEFAULT_DOC).trim();
|
||||
// Allow one level of subdirectory (providers/tg4.md) but nothing escaping docs/.
|
||||
if (!f || f.includes("..") || f.includes("\\")) return DEFAULT_DOC;
|
||||
if (!/^[\w.-]+(?:\/[\w.-]+)?\.md$/i.test(f)) return DEFAULT_DOC;
|
||||
return f;
|
||||
}
|
||||
|
||||
// Keep "/" readable in the path; only the segments need escaping.
|
||||
function encodePath(name) {
|
||||
return name.split("/").map(encodeURIComponent).join("/");
|
||||
}
|
||||
|
||||
function forgejoSrc(name) {
|
||||
return FORGEJO + "/src/branch/" + BRANCH + "/docs/" + encodePath(name);
|
||||
}
|
||||
|
||||
function forgejoRaw(name) {
|
||||
return "/git-api/" + OWNER + "/" + REPO + "/raw/branch/" + BRANCH + "/docs/" + encodePath(name);
|
||||
}
|
||||
|
||||
function setActiveNav(name) {
|
||||
if (!listEl) return;
|
||||
listEl.querySelectorAll("a").forEach((a) => {
|
||||
a.classList.toggle("active", a.getAttribute("data-file") === name);
|
||||
});
|
||||
}
|
||||
|
||||
function renderNav(files) {
|
||||
if (!listEl) return;
|
||||
listEl.innerHTML = files.map((name) => {
|
||||
const href = "docs.html?f=" + encodeURIComponent(name);
|
||||
return "<li><a href=\"" + href + "\" data-file=\"" + name.replace(/"/g, "") + "\">" + name + "</a></li>";
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function rewriteDocLinks(root) {
|
||||
root.querySelectorAll("a[href]").forEach((a) => {
|
||||
const href = a.getAttribute("href") || "";
|
||||
if (/^https?:\/\//i.test(href) || href.startsWith("#") || href.startsWith("mailto:")) return;
|
||||
const clean = href.replace(/^\.\//, "");
|
||||
if (/^[\w.-]+(?:\/[\w.-]+)?\.md(?:#.*)?$/i.test(clean)) {
|
||||
const [file, hash] = clean.split("#");
|
||||
a.setAttribute("href", "docs.html?f=" + encodeURIComponent(file) + (hash ? "#" + hash : ""));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function dirDocs(path) {
|
||||
const res = await fetch(API + "/contents/" + path + "?ref=" + encodeURIComponent(BRANCH));
|
||||
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||
const entries = await res.json();
|
||||
return Array.isArray(entries) ? entries : [];
|
||||
}
|
||||
|
||||
async function listDocs() {
|
||||
try {
|
||||
const entries = await dirDocs("docs");
|
||||
const files = entries
|
||||
.filter((e) => e && e.type === "file" && /\.md$/i.test(e.name || ""))
|
||||
.map((e) => e.name);
|
||||
|
||||
// Pull in one level of subdirectories, e.g. docs/providers/.
|
||||
const dirs = entries.filter((e) => e && e.type === "dir" && e.name);
|
||||
for (const d of dirs) {
|
||||
try {
|
||||
const sub = await dirDocs("docs/" + d.name);
|
||||
sub
|
||||
.filter((e) => e && e.type === "file" && /\.md$/i.test(e.name || ""))
|
||||
.forEach((e) => files.push(d.name + "/" + e.name));
|
||||
} catch {
|
||||
/* a subdirectory we cannot list is not fatal */
|
||||
}
|
||||
}
|
||||
|
||||
files.sort((a, b) => {
|
||||
const ia = ORDER.indexOf(a);
|
||||
const ib = ORDER.indexOf(b);
|
||||
if (ia !== -1 || ib !== -1) {
|
||||
return (ia === -1 ? ORDER.length : ia) - (ib === -1 ? ORDER.length : ib);
|
||||
}
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
return files.length ? files : FALLBACK;
|
||||
} catch {
|
||||
return FALLBACK;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDoc(name) {
|
||||
if (titleEl) titleEl.textContent = name;
|
||||
if (sourceEl) {
|
||||
sourceEl.href = forgejoSrc(name);
|
||||
sourceEl.textContent = "View on Forgejo";
|
||||
}
|
||||
setActiveNav(name);
|
||||
if (statusEl) {
|
||||
statusEl.hidden = false;
|
||||
statusEl.textContent = "Loading " + name + "…";
|
||||
}
|
||||
if (contentEl) contentEl.hidden = true;
|
||||
|
||||
try {
|
||||
const res = await fetch(forgejoRaw(name));
|
||||
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||
const md = await res.text();
|
||||
if (typeof marked === "undefined" || !marked.parse) {
|
||||
throw new Error("marked missing");
|
||||
}
|
||||
marked.setOptions({ gfm: true, breaks: false });
|
||||
contentEl.innerHTML = marked.parse(md);
|
||||
rewriteDocLinks(contentEl);
|
||||
statusEl.hidden = true;
|
||||
contentEl.hidden = false;
|
||||
document.title = name + " — NULLDRM Docs";
|
||||
} catch {
|
||||
if (statusEl) {
|
||||
statusEl.innerHTML =
|
||||
"Could not load <code>" + name + "</code>. " +
|
||||
"Open it on <a href=\"" + forgejoSrc(name) + "\" rel=\"noopener\">Forgejo</a>.";
|
||||
statusEl.hidden = false;
|
||||
}
|
||||
if (contentEl) contentEl.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
async function boot() {
|
||||
const files = await listDocs();
|
||||
renderNav(files);
|
||||
let file = currentFile();
|
||||
if (files.indexOf(file) === -1) file = files[0] || DEFAULT_DOC;
|
||||
await loadDoc(file);
|
||||
}
|
||||
|
||||
boot();
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue