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

71 lines
1.7 KiB
Vue

<script lang="ts" setup>
import { hashSync } from '~/utils/hash';
const props = defineProps<{ code: string; lang: string }>();
const renderId = hashSync(props.code + props.lang);
const { data: html } = useAsyncData<string>(`shiki-${renderId}`, async () => parseCode());
const lineNumberWidth = computed(() => {
if (!html.value) return 1;
// Count newlines in the generated HTML or the source code
// Using props.code is safer and faster than parsing the HTML string
return props.code.split('\n').length.toString().length;
});
watch(() => props.code, async () => {
html.value = await parseCode();
});
async function parseCode() {
const shiki = await getShikiHighlighter();
let lang = props.lang.toLowerCase();
try {
shiki.getLanguage(lang);
} catch {
lang = 'text';
}
return shiki.codeToHtml(props.code.trim(), {
lang,
themes: { dark: 'vitesse-dark', light: 'vitesse-light' },
});
}
</script>
<template>
<div class="rounded-xl overflow-hidden code-container" :style="`--line-number-width: ${lineNumberWidth}ch`"
:id="`code-${renderId}`" v-html="html">
</div>
</template>
<style>
.code-container {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
.code-container>pre {
overflow-x: auto;
scrollbar-width: thin;
padding: 1rem;
line-height: 1.625;
counter-reset: lines;
}
.code-container>pre>code .line::before {
counter-increment: lines;
content: counter(lines);
width: var(--line-number-width);
margin-right: 1.5rem;
display: inline-block;
text-align: right;
}
.dark .code-container>pre>code .line::before {
color: rgba(255, 255, 255, 0.25);
}
.light .code-container>pre>code .line::before {
color: rgba(0, 0, 0, 0.25);
}
</style>