mindnet_obsidian/src/mapping/sectionParser.ts
Lars 725adb5302
Some checks are pending
Node.js build / build (20.x) (push) Waiting to run
Node.js build / build (22.x) (push) Waiting to run
Enhance UI and functionality for Chain Workbench and related features
- Introduced a wide two-column layout for the Chain Workbench modal, improving user experience and accessibility.
- Added new styles for workbench components, including headers, filters, and main containers, to enhance visual organization.
- Updated chain templates to allow for multiple distinct matches per template, improving flexibility in template matching.
- Enhanced documentation to clarify the new settings and commands related to the Chain Workbench and edge detection features.
- Implemented logging for better tracking of missing configurations, ensuring users are informed about any loading issues.
2026-02-05 11:41:15 +01:00

153 lines
5.2 KiB
TypeScript

/**
* Section parser: Split markdown by headings and extract wikilinks per section.
*/
export interface NoteSection {
heading: string | null; // null for content before first heading
headingLevel: number; // 0 for content before first heading
content: string; // Full content of section (including heading)
startLine: number; // Line index where section starts
endLine: number; // Line index where section ends (exclusive)
links: string[]; // Deduplicated wikilinks found in this section
/** WP-26: Section type from `> [!section] type` callout in this section; null if not set. */
sectionType: string | null;
/** WP-26: Block ID from heading line (e.g. `## Title ^block-id`); null if not set. */
blockId: string | null;
}
/** Match `> [!section] type` callout (type = word characters). */
const SECTION_CALLOUT_REGEX = /^\s*>\s*\[!section\]\s*(\S+)\s*$/i;
/** Match block ID at end of heading text: `... ^block-id`. */
const BLOCK_ID_IN_HEADING_REGEX = /\s+\^([a-zA-Z0-9_-]+)\s*$/;
/**
* Split markdown content into sections by headings.
*/
export function splitIntoSections(markdown: string): NoteSection[] {
const lines = markdown.split(/\r?\n/);
const sections: NoteSection[] = [];
let currentSection: NoteSection | null = null;
let currentContent: string[] = [];
let currentStartLine = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line === undefined) continue;
// Check if line is a heading
const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
if (headingMatch) {
// Save previous section if exists
if (currentSection !== null || currentContent.length > 0) {
const content = currentContent.join("\n");
const links = extractWikilinks(content);
sections.push({
heading: currentSection?.heading || null,
headingLevel: currentSection?.headingLevel || 0,
content,
startLine: currentStartLine,
endLine: i,
links,
sectionType: currentSection?.sectionType ?? null,
blockId: currentSection?.blockId ?? null,
});
}
// Start new section: extract blockId from heading text (e.g. "Title ^my-id")
const headingLevel = (headingMatch[1]?.length || 0);
const headingText = (headingMatch[2]?.trim() || "");
const blockIdMatch = headingText.match(BLOCK_ID_IN_HEADING_REGEX);
const blockId = blockIdMatch ? blockIdMatch[1] ?? null : null;
currentSection = {
heading: headingText,
headingLevel,
content: line,
startLine: i,
endLine: i + 1,
links: [],
sectionType: null,
blockId,
};
currentContent = [line];
currentStartLine = i;
} else {
// Add line to current section
if (currentSection) {
// WP-26: Detect `> [!section] type` callout (typically right after heading)
const sectionCalloutMatch = line.match(SECTION_CALLOUT_REGEX);
if (sectionCalloutMatch && sectionCalloutMatch[1]) {
currentSection.sectionType = sectionCalloutMatch[1].trim();
}
currentContent.push(line);
currentSection.content = currentContent.join("\n");
currentSection.endLine = i + 1;
} else {
// Content before first heading
currentContent.push(line);
}
}
}
// Save last section
if (currentSection || currentContent.length > 0) {
const content = currentContent.join("\n");
const links = extractWikilinks(content);
sections.push({
heading: currentSection?.heading || null,
headingLevel: currentSection?.headingLevel || 0,
content,
startLine: currentStartLine,
endLine: lines.length,
links,
sectionType: currentSection?.sectionType ?? null,
blockId: currentSection?.blockId ?? null,
});
}
return sections;
}
/**
* Extract all wikilinks from markdown text (deduplicated).
*/
export function extractWikilinks(markdown: string): string[] {
const links = new Set<string>();
const wikilinkRegex = /\[\[([^\]]+?)\]\]/g;
let match: RegExpExecArray | null;
while ((match = wikilinkRegex.exec(markdown)) !== null) {
if (match[1]) {
const target = match[1].trim();
if (target) {
// Check if it's a rel: link format [[rel:type|link]]
if (target.startsWith("rel:")) {
// Extract link part after |
const parts = target.split("|");
if (parts.length >= 2) {
// It's [[rel:type|link]] format, extract the link part
const linkPart = parts[1] || "";
if (linkPart) {
// Alias entfernen (|), Abschnitt (#) behalten für Edge-Block
const linkTarget = linkPart.split("|")[0]?.trim() || linkPart;
if (linkTarget) {
links.add(linkTarget);
}
}
}
} else {
// Normal link format [[link]] oder [[link#Abschnitt]]
// Alias entfernen (|), Abschnitt (#) behalten für Edge-Block
const linkTarget = target.split("|")[0]?.trim() || target;
if (linkTarget) {
links.add(linkTarget);
}
}
}
}
}
return Array.from(links).sort(); // Deterministic ordering
}