@tanstack/highlight
Version:
Tiny class-based syntax highlighting for documentation.
84 lines (83 loc) • 2.8 kB
JavaScript
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';
}