From 71b8d111fb8b6780b979c0cf97fa8532e5f2aff6 Mon Sep 17 00:00:00 2001 From: Ap_Tx <3045929398@qq.com> Date: Sun, 18 Oct 2026 13:13:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E7=9A=AE=E8=82=A4?= =?UTF-8?q?=E7=AB=99=E7=BB=B4=E5=9F=BA=E5=8F=AA=E8=AF=BB=E6=B5=8F=E8=A7=88?= =?UTF-8?q?=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 WikiView 页面:输入标题加载页面,支持内部词条跳转与返回栈 - 新增 WikiNodeRenderer 递归渲染器:标题、段落、加粗斜体下划线删除线、高亮、行内代码、黑幕、分隔线、内外链、有序无序列表、引用、代码块、表格、附件、提示块 - 新增 WikiNotice 提示块与 WikiAttachmentNode 附件组件,图片支持点击查看大图 - 补充维基节点与附件类型定义,新增附件查询接口与附件内容 URL 拼接 - 主页导航 Wiki 卡片改为站内打开 - 中英文各补 10 个文案 --- .../components/ui/skin-site/SkinSiteHome.vue | 4 + .../ui/skin-site/SkinSiteNavCards.vue | 19 +- .../src/components/ui/skin-site/WikiView.vue | 192 ++++++++++++++++++ .../ui/skin-site/wiki/WikiAttachmentNode.vue | 74 +++++++ .../ui/skin-site/wiki/WikiNodeRenderer.vue | 185 +++++++++++++++++ .../ui/skin-site/wiki/WikiNotice.vue | 32 +++ .../ui/skin-site/wiki/wiki-color.ts | 18 ++ .../app-frontend/src/helpers/skin-site-api.ts | 48 ++++- .../app-frontend/src/locales/en-US/index.json | 12 +- .../app-frontend/src/locales/zh-CN/index.json | 12 +- 10 files changed, 590 insertions(+), 6 deletions(-) create mode 100644 apps/app-frontend/src/components/ui/skin-site/WikiView.vue create mode 100644 apps/app-frontend/src/components/ui/skin-site/wiki/WikiAttachmentNode.vue create mode 100644 apps/app-frontend/src/components/ui/skin-site/wiki/WikiNodeRenderer.vue create mode 100644 apps/app-frontend/src/components/ui/skin-site/wiki/WikiNotice.vue create mode 100644 apps/app-frontend/src/components/ui/skin-site/wiki/wiki-color.ts diff --git a/apps/app-frontend/src/components/ui/skin-site/SkinSiteHome.vue b/apps/app-frontend/src/components/ui/skin-site/SkinSiteHome.vue index 4d626df..23fe86a 100644 --- a/apps/app-frontend/src/components/ui/skin-site/SkinSiteHome.vue +++ b/apps/app-frontend/src/components/ui/skin-site/SkinSiteHome.vue @@ -18,6 +18,7 @@ import SkinSiteNavCards from './SkinSiteNavCards.vue' import SkinSiteProfileModal from './SkinSiteProfileModal.vue' import SkinSiteProfileView from './SkinSiteProfileView.vue' import SkinSiteReviewView from './SkinSiteReviewView.vue' +import WikiView from './WikiView.vue' const { formatMessage } = useVIntl() const { addNotification } = injectNotificationManager() @@ -45,6 +46,7 @@ const view = ref< | 'bug-detail' | 'bug-notifications' | 'admin' + | 'wiki' >('home') const selectedBugId = ref(null) @@ -98,6 +100,8 @@ async function copyAddress() { @open="openBug" /> + + () +const emit = defineEmits<{ + (e: 'navigate', view: 'downloads' | 'review' | 'bugs' | 'map' | 'wiki'): void +}>() const messages = defineMessages({ title: { id: 'app.skin-site.home.nav.title', defaultMessage: 'Skin site' }, @@ -104,8 +106,19 @@ const items: NavItem[] = [ ] function open(item: NavItem) { - if (item.id === 'download' || item.id === 'review' || item.id === 'bugs' || item.id === 'map') { - emit('navigate', item.id === 'download' ? 'downloads' : item.id) + if ( + item.id === 'download' || + item.id === 'review' || + item.id === 'bugs' || + item.id === 'map' || + item.id === 'wiki' + ) { + emit( + 'navigate', + item.id === 'download' + ? 'downloads' + : (item.id as 'review' | 'bugs' | 'map' | 'wiki'), + ) return } void import('@tauri-apps/plugin-opener').then(({ openUrl }) => openUrl(item.href)) diff --git a/apps/app-frontend/src/components/ui/skin-site/WikiView.vue b/apps/app-frontend/src/components/ui/skin-site/WikiView.vue new file mode 100644 index 0000000..4ad2544 --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin-site/WikiView.vue @@ -0,0 +1,192 @@ + + + diff --git a/apps/app-frontend/src/components/ui/skin-site/wiki/WikiAttachmentNode.vue b/apps/app-frontend/src/components/ui/skin-site/wiki/WikiAttachmentNode.vue new file mode 100644 index 0000000..4f73b5a --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin-site/wiki/WikiAttachmentNode.vue @@ -0,0 +1,74 @@ + + + diff --git a/apps/app-frontend/src/components/ui/skin-site/wiki/WikiNodeRenderer.vue b/apps/app-frontend/src/components/ui/skin-site/wiki/WikiNodeRenderer.vue new file mode 100644 index 0000000..0c5288f --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin-site/wiki/WikiNodeRenderer.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/apps/app-frontend/src/components/ui/skin-site/wiki/WikiNotice.vue b/apps/app-frontend/src/components/ui/skin-site/wiki/WikiNotice.vue new file mode 100644 index 0000000..95ae146 --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin-site/wiki/WikiNotice.vue @@ -0,0 +1,32 @@ + + + diff --git a/apps/app-frontend/src/components/ui/skin-site/wiki/wiki-color.ts b/apps/app-frontend/src/components/ui/skin-site/wiki/wiki-color.ts new file mode 100644 index 0000000..e64cb4e --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin-site/wiki/wiki-color.ts @@ -0,0 +1,18 @@ +export function safeWikiColor(value: unknown): string | null { + if (typeof value !== 'string') return null + const color = value.trim() + return color.length <= 80 && + /^(?:[a-z]{1,20}|#[\da-f]{3}|#[\da-f]{4}|#[\da-f]{6}|#[\da-f]{8}|rgba?\([\d\s.,%]+\))$/i.test(color) + ? color + : null +} + +export function safeExternalUrl(url?: string | null): string { + if (!url) return '#' + try { + const parsed = new URL(url) + return parsed.protocol === 'http:' || parsed.protocol === 'https:' ? parsed.toString() : '#' + } catch { + return '#' + } +} diff --git a/apps/app-frontend/src/helpers/skin-site-api.ts b/apps/app-frontend/src/helpers/skin-site-api.ts index c2cf8d8..c3bba36 100644 --- a/apps/app-frontend/src/helpers/skin-site-api.ts +++ b/apps/app-frontend/src/helpers/skin-site-api.ts @@ -1740,11 +1740,45 @@ export function skinSiteDeleteModRuntime(id: number): Promise> { return skinSiteRequest('wiki/admin/migration/' + jobId, { method: 'DELETE' }) } + +export function skinSiteWikiAttachment( + name: string, +): Promise> { + return skinSiteRequest( + 'wiki/attachment?name=' + encodeURIComponent(name), + ) +} + +export function skinSiteWikiAttachmentUrl(id: number): string { + return `${SKIN_SITE_ORIGIN}${REQUEST_TYPE_PATH.starlight}/wiki/attachment/${id}/content` +} diff --git a/apps/app-frontend/src/locales/en-US/index.json b/apps/app-frontend/src/locales/en-US/index.json index 571bc67..9a3ae7f 100644 --- a/apps/app-frontend/src/locales/en-US/index.json +++ b/apps/app-frontend/src/locales/en-US/index.json @@ -12424,5 +12424,15 @@ "app.skin-site.admin.wiki.published": {"message": "Published {imported} pages, overwrote {overwritten}."}, "app.skin-site.admin.wiki.rolled-back": {"message": "Migration batch rolled back."}, "app.skin-site.admin.wiki.job-deleted": {"message": "Migration record deleted."}, - "app.skin-site.admin.wiki.select-xml": {"message": "Choose a MediaWiki XML file first."} + "app.skin-site.admin.wiki.select-xml": {"message": "Choose a MediaWiki XML file first."}, + "app.skin-site.wiki.title": {"message": "StarLight Wiki"}, + "app.skin-site.wiki.search-placeholder": {"message": "Enter a page title"}, + "app.skin-site.wiki.open": {"message": "Open"}, + "app.skin-site.wiki.loading": {"message": "Loading…"}, + "app.skin-site.wiki.not-found": {"message": "Page not found."}, + "app.skin-site.wiki.empty": {"message": "Enter a page title above to start reading."}, + "app.skin-site.wiki.updated-at": {"message": "Last edited {date} by {author}"}, + "app.skin-site.wiki.revision": {"message": "Revision #{id}"}, + "app.skin-site.wiki.categories": {"message": "Categories"}, + "app.skin-site.wiki.back": {"message": "Back"} } diff --git a/apps/app-frontend/src/locales/zh-CN/index.json b/apps/app-frontend/src/locales/zh-CN/index.json index be7e653..0e33964 100644 --- a/apps/app-frontend/src/locales/zh-CN/index.json +++ b/apps/app-frontend/src/locales/zh-CN/index.json @@ -12255,5 +12255,15 @@ "app.skin-site.admin.wiki.published": {"message": "已发布 {imported} 个页面,覆盖 {overwritten} 个。"}, "app.skin-site.admin.wiki.rolled-back": {"message": "迁移批次已安全回滚。"}, "app.skin-site.admin.wiki.job-deleted": {"message": "迁移记录已删除。"}, - "app.skin-site.admin.wiki.select-xml": {"message": "请先选择 MediaWiki XML 文件。"} + "app.skin-site.admin.wiki.select-xml": {"message": "请先选择 MediaWiki XML 文件。"}, + "app.skin-site.wiki.title": {"message": "StarLight 维基"}, + "app.skin-site.wiki.search-placeholder": {"message": "输入页面标题"}, + "app.skin-site.wiki.open": {"message": "打开"}, + "app.skin-site.wiki.loading": {"message": "加载中…"}, + "app.skin-site.wiki.not-found": {"message": "页面不存在。"}, + "app.skin-site.wiki.empty": {"message": "在上方输入页面标题开始阅读。"}, + "app.skin-site.wiki.updated-at": {"message": "最后编辑于 {date},编辑者 {author}"}, + "app.skin-site.wiki.revision": {"message": "修订 #{id}"}, + "app.skin-site.wiki.categories": {"message": "分类"}, + "app.skin-site.wiki.back": {"message": "返回"} }