71 lines
1.7 KiB
Vue
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>
|