93 lines
3.1 KiB
JavaScript
93 lines
3.1 KiB
JavaScript
"use strict";
|
|
|
|
// Obsidian injects its own module resolver. Under plain `node --test` it is
|
|
// absent, so guard the require and fall back to an empty base class. This is
|
|
// what keeps the pure helpers below unit-testable without an Obsidian runtime.
|
|
let OB = null;
|
|
try {
|
|
OB = require("obsidian");
|
|
} catch (_) {
|
|
OB = null;
|
|
}
|
|
const PluginBase = OB ? OB.Plugin : class {};
|
|
|
|
const STATIONS = ["Chat box", "ReAct", "Tools", "Memory", "Skills", "Process", "OS"];
|
|
|
|
const DEFAULTS = {
|
|
script: "raw/sources/Webinar script.md",
|
|
coverage: "wiki/script-coverage.md",
|
|
rawDir: "raw/sources",
|
|
wikiSourceDir: "wiki/sources",
|
|
conceptDir: "wiki/concepts",
|
|
};
|
|
|
|
const RAW_PATH_RE = /^\s*-\s*\*\*Raw path:\*\*\s*`([^`]+)`/m;
|
|
|
|
function extractRawPath(text) {
|
|
const m = String(text).match(RAW_PATH_RE);
|
|
return m ? m[1].trim() : null;
|
|
}
|
|
|
|
function derivePipeline({ rawFiles, sourcePages }) {
|
|
// First claim on a raw path wins. A later page claiming the same file is a
|
|
// duplicate claim — real catalog drift — and joins `orphaned` rather than
|
|
// being silently dropped. `orphaned` therefore means "source page not paired
|
|
// with a raw file", whatever the reason.
|
|
const claimed = new Map();
|
|
const duplicates = [];
|
|
for (const page of sourcePages) {
|
|
if (!page.rawPath) continue;
|
|
if (claimed.has(page.rawPath)) duplicates.push(page);
|
|
else claimed.set(page.rawPath, page);
|
|
}
|
|
|
|
const processed = [];
|
|
const unprocessed = [];
|
|
for (const file of rawFiles) {
|
|
const page = claimed.get(file.path);
|
|
if (page) processed.push(Object.assign({}, file, { page }));
|
|
else unprocessed.push(file);
|
|
}
|
|
|
|
// One pass over sourcePages, so a page appears in `orphaned` at most once no
|
|
// matter how many of the three reasons apply to it. Concatenating a separate
|
|
// duplicates array here would double-count a losing claimant whose shared raw
|
|
// path is also missing from disk.
|
|
const rawPaths = new Set(rawFiles.map((f) => f.path));
|
|
const duplicateSet = new Set(duplicates);
|
|
const orphaned = sourcePages.filter(
|
|
(p) => duplicateSet.has(p) || !p.rawPath || !rawPaths.has(p.rawPath)
|
|
);
|
|
|
|
unprocessed.sort((a, b) => a.name.localeCompare(b.name));
|
|
processed.sort((a, b) => b.page.name.localeCompare(a.page.name));
|
|
|
|
return { processed, unprocessed, orphaned };
|
|
}
|
|
|
|
function parseConfig(source) {
|
|
const cfg = Object.assign({}, DEFAULTS);
|
|
for (const line of String(source).split(/\r?\n/)) {
|
|
const m = line.match(/^\s*([A-Za-z][A-Za-z0-9_]*)\s*:\s*(.+?)\s*$/);
|
|
if (m && Object.prototype.hasOwnProperty.call(DEFAULTS, m[1])) {
|
|
cfg[m[1]] = m[2];
|
|
}
|
|
}
|
|
return cfg;
|
|
}
|
|
|
|
class WebinarDashPlugin extends PluginBase {
|
|
async onload() {
|
|
this.registerMarkdownCodeBlockProcessor("webinar-dash", (source, el, ctx) => {
|
|
const cfg = parseConfig(source);
|
|
const root = el.createDiv({ cls: "webinar-dash" });
|
|
root.createDiv({ cls: "wd-eyebrow", text: "Webinar dashboard" });
|
|
root.createEl("p", { text: `Reading coverage from ${cfg.coverage}` });
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = WebinarDashPlugin;
|
|
module.exports.default = WebinarDashPlugin;
|
|
module.exports.__test__ = { parseConfig, extractRawPath, derivePipeline, STATIONS, DEFAULTS };
|