- Updated `parseEdgesFromCallouts.ts` to support edge extraction from both callout blocks and plain lines, allowing for more flexible edge definitions. - Introduced new settings in `settings.ts` for maximum assignments collected per template, enhancing loop protection during edge processing. - Enhanced documentation in `Entwicklerhandbuch.md` to reflect changes in edge parsing and settings. - Improved UI components to utilize the new settings for better user experience in the Chain Workbench and related features.
113 lines
3.5 KiB
TypeScript
113 lines
3.5 KiB
TypeScript
import type { ParsedEdge } from "./types";
|
||
|
||
// Match edge header: allow spaces between '>' characters (Obsidian uses "> >" for nesting)
|
||
const EDGE_HEADER_RE = /^\s*((?:>\s*)+)\s*\[!edge\]\s*(.+?)\s*$/i;
|
||
// Match edge header WITHOUT leading '>' (plain line) – so edges outside callout blocks are also found
|
||
const EDGE_HEADER_PLAIN_RE = /^\s*\[!edge\]\s+(.+?)\s*$/i;
|
||
const TARGET_LINK_RE = /\[\[([^\]]+?)\]\]/g;
|
||
|
||
/** Sentinel: edge block has no quote level (plain lines); only ends on next edge line or end. */
|
||
const PLAIN_EDGE_LEVEL = -1;
|
||
|
||
/**
|
||
* Extract edges from any callout nesting and from plain lines:
|
||
* - Edge with callout: > [!edge] <type> (any number of '>' allowed)
|
||
* - Edge without callout: [!edge] <type> (so edges outside abstract block are found)
|
||
* - Collect targets from subsequent lines while quoteLevel >= edgeLevel (or plain mode)
|
||
* - Stop when:
|
||
* a) next [!edge] header appears, OR
|
||
* b) quoteLevel drops below edgeLevel (block ends), ignoring blank lines, OR
|
||
* c) in plain mode: next [!edge] line
|
||
*/
|
||
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*(?:(?:>\s*)+)/);
|
||
if (!m || !m[0]) return 0;
|
||
return (m[0].match(/>/g) || []).length;
|
||
};
|
||
|
||
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 (with callout quote)
|
||
const edgeMatch = line.match(EDGE_HEADER_RE);
|
||
if (edgeMatch && edgeMatch[1] && edgeMatch[2]) {
|
||
flush(i - 1);
|
||
currentEdgeLevel = (edgeMatch[1].match(/>/g) || []).length;
|
||
current = {
|
||
rawType: edgeMatch[2].trim(),
|
||
targets: [],
|
||
lineStart: i,
|
||
lineEnd: i,
|
||
};
|
||
continue;
|
||
}
|
||
|
||
// Start of edge block without '>' (plain line) – e.g. edge written outside abstract block
|
||
const plainMatch = line.match(EDGE_HEADER_PLAIN_RE);
|
||
if (plainMatch && plainMatch[1]) {
|
||
flush(i - 1);
|
||
currentEdgeLevel = PLAIN_EDGE_LEVEL;
|
||
current = {
|
||
rawType: plainMatch[1].trim(),
|
||
targets: [],
|
||
lineStart: i,
|
||
lineEnd: i,
|
||
};
|
||
continue;
|
||
}
|
||
|
||
if (!current) continue;
|
||
|
||
const trimmed = line.trim();
|
||
const ql = getQuoteLevel(line);
|
||
|
||
// In plain mode, only end on next edge line (already handled above); collect [[links]] from any line
|
||
if (currentEdgeLevel === PLAIN_EDGE_LEVEL) {
|
||
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);
|
||
}
|
||
}
|
||
continue;
|
||
}
|
||
|
||
// End of quoted edge block if quote level drops below the edge header level
|
||
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;
|
||
}
|