"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); } const rawPaths = new Set(rawFiles.map((f) => f.path)); const orphaned = sourcePages .filter((p) => !p.rawPath || !rawPaths.has(p.rawPath)) .concat(duplicates); 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 };