|
|
import type { Plugin } from 'unified'; |
|
|
import { visit } from 'unist-util-visit'; |
|
|
import type { Break, Content, Paragraph, PhrasingContent, Root, Text } from 'mdast'; |
|
|
import { LINE_BREAK, NBSP, PHRASE_PARENTS, TAB_AS_SPACES } from '$lib/constants/literal-html'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function preserveIndent(line: string): string { |
|
|
let index = 0; |
|
|
let output = ''; |
|
|
|
|
|
while (index < line.length) { |
|
|
const char = line[index]; |
|
|
|
|
|
if (char === ' ') { |
|
|
output += NBSP; |
|
|
index += 1; |
|
|
continue; |
|
|
} |
|
|
|
|
|
if (char === '\t') { |
|
|
output += TAB_AS_SPACES; |
|
|
index += 1; |
|
|
continue; |
|
|
} |
|
|
|
|
|
break; |
|
|
} |
|
|
|
|
|
return output + line.slice(index); |
|
|
} |
|
|
|
|
|
function createLiteralChildren(value: string): PhrasingContent[] { |
|
|
const lines = value.split(LINE_BREAK); |
|
|
const nodes: PhrasingContent[] = []; |
|
|
|
|
|
for (const [lineIndex, rawLine] of lines.entries()) { |
|
|
if (lineIndex > 0) { |
|
|
nodes.push({ type: 'break' } as Break as unknown as PhrasingContent); |
|
|
} |
|
|
|
|
|
nodes.push({ |
|
|
type: 'text', |
|
|
value: preserveIndent(rawLine) |
|
|
} as Text as unknown as PhrasingContent); |
|
|
} |
|
|
|
|
|
if (!nodes.length) { |
|
|
nodes.push({ type: 'text', value: '' } as Text as unknown as PhrasingContent); |
|
|
} |
|
|
|
|
|
return nodes; |
|
|
} |
|
|
|
|
|
export const remarkLiteralHtml: Plugin<[], Root> = () => { |
|
|
return (tree) => { |
|
|
visit(tree, 'html', (node, index, parent) => { |
|
|
if (!parent || typeof index !== 'number') { |
|
|
return; |
|
|
} |
|
|
|
|
|
const replacement = createLiteralChildren(node.value); |
|
|
|
|
|
if (!PHRASE_PARENTS.has(parent.type as string)) { |
|
|
const paragraph: Paragraph = { |
|
|
type: 'paragraph', |
|
|
children: replacement as Paragraph['children'], |
|
|
data: { literalHtml: true } |
|
|
}; |
|
|
|
|
|
const siblings = parent.children as unknown as Content[]; |
|
|
siblings.splice(index, 1, paragraph as unknown as Content); |
|
|
|
|
|
if (index > 0) { |
|
|
const previous = siblings[index - 1] as Paragraph | undefined; |
|
|
|
|
|
if ( |
|
|
previous?.type === 'paragraph' && |
|
|
(previous.data as { literalHtml?: boolean } | undefined)?.literalHtml |
|
|
) { |
|
|
const prevChildren = previous.children as unknown as PhrasingContent[]; |
|
|
|
|
|
if (prevChildren.length) { |
|
|
const lastChild = prevChildren[prevChildren.length - 1]; |
|
|
|
|
|
if (lastChild.type !== 'break') { |
|
|
prevChildren.push({ |
|
|
type: 'break' |
|
|
} as Break as unknown as PhrasingContent); |
|
|
} |
|
|
} |
|
|
|
|
|
prevChildren.push(...(paragraph.children as unknown as PhrasingContent[])); |
|
|
|
|
|
siblings.splice(index, 1); |
|
|
|
|
|
return index; |
|
|
} |
|
|
} |
|
|
|
|
|
return index + 1; |
|
|
} |
|
|
|
|
|
(parent.children as unknown as PhrasingContent[]).splice( |
|
|
index, |
|
|
1, |
|
|
...(replacement as unknown as PhrasingContent[]) |
|
|
); |
|
|
|
|
|
return index + replacement.length; |
|
|
}); |
|
|
}; |
|
|
}; |
|
|
|