2026-01-17 07:27:11 +01:00
|
|
|
/**
|
|
|
|
|
* Section parser: Split markdown by headings and extract wikilinks per section.
|
|
|
|
|
*/
|
|
|
|
|
|
2026-01-17 22:15:11 +01:00
|
|
|
import { normalizeLinkTarget } from "../unresolvedLink/linkHelpers";
|
|
|
|
|
|
2026-01-17 07:27:11 +01:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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: content,
|
|
|
|
|
startLine: currentStartLine,
|
|
|
|
|
endLine: i,
|
|
|
|
|
links: links,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start new section
|
|
|
|
|
const headingLevel = (headingMatch[1]?.length || 0);
|
|
|
|
|
const headingText = (headingMatch[2]?.trim() || "");
|
|
|
|
|
currentSection = {
|
|
|
|
|
heading: headingText,
|
|
|
|
|
headingLevel: headingLevel,
|
|
|
|
|
content: line,
|
|
|
|
|
startLine: i,
|
|
|
|
|
endLine: i + 1,
|
|
|
|
|
links: [],
|
|
|
|
|
};
|
|
|
|
|
currentContent = [line];
|
|
|
|
|
currentStartLine = i;
|
|
|
|
|
} else {
|
|
|
|
|
// Add line to current section
|
|
|
|
|
if (currentSection) {
|
|
|
|
|
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: content,
|
|
|
|
|
startLine: currentStartLine,
|
|
|
|
|
endLine: lines.length,
|
|
|
|
|
links: links,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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]) {
|
2026-01-17 22:15:11 +01:00
|
|
|
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) {
|
|
|
|
|
// Normalize: remove alias (|alias) and heading (#heading)
|
|
|
|
|
const normalized = normalizeLinkTarget(linkPart);
|
|
|
|
|
if (normalized) {
|
|
|
|
|
links.add(normalized);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Normal link format [[link]]
|
|
|
|
|
// Normalize: remove alias (|alias) and heading (#heading)
|
|
|
|
|
const normalized = normalizeLinkTarget(target);
|
|
|
|
|
if (normalized) {
|
|
|
|
|
links.add(normalized);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-17 07:27:11 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Array.from(links).sort(); // Deterministic ordering
|
|
|
|
|
}
|