Files
veridian/app/components/Markdown/Renderer.vue
T

91 lines
2.6 KiB
Vue

<script setup lang="ts">
import type { MarkdownAstNode, MarkdownElementNode } from '~/utils/markdown';
import { createMarkdownParser, getNodeAttributes, getNodeChildren, getNodeTag, splitParagraphBreaks, textContent, toVueAttributes } from '~/utils/markdown';
import { createTextVNode, h, ref, watch } from 'vue';
import MarkdownMath from './Math.vue';
import MarkdownShikiHighlight from './ShikiHighlight.vue';
const props = defineProps<{
content: string;
finished: boolean;
id: string;
}>();
const parser = createMarkdownParser();
const parse = async (content: string, finished: boolean) => {
const tree = await parser(content, { streaming: !finished });
return splitParagraphBreaks(tree);
};
const parsed = ref(await parse(props.content, props.finished));
let parseVersion = 0;
watch(
[() => props.content, () => props.finished],
async ([content, finished]) => {
const version = ++parseVersion;
const tree = await parse(content, finished);
if (version === parseVersion) {
parsed.value = tree;
}
},
);
const renderNode = (node: MarkdownAstNode, path: string): any => {
if (typeof node === 'string') {
return createTextVNode(node);
}
if (!Array.isArray(node)) {
return null;
}
if (node[0] === null) {
return null;
}
const tag = getNodeTag(node);
const attributes = getNodeAttributes(node);
if (tag === 'pre') {
const language = typeof attributes.language === 'string' ? attributes.language : 'text';
return h(MarkdownShikiHighlight, {
key: path,
code: textContent(node),
language,
});
}
if (tag === 'math') {
return h(MarkdownMath, {
key: path,
content: typeof attributes.content === 'string' ? attributes.content : textContent(node),
display: typeof attributes.class === 'string' && attributes.class.includes('block'),
});
}
const children = getNodeChildren(node)
.map((child, index) => renderNode(child, `${path}.${index}`))
.filter((child) => child !== null);
return h(
tag,
{ ...toVueAttributes(attributes), key: path },
children,
);
};
const render = () => {
const children = parsed.value.nodes
.map((node, index) => renderNode(node, `${props.id}.${index}`))
.filter((child) => child !== null);
return h('div', { class: 'prose-wrapper' }, [
h('article', { class: 'markdown-body' }, children),
]);
};
</script>
<template>
<render />
</template>