mirror of
https://github.com/HoshinoSuzumi/rayine-ui.git
synced 2025-04-10 04:58:51 +08:00
114 lines
2.9 KiB
Vue
114 lines
2.9 KiB
Vue
<script lang="ts" setup>
|
|
import FileTypeVue from "./icon/VscodeIconsFileTypeVue.vue"
|
|
import FileTypeTypescript from "./icon/VscodeIconsFileTypeTypescriptOfficial.vue"
|
|
import FileTypeJavascript from "./icon/VscodeIconsFileTypeJsOfficial.vue"
|
|
import TablerTerminal from "./icon/TablerTerminal.vue";
|
|
|
|
const hightlighter = useShikiHighlighter();
|
|
|
|
const slots = defineSlots<{
|
|
default?: () => VNode[];
|
|
code?: () => VNode[];
|
|
}>();
|
|
|
|
const IconComponents = {
|
|
'vue': FileTypeVue,
|
|
'vue-html': FileTypeVue,
|
|
'sh': TablerTerminal,
|
|
'ts': FileTypeTypescript,
|
|
'js': FileTypeJavascript,
|
|
}
|
|
|
|
const codeSlotContent = computed(() => {
|
|
if (slots.code) {
|
|
const slotContent = slots.code();
|
|
let contentLines = slotContent
|
|
.map(vnode => vnode.children || '')
|
|
.join('')
|
|
.replace('\n', '') // remove first line break
|
|
.split('\n');
|
|
|
|
// calculate the minimum indent
|
|
const minIndent = contentLines.reduce((min, line) => {
|
|
const match = line.match(/^(\s*)\S/);
|
|
if (match) {
|
|
return Math.min(min, match[1].length);
|
|
}
|
|
return min;
|
|
}, Infinity);
|
|
|
|
// remove the minimum indent from each line
|
|
const stringContent = contentLines.map(line => line.slice(minIndent)).join('\n');
|
|
|
|
return stringContent;
|
|
}
|
|
return '';
|
|
})
|
|
|
|
const props = defineProps({
|
|
filename: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
lang: {
|
|
type: String as PropType<keyof typeof IconComponents>,
|
|
default: '',
|
|
},
|
|
code: {
|
|
type: String,
|
|
default: '',
|
|
}
|
|
})
|
|
|
|
const { data: ast } = await useAsyncData(`${'name'}-ast-${JSON.stringify({ slots: props.slots, code: props.code })}`, async () => {
|
|
let formatted = ''
|
|
try {
|
|
// @ts-ignore
|
|
formatted = await $prettier.format(code.value, {
|
|
trailingComma: 'none',
|
|
semi: false,
|
|
singleQuote: true
|
|
})
|
|
} catch {
|
|
formatted = props.code
|
|
}
|
|
|
|
return parseMarkdown(formatted, {
|
|
highlight: {
|
|
highlighter,
|
|
theme: {
|
|
light: 'material-theme-lighter',
|
|
default: 'material-theme',
|
|
dark: 'material-theme-palenight'
|
|
}
|
|
}
|
|
})
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="border border-neutral-200 dark:border-neutral-700 rounded-lg">
|
|
<div v-if="filename" class="p-4 py-2 border-b border-neutral-200 dark:border-neutral-700">
|
|
<span class="flex items-center gap-1">
|
|
<component v-if="lang" :is="IconComponents[lang]" class="inline" />
|
|
<span class="text-sm text-neutral-500 dark:text-neutral-400">{{ filename }}</span>
|
|
</span>
|
|
</div>
|
|
|
|
<template v-if="slots.default">
|
|
<div :class="['p-4 overflow-auto', $slots.code ? 'border-b border-neutral-200 dark:border-neutral-700' : '']">
|
|
<slot></slot>
|
|
</div>
|
|
</template>
|
|
|
|
<template v-if="slots.code">
|
|
<div class="p-4 overflow-auto">
|
|
<!-- <LazyShiki class="text-sm" :lang="lang" :code="codeSlotContent" /> -->
|
|
<ContentRenderer :value="ast" v-if="ast"/>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped></style>
|