52 lines
1.5 KiB
TypeScript
52 lines
1.5 KiB
TypeScript
import type { Root, ElementContent } from 'hast'
|
|
import type { Plugin } from 'unified'
|
|
import { visit } from 'unist-util-visit'
|
|
|
|
/**
|
|
* A rehype plugin that processes text nodes and wraps text enclosed in double equal signs (==) with <mark> tags.
|
|
* For example, "This is ==highlighted==" will be transformed into "This is <mark>highlighted</mark>".
|
|
*/
|
|
export const rehypeMark: Plugin<[], Root> = () => (tree: Root) => {
|
|
visit(tree, 'text', (node: ElementContent, index, parent) => {
|
|
if (!parent || typeof index !== 'number') return
|
|
|
|
const textNode = node as ElementContent & { value: string }
|
|
const regex = /==([^=]+)==/g
|
|
const matches = [...textNode.value.matchAll(regex)]
|
|
|
|
if (matches.length === 0) return
|
|
|
|
const newNodes: ElementContent[] = []
|
|
let lastIndex = 0
|
|
|
|
for (const match of matches) {
|
|
const [fullMatch, innerText] = match
|
|
const matchStartIndex = match.index ?? 0
|
|
|
|
if (matchStartIndex > lastIndex) {
|
|
newNodes.push({
|
|
type: 'text',
|
|
value: textNode.value.slice(lastIndex, matchStartIndex),
|
|
})
|
|
}
|
|
|
|
newNodes.push({
|
|
type: 'element',
|
|
tagName: 'mark',
|
|
properties: {},
|
|
children: [{ type: 'text', value: innerText }],
|
|
})
|
|
|
|
lastIndex = matchStartIndex + fullMatch.length
|
|
}
|
|
|
|
if (lastIndex < textNode.value.length) {
|
|
newNodes.push({
|
|
type: 'text',
|
|
value: textNode.value.slice(lastIndex),
|
|
})
|
|
}
|
|
|
|
parent.children.splice(index, 1, ...newNodes)
|
|
})
|
|
}
|