- 新增 WikiView 页面:输入标题加载页面,支持内部词条跳转与返回栈 - 新增 WikiNodeRenderer 递归渲染器:标题、段落、加粗斜体下划线删除线、高亮、行内代码、黑幕、分隔线、内外链、有序无序列表、引用、代码块、表格、附件、提示块 - 新增 WikiNotice 提示块与 WikiAttachmentNode 附件组件,图片支持点击查看大图 - 补充维基节点与附件类型定义,新增附件查询接口与附件内容 URL 拼接 - 主页导航 Wiki 卡片改为站内打开 - 中英文各补 10 个文案
This commit is contained in:
1 parent
febbd84ae7
commit
71b8d111fb
10 files changed
+590
-6
No files matched your search
@@ -0,0 +1,185 @@
|
||||
<script setup lang="ts">
|
||||
import { type SkinSiteWikiNode } from '@/helpers/skin-site-api'
|
||||
|
||||
import { safeExternalUrl, safeWikiColor } from './wiki-color'
|
||||
|
||||
defineOptions({ name: 'WikiNodeRenderer' })
|
||||
|
||||
defineProps<{ nodes: SkinSiteWikiNode[] }>()
|
||||
const emit = defineEmits<{ (e: 'navigate', title: string): void }>()
|
||||
|
||||
function headingTag(level?: number | null): string {
|
||||
return 'h' + Math.min(6, Math.max(1, level ?? 2))
|
||||
}
|
||||
|
||||
function navigate(title?: string | null) {
|
||||
emit('navigate', (title ?? '').replaceAll('_', ' '))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-for="(node, index) in nodes" :key="node.type + '-' + index">
|
||||
<component
|
||||
:is="headingTag(node.level)"
|
||||
v-if="node.type === 'heading'"
|
||||
class="wiki-heading scroll-mt-24"
|
||||
:style="{ textAlign: node.align ?? undefined }"
|
||||
>
|
||||
<WikiNodeRenderer :nodes="node.children" @navigate="navigate" />
|
||||
</component>
|
||||
<p
|
||||
v-else-if="node.type === 'paragraph'"
|
||||
class="my-3 leading-7 break-words whitespace-pre-wrap"
|
||||
:style="{ textAlign: node.align ?? undefined }"
|
||||
>
|
||||
<WikiNodeRenderer :nodes="node.children" @navigate="navigate" />
|
||||
</p>
|
||||
<strong v-else-if="node.type === 'strong'" class="font-bold">
|
||||
<WikiNodeRenderer :nodes="node.children" @navigate="navigate" />
|
||||
</strong>
|
||||
<small v-else-if="node.type === 'small'">
|
||||
<WikiNodeRenderer :nodes="node.children" @navigate="navigate" />
|
||||
</small>
|
||||
<span
|
||||
v-else-if="node.type === 'color'"
|
||||
:style="{ color: safeWikiColor(node.color ?? '') ?? undefined }"
|
||||
>
|
||||
<WikiNodeRenderer :nodes="node.children" @navigate="navigate" />
|
||||
</span>
|
||||
<em v-else-if="node.type === 'emphasis'" class="italic">
|
||||
<WikiNodeRenderer :nodes="node.children" @navigate="navigate" />
|
||||
</em>
|
||||
<u v-else-if="node.type === 'underline'">
|
||||
<WikiNodeRenderer :nodes="node.children" @navigate="navigate" />
|
||||
</u>
|
||||
<s v-else-if="node.type === 'strike'">
|
||||
<WikiNodeRenderer :nodes="node.children" @navigate="navigate" />
|
||||
</s>
|
||||
<mark v-else-if="node.type === 'highlight'" class="rounded bg-yellow/25 px-0.5 text-contrast">
|
||||
<WikiNodeRenderer :nodes="node.children" @navigate="navigate" />
|
||||
</mark>
|
||||
<code v-else-if="node.type === 'inlineCode'" class="rounded bg-button-bg px-1 font-mono text-sm">
|
||||
<WikiNodeRenderer :nodes="node.children" @navigate="navigate" />
|
||||
</code>
|
||||
<span
|
||||
v-else-if="node.type === 'spoiler'"
|
||||
class="cursor-help rounded bg-contrast text-contrast transition-colors hover:bg-transparent hover:text-inherit"
|
||||
:title="node.tooltip || '我知道你在偷看'"
|
||||
tabindex="0"
|
||||
>
|
||||
<WikiNodeRenderer :nodes="node.children" @navigate="navigate" />
|
||||
</span>
|
||||
<br v-else-if="node.type === 'break'" />
|
||||
<hr v-else-if="node.type === 'rule'" class="my-6 border-surface-5" />
|
||||
<button
|
||||
v-else-if="node.type === 'internalLink'"
|
||||
type="button"
|
||||
class="font-medium underline-offset-2 hover:underline"
|
||||
:class="node.exists === false ? 'text-red' : 'text-brand'"
|
||||
:title="node.exists === false ? '该词条尚未创建' : undefined"
|
||||
@click="navigate(node.title)"
|
||||
>
|
||||
<WikiNodeRenderer v-if="node.children.length" :nodes="node.children" @navigate="navigate" />
|
||||
<template v-else>{{ node.text }}</template>
|
||||
</button>
|
||||
<a
|
||||
v-else-if="node.type === 'externalLink'"
|
||||
class="text-brand underline-offset-2 hover:underline"
|
||||
:href="safeExternalUrl(node.href)"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<WikiNodeRenderer v-if="node.children.length" :nodes="node.children" @navigate="navigate" />
|
||||
<template v-else>{{ node.text }}</template>
|
||||
</a>
|
||||
<ul v-else-if="node.type === 'unorderedList'" class="my-3 list-disc pl-7">
|
||||
<WikiNodeRenderer :nodes="node.children" @navigate="navigate" />
|
||||
</ul>
|
||||
<ol
|
||||
v-else-if="node.type === 'orderedList'"
|
||||
:start="node.start ?? undefined"
|
||||
class="my-3 list-decimal pl-7"
|
||||
>
|
||||
<WikiNodeRenderer :nodes="node.children" @navigate="navigate" />
|
||||
</ol>
|
||||
<li v-else-if="node.type === 'listItem'" class="my-1">
|
||||
<WikiNodeRenderer :nodes="node.children" @navigate="navigate" />
|
||||
</li>
|
||||
<blockquote
|
||||
v-else-if="node.type === 'quote'"
|
||||
class="my-4 border-l-4 border-brand/60 bg-brand/5 px-4 py-2"
|
||||
>
|
||||
<WikiNodeRenderer :nodes="node.children" @navigate="navigate" />
|
||||
</blockquote>
|
||||
<pre
|
||||
v-else-if="node.type === 'code'"
|
||||
class="my-4 overflow-x-auto rounded-lg border border-solid border-surface-5 bg-bg-raised p-4 font-mono text-sm"
|
||||
><code>{{ node.text }}</code></pre>
|
||||
<div v-else-if="node.type === 'table'" class="my-5 overflow-x-auto">
|
||||
<table class="w-full border-collapse text-sm">
|
||||
<tbody>
|
||||
<WikiNodeRenderer
|
||||
v-if="node.children.length"
|
||||
:nodes="node.children"
|
||||
@navigate="navigate"
|
||||
/>
|
||||
<tr v-for="(row, rowIndex) in node.rows" :key="rowIndex">
|
||||
<td v-for="(cell, cellIndex) in row" :key="cellIndex" class="border border-solid border-surface-5 px-2 py-1">
|
||||
<WikiNodeRenderer :nodes="cell" @navigate="navigate" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<tr v-else-if="node.type === 'tableRow'">
|
||||
<WikiNodeRenderer :nodes="node.children" @navigate="navigate" />
|
||||
</tr>
|
||||
<component
|
||||
:is="node.type === 'tableHeader' ? 'th' : 'td'"
|
||||
v-else-if="node.type === 'tableCell' || node.type === 'tableHeader'"
|
||||
class="border border-solid border-surface-5 px-2 py-1"
|
||||
:colspan="node.colspan ?? 1"
|
||||
:rowspan="node.rowspan ?? 1"
|
||||
>
|
||||
<WikiNodeRenderer :nodes="node.children" @navigate="navigate" />
|
||||
</component>
|
||||
<WikiAttachmentNode
|
||||
v-else-if="node.type === 'attachment' && node.title"
|
||||
:name="node.title"
|
||||
:caption="node.text"
|
||||
/>
|
||||
<WikiNotice
|
||||
v-else-if="node.type === 'notice' && node.title"
|
||||
:title="node.title"
|
||||
:description="node.text"
|
||||
:variant="node.variant"
|
||||
>
|
||||
<WikiNodeRenderer v-if="node.children.length" :nodes="node.children" @navigate="navigate" />
|
||||
<p v-else-if="node.text" class="whitespace-pre-wrap">{{ node.text }}</p>
|
||||
</WikiNotice>
|
||||
<span v-else>{{ node.text }}</span>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.wiki-heading {
|
||||
margin-top: 1.65rem;
|
||||
margin-bottom: 0.7rem;
|
||||
border-bottom: 1px solid color-mix(in oklab, currentColor 16%, transparent);
|
||||
padding-bottom: 0.4rem;
|
||||
font-weight: 750;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
h1.wiki-heading {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
h2.wiki-heading {
|
||||
font-size: 1.55rem;
|
||||
}
|
||||
|
||||
h3.wiki-heading {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user