UNPKG

@tanstack/highlight

Version:

Tiny class-based syntax highlighting for documentation.

84 lines (83 loc) • 2.8 kB
import { codeFenceToHast, } from './markdown.js'; export function rehypeHighlightCodeBlocks(options) { return function transformer(tree) { replacePreCodeNodes(tree, options); }; } export function rehypePreCodeToHast(node, options) { if (hasClassName(node, 'th-code')) return undefined; const code = getCodeChild(node); if (!code) return undefined; const highlighted = codeFenceToHast({ code: collectText(code), decorations: options.getDecorations?.(node), lang: getLanguage(code), lineNumbers: options.lineNumbers, meta: typeof code.data?.meta === 'string' ? code.data.meta : undefined, title: options.getTitle?.(node), }, options.highlighter); const highlightedCode = highlighted.children[0]; return { ...node, ...highlighted, properties: { ...node.properties, ...highlighted.properties, className: [...getClassNames(node), ...getClassNames(highlighted)], }, children: [ { ...code, ...highlightedCode, properties: { ...code.properties, ...highlightedCode.properties }, }, ], }; } function replacePreCodeNodes(node, options) { const children = node.children; if (!children) return; for (let index = 0; index < children.length; index++) { const child = children[index]; if (isElement(child) && child.tagName === 'pre') { const highlighted = rehypePreCodeToHast(child, options); if (highlighted) children[index] = highlighted; continue; } replacePreCodeNodes(child, options); } } function getCodeChild(node) { return node.children.find((child) => isElement(child) && child.tagName === 'code'); } function getClassNames(node) { const className = node.properties?.className; return Array.isArray(className) ? className.filter((value) => typeof value === 'string') : typeof className === 'string' ? className.split(/\s+/).filter(Boolean) : []; } function getLanguage(node) { return getClassNames(node) .find((value) => value.startsWith('language-')) ?.slice('language-'.length); } function hasClassName(node, expected) { return getClassNames(node).includes(expected); } function collectText(node) { if (node.type === 'text' && typeof node.value === 'string') return node.value; if ('children' in node && Array.isArray(node.children)) { return node.children.map((child) => collectText(child)).join(''); } return ''; } function isElement(node) { return node.type === 'element' && typeof node.tagName === 'string'; }