Files
paseo/packages/app/src/file-pane/markdown-preview/document.ts
T
Mohamed Boudra 23d7a8eaf9 Improve Markdown file preview readability (#3240)
* feat(app): improve markdown file previews

Keep rendered documents on the chat reading rail, surface YAML front matter as readable metadata, and make shared Markdown links retain the chat accent and hover treatment.

* fix(app): preserve unsafe frontmatter source

* test(app): cover markdown link behavior
2026-08-12 00:18:30 +02:00

71 lines
1.9 KiB
TypeScript

import { parseDocument, stringify } from "yaml";
export interface MarkdownFrontMatterRow {
key: string;
value: string;
}
export interface MarkdownPreviewDocument {
frontMatter: MarkdownFrontMatterRow[];
body: string;
}
const FRONT_MATTER_PATTERN = /^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/;
export function parseMarkdownPreviewDocument(source: string): MarkdownPreviewDocument {
const match = FRONT_MATTER_PATTERN.exec(source);
if (!match) {
return { frontMatter: [], body: source };
}
try {
return parseFrontMatter(match, source);
} catch {
return { frontMatter: [], body: source };
}
}
function parseFrontMatter(match: RegExpExecArray, source: string): MarkdownPreviewDocument {
const document = parseDocument(match[1], { schema: "core" });
if (document.errors.length > 0) {
return { frontMatter: [], body: source };
}
const metadata = document.toJS({ maxAliasCount: 100 });
if (!isMetadataRecord(metadata)) {
return { frontMatter: [], body: source };
}
const frontMatter = Object.entries(metadata).map(([key, value]) => ({
key,
value: formatFrontMatterValue(value),
}));
return {
frontMatter,
body: source.slice(match[0].length),
};
}
function isMetadataRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function formatFrontMatterValue(value: unknown): string {
if (Array.isArray(value) && value.every(isScalar)) {
return value.map(formatScalar).join(", ");
}
if (isScalar(value)) {
return formatScalar(value);
}
return stringify(value).trim();
}
function isScalar(value: unknown): value is string | number | boolean | null {
return value === null || ["string", "number", "boolean"].includes(typeof value);
}
function formatScalar(value: string | number | boolean | null): string {
return value === null ? "null" : String(value);
}