Something went wrong. Try again.
a reactive (signals based) hypermedia web framework (wip) stormlightlabs.github.io/volt
hypermedia frontend signals
Something went wrong. Try again.
2.9 kB · 114 lines
TypeScript
at commit ad65ebb7
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115import { createHash } from "node:crypto";
/** * Represents a section extracted from markdown */export type Section = { heading: string; content: string; hash: string };
/** * Result of diffing two sets of sections */export type SectionDiff = { added: number; removed: number; edited: number };
/** * Extract all ## and ### headings from markdown content */export function extractSections(markdown: string): Section[] { const lines = markdown.split("\n"); const sections: Section[] = []; let currentSection: { heading: string; lines: string[] } | undefined = undefined;
for (const line of lines) { const trimmed = line.trim();
if (trimmed.startsWith("## ") || trimmed.startsWith("### ")) { if (currentSection) { const content = currentSection.lines.join("\n").trim(); sections.push({ heading: currentSection.heading, content, hash: hashContent(content) }); }
currentSection = { heading: trimmed, lines: [] }; } else if (currentSection) { currentSection.lines.push(line); } }
if (currentSection) { const content = currentSection.lines.join("\n").trim(); sections.push({ heading: currentSection.heading, content, hash: hashContent(content) }); }
return sections;}
/** * Compare two sets of sections (matched by heading text) and calculate the diff */export function diffSections(oldSections: Section[], newSections: Section[]): SectionDiff { const oldMap = new Map(oldSections.map((s) => [s.heading, s])); const newMap = new Map(newSections.map((s) => [s.heading, s]));
let added = 0; let removed = 0; let edited = 0;
for (const [heading, newSection] of newMap) { const oldSection = oldMap.get(heading);
if (!oldSection) { added++; } else if (oldSection.hash !== newSection.hash) { edited++; } }
// Find removed sections for (const heading of oldMap.keys()) { if (!newMap.has(heading)) { removed++; } }
return { added, removed, edited };}
/** * Hash content using SHA-256 */function hashContent(content: string): string { return createHash("sha256").update(content).digest("hex");}
/** * Extract section headings as a simple string array */export function extractHeadings(markdown: string): string[] { return extractSections(markdown).map((s) => s.heading);}
/** * Hash entire markdown content (without frontmatter) */export function hashMarkdown(markdown: string): string { const withoutFrontmatter = stripFrontmatter(markdown); return hashContent(withoutFrontmatter);}
/** * Remove YAML frontmatter from markdown */function stripFrontmatter(markdown: string): string { const lines = markdown.split("\n");
if (lines[0]?.trim() !== "---") { return markdown; }
for (let i = 1; i < lines.length; i++) { if (lines[i].trim() === "---") { return lines.slice(i + 1).join("\n"); } }
return markdown;}