UNPKG

@sveltek/unplugins

Version:

Sveltek's Unified plugins for Markdown preprocessor.

103 lines (96 loc) 3.31 kB
import { visit } from 'unist-util-visit'; const remarkToc = (options = {}) => { const { depth = 3, links = true } = options; return (tree, vfile) => { const frontmatter = vfile.data.frontmatter; const toc = []; let i = 0; visit(tree, "heading", (node) => { const [child] = node.children; let value = ""; let id = ""; if (child.type === "text") { value = child.value; id = value.toLowerCase().replace(/[^a-z0-9\s-]/g, "").trim().replace(/\s+/g, "-"); } if (links) { node.children = []; node.children.push({ type: "link", url: `#${id}`, children: [{ type: "text", value }] }); } const data = node.data || (node.data = {}); const props = data.hProperties || (data.hProperties = {}); if (node.depth > 1 && node.depth <= depth) { if (toc.some((h) => h.id === id)) id = `${id}-${i + 1}`; if (!props.id) props.id = id; toc.push({ id, depth: node.depth, value }); } }); frontmatter.toc = toc; }; }; const countWords = (text) => (text.match(/\S+/g) || []).length; const readingStats = (text, { wordsPerMinute = 200 } = {}) => { const words = countWords(text); const min = words / wordsPerMinute; const minutes = min < 1 ? 1 : Math.round(min * 10) / 10; return { minutes, words, text: `${minutes} min read` }; }; const remarkReadingStats = (options = {}) => { const { wordsPerMinute } = options; return (tree, vfile) => { const frontmatter = vfile.data.frontmatter; let text = ""; visit(tree, "text", (node) => { text += node.value; }); frontmatter.readingStats = readingStats(text, { wordsPerMinute }); }; }; const isFunction = (v) => v instanceof Function; const rehypeShiki = function(options = {}) { const { theme, themes, langs, highlighter, codeToHtml: codeHtmlOptions, root = (node) => { node.tagName = "div"; } } = options; const defaultTheme = theme || "github-dark-default"; const defaultLangs = langs || ["javascript", "typescript", "svelte"]; return async (tree, file) => { const { rehypeHighlight } = await import('@sveltek/markdown'); const { getSingletonHighlighter } = await import('shiki'); const shikiHighlighter = getSingletonHighlighter({ ...highlighter, themes: highlighter?.themes || themes && Object.values(themes) || [defaultTheme], langs: highlighter?.langs || defaultLangs }); const { codeToHtml } = await shikiHighlighter; const highlightOptions = { highlighter: async ({ code, lang, meta }) => { const parsedMeta = options.parseMeta?.(meta); if (parsedMeta) meta = parsedMeta; const codeHtml = isFunction(codeHtmlOptions) ? codeHtmlOptions({ code, lang, meta }) : codeHtmlOptions; if (code) { return codeToHtml(code, { ...codeHtml, lang: lang || "text", ...themes ? { themes: codeHtml?.themes || themes } : { theme: codeHtml?.theme || defaultTheme } }); } }, root }; const transformer = rehypeHighlight.call(this, highlightOptions); return transformer?.(tree, file, () => { }); }; }; export { rehypeShiki, remarkReadingStats, remarkToc };