Files

72 lines
2.1 KiB
TypeScript

import { useState, useEffect } from 'react';
import { marked, Renderer } from 'marked';
import { CustomSlugger } from '../utils/markdownUtils.ts';
interface TocItem {
id: string;
level: number;
text: string;
}
interface HeadingToken {
type: 'heading';
raw: string;
depth: number;
text: string;
}
export const useMarkdown = (fileUrl: string) => {
const [markdownHtml, setMarkdownHtml] = useState('');
const [toc, setToc] = useState<TocItem[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
const renderer = new Renderer();
const rendererSlugger = new CustomSlugger();
(renderer as any).heading = (text: string, level: number, raw: string) => {
let headingContent = text;
if (typeof text !== 'string') {
headingContent = marked.parseInline(String(raw || ''), { async: false }) as string;
}
const id = rendererSlugger.slug(raw || text);
return `<h${level} id="${id}" class="scroll-mt-24">${headingContent}</h${level}>`;
};
const fetchAndParseMarkdown = async () => {
try {
const response = await fetch(fileUrl);
if (!response.ok) {
throw new Error(`Failed to load ${fileUrl}: ${response.statusText}`);
}
const text = await response.text();
const tokens = marked.lexer(text);
const tocSlugger = new CustomSlugger();
const tocItems = tokens
.filter((token): token is HeadingToken => (token as any).type === 'heading')
.map((token) => ({
id: tocSlugger.slug(token.raw || token.text),
level: token.depth,
text: token.text,
}));
setToc(tocItems);
const html = await marked.parse(text, { renderer, gfm: true });
setMarkdownHtml(html);
} catch (err: any) {
console.error(`Error loading ${fileUrl}:`, err);
setError(err.message);
} finally {
setIsLoading(false);
}
};
fetchAndParseMarkdown();
}, [fileUrl]);
return { markdownHtml, toc, isLoading, error };
};