79 lines
2.1 KiB
TypeScript
79 lines
2.1 KiB
TypeScript
import type { ParsedEdge } from "./types";
|
|
|
|
const EDGE_HEADER_RE = /^\s*(>+)\s*\[!edge\]\s*(.+?)\s*$/i;
|
|
const TARGET_LINK_RE = /\[\[([^\]]+?)\]\]/g;
|
|
|
|
/**
|
|
* Extract edges from any callout nesting:
|
|
* - Edge starts with: > [!edge] <type> (any number of '>' allowed)
|
|
* - Collect targets from subsequent lines while quoteLevel >= edgeLevel
|
|
* - Stop when:
|
|
* a) next [!edge] header appears, OR
|
|
* b) quoteLevel drops below edgeLevel (block ends), ignoring blank lines
|
|
*/
|
|
export function parseEdgesFromCallouts(markdown: string): ParsedEdge[] {
|
|
const lines = markdown.split(/\r?\n/);
|
|
const edges: ParsedEdge[] = [];
|
|
|
|
let current: ParsedEdge | null = null;
|
|
let currentEdgeLevel = 0;
|
|
|
|
const getQuoteLevel = (line: string): number => {
|
|
const m = line.match(/^\s*(>+)/);
|
|
return m && m[1] ? m[1].length : 0;
|
|
};
|
|
|
|
const flush = (endLine: number) => {
|
|
if (!current) return;
|
|
current.lineEnd = endLine;
|
|
if (current.targets.length > 0) edges.push(current);
|
|
current = null;
|
|
currentEdgeLevel = 0;
|
|
};
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
if (line === undefined) continue;
|
|
|
|
// Start of a new edge block
|
|
const edgeMatch = line.match(EDGE_HEADER_RE);
|
|
if (edgeMatch && edgeMatch[1] && edgeMatch[2]) {
|
|
flush(i - 1);
|
|
|
|
currentEdgeLevel = edgeMatch[1].length;
|
|
current = {
|
|
rawType: edgeMatch[2].trim(),
|
|
targets: [],
|
|
lineStart: i,
|
|
lineEnd: i,
|
|
};
|
|
continue;
|
|
}
|
|
|
|
if (!current) continue;
|
|
|
|
const trimmed = line.trim();
|
|
const ql = getQuoteLevel(line);
|
|
|
|
// End of the current edge block if quote level drops below the edge header level
|
|
// (ignore blank lines)
|
|
if (trimmed !== "" && ql < currentEdgeLevel) {
|
|
flush(i - 1);
|
|
continue;
|
|
}
|
|
|
|
// Collect targets (multiple per line allowed)
|
|
TARGET_LINK_RE.lastIndex = 0;
|
|
let m: RegExpExecArray | null;
|
|
while ((m = TARGET_LINK_RE.exec(line)) !== null) {
|
|
if (m[1]) {
|
|
const t = m[1].trim();
|
|
if (t) current.targets.push(t);
|
|
}
|
|
}
|
|
}
|
|
|
|
flush(lines.length - 1);
|
|
return edges;
|
|
}
|