import type {SembleCard, SembleCollection} from "../semble/types"; export interface ParsedSource { cards: SembleCard[]; collections: SembleCollection[]; } export function parseMarkdownSource(sourceId: string, text: string): ParsedSource { const lines = text.split(/\r?\n/); const cards: SembleCard[] = []; const collections: SembleCollection[] = []; let collection: string | undefined; for (let index = 0; index < lines.length; index += 1) { const line = lines[index]; if (!line) continue; const headerMatch = line.match(/^#\s+(.+?)\s*$/); if (headerMatch) { collection = headerMatch[1]?.trim(); if (collection) { const {description, nextIndex} = parseCollectionDescription(lines, index + 1); collections.push({ name: collection, description, sourceId, line: index + 1 }); if (nextIndex > index + 1) { index = nextIndex - 1; } } continue; } const listMatch = line.match(/^\s*[-*]\s+(.+)$/); const candidate = listMatch ? listMatch[1] ?? "" : line.trim(); const parsed = parseCardLine(candidate); if (!parsed) continue; const cardId = `${sourceId}#L${index + 1}:${parsed.url}`; cards.push({ ...parsed, collection, sourceId, line: index + 1, id: cardId }); } return {cards, collections}; } function parseCollectionDescription(lines: string[], startIndex: number): { description?: string; nextIndex: number; } { const descriptionParts: string[] = []; let index = startIndex; for (; index < lines.length; index += 1) { const line = lines[index] ?? ""; const trimmed = line.trim(); if (!trimmed) { if (descriptionParts.length > 0) { index += 1; } break; } if (/^#\s+/.test(trimmed) || /^\s*[-*]\s+/.test(trimmed)) { break; } descriptionParts.push(trimmed); } const description = descriptionParts.length > 0 ? descriptionParts.join(" ") : undefined; return {description, nextIndex: index}; } function parseCardLine(text: string): Omit | null { const trimmed = text.trim(); if (!trimmed) return null; const splitIndex = trimmed.indexOf(" : "); const linkPart = splitIndex >= 0 ? trimmed.slice(0, splitIndex).trim() : trimmed; const notePart = splitIndex >= 0 ? trimmed.slice(splitIndex + 3).trim() : undefined; const markdownLink = linkPart.match(/^\[(.+?)\]\((.+?)\)$/); if (markdownLink) { const title = markdownLink[1]?.trim(); const url = markdownLink[2]?.trim(); if (!url) return null; return { url, title: title || undefined, note: notePart || undefined }; } const angleLink = linkPart.match(/^<([^>]+)>$/); if (angleLink) { const url = angleLink[1]?.trim(); if (!url) return null; return { url, note: notePart || undefined }; } if (!/^(https?:\/\/|at:\/\/)\S+$/i.test(linkPart)) { return null; } return { url: linkPart, note: notePart || undefined }; }