@sveltek/unplugins
Version:
Sveltek's Unified plugins for Markdown preprocessor.
205 lines (198 loc) • 4.77 kB
JavaScript
import { visit } from "unist-util-visit";
export * from "unist-util-visit"
//#region src/remark/toc/index.ts
/**
* A custom `Remark` plugin that creates `Table of Content` (Toc).
*
* Automatically adds a link with the appropriate attributes to the headings.
*
* It also stores Toc items to `frontmatter` for easy access.
*
* @example
*
* ```ts
* import { svelteMarkdown } from '@sveltek/markdown'
* import { remarkToc } from '@sveltek/unplugins'
*
* svelteMarkdown({
* plugins: {
* remark: [remarkToc]
* }
* })
* ```
*
* Or with options:
*
* ```js
* svelteMarkdown({
* plugins: {
* remark: [[remarkToc, { depth: 3 }]]
* }
* })
* ```
*/
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;
};
};
//#endregion
//#region src/remark/reading-stats/stats.ts
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`
};
};
//#endregion
//#region src/remark/reading-stats/index.ts
/**
* A custom `Remark` plugin that creates `Reading Stats`.
*
* Stores reading details to `frontmatter` for easy access.
*
* @example
*
* ```ts
* import { svelteMarkdown } from '@sveltek/markdown'
* import { remarkReadingStats } from '@sveltek/unplugins'
*
* svelteMarkdown({
* plugins: {
* remark: [remarkReadingStats]
* }
* })
* ```
*
* Or with options:
*
* ```js
* svelteMarkdown({
* plugins: {
* remark: [[remarkReadingStats, { wordsPerMinute: 300 }]]
* }
* })
* ```
*/
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 });
};
};
//#endregion
//#region src/rehype/shiki/utils.ts
const isFunction = (v) => v instanceof Function;
//#endregion
//#region src/rehype/shiki/index.ts
/**
* A custom `Rehype` plugin for `Shiki`.
*
* @example
*
* ```js
* import { svelteMarkdown } from '@sveltek/markdown'
* import { rehypeShiki } from '@sveltek/unplugins'
*
* svelteMarkdown({
* plugins: {
* rehype: [rehypeShiki]
* }
* })
* ```
*
* Or with options:
*
* ```js
* svelteMarkdown({
* plugins: {
* rehype: [[rehypeShiki, { theme: 'github-light-default' }]]
* }
* })
* ```
*/
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 { codeToHtml } = await getSingletonHighlighter({
...highlighter,
themes: highlighter?.themes || themes && Object.values(themes) || [defaultTheme],
langs: highlighter?.langs || defaultLangs
});
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
};
return rehypeHighlight.call(this, highlightOptions)?.(tree, file, () => {});
};
};
//#endregion
export { rehypeShiki, remarkReadingStats, remarkToc };