clarity-theme 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +276 -0
- package/README.zh-CN.md +297 -0
- package/app/app.config.ts +110 -0
- package/app/app.vue +5 -0
- package/app/assets/css/_variable.scss +8 -0
- package/app/assets/css/animation.scss +50 -0
- package/app/assets/css/article.scss +152 -0
- package/app/assets/css/color.scss +58 -0
- package/app/assets/css/font.scss +35 -0
- package/app/assets/css/main.scss +230 -0
- package/app/assets/css/reusable.scss +186 -0
- package/app/assets/icons/solar-rewind-back-bold-duotone.svg +1 -0
- package/app/assets/icons/solar-rewind-forward-bold-duotone.svg +1 -0
- package/app/components/blog/BlogAside.vue +63 -0
- package/app/components/blog/BlogFooter.vue +61 -0
- package/app/components/blog/BlogHeader.global.vue +146 -0
- package/app/components/blog/BlogPanel.vue +79 -0
- package/app/components/blog/BlogSidebar.vue +160 -0
- package/app/components/blog/BlogWidget.vue +134 -0
- package/app/components/blog/Mask.vue +42 -0
- package/app/components/blog/SkipToContent.vue +22 -0
- package/app/components/blog/ThemeToggle.vue +50 -0
- package/app/components/content/Alert.vue +96 -0
- package/app/components/content/Badge.vue +90 -0
- package/app/components/content/Blur.vue +22 -0
- package/app/components/content/CardList.vue +32 -0
- package/app/components/content/Chat.vue +79 -0
- package/app/components/content/Copy.vue +134 -0
- package/app/components/content/EmojiClock.vue +53 -0
- package/app/components/content/FeedCard.vue +212 -0
- package/app/components/content/FeedGroup.vue +130 -0
- package/app/components/content/Folding.vue +69 -0
- package/app/components/content/Key.vue +164 -0
- package/app/components/content/LinkBanner.vue +59 -0
- package/app/components/content/LinkCard.vue +74 -0
- package/app/components/content/MdTitle.vue +26 -0
- package/app/components/content/Mermaid.vue +133 -0
- package/app/components/content/MusicScore.vue +178 -0
- package/app/components/content/Pic.vue +55 -0
- package/app/components/content/Poetry.vue +41 -0
- package/app/components/content/ProseA.vue +40 -0
- package/app/components/content/ProseCode.vue +95 -0
- package/app/components/content/ProsePre.vue +291 -0
- package/app/components/content/ProseTable.vue +70 -0
- package/app/components/content/Quote.vue +51 -0
- package/app/components/content/Tab.vue +100 -0
- package/app/components/content/Timeline.vue +73 -0
- package/app/components/content/Tip.vue +52 -0
- package/app/components/content/VideoEmbed.vue +127 -0
- package/app/components/partial/Button.vue +98 -0
- package/app/components/partial/DlGroup.vue +69 -0
- package/app/components/partial/Dropdown.vue +54 -0
- package/app/components/partial/Error.vue +70 -0
- package/app/components/partial/Expand.vue +51 -0
- package/app/components/partial/IconNavList.vue +42 -0
- package/app/components/partial/Pagination.vue +113 -0
- package/app/components/partial/RadioGroup.vue +77 -0
- package/app/components/partial/Secret.vue +23 -0
- package/app/components/partial/Slider.vue +96 -0
- package/app/components/partial/Toggle.vue +70 -0
- package/app/components/popover/Lightbox.vue +77 -0
- package/app/components/popover/Search.vue +214 -0
- package/app/components/popover/SearchItem.vue +64 -0
- package/app/components/post/Archive.vue +104 -0
- package/app/components/post/Article.vue +138 -0
- package/app/components/post/Comment.vue +275 -0
- package/app/components/post/Excerpt.vue +66 -0
- package/app/components/post/OrderToggle.vue +101 -0
- package/app/components/post/PostFooter.vue +80 -0
- package/app/components/post/PostHeader.vue +147 -0
- package/app/components/post/PostSurround.vue +111 -0
- package/app/components/post/Slide.vue +215 -0
- package/app/components/util/Date.vue +62 -0
- package/app/components/util/HydrateSafe.vue +10 -0
- package/app/components/util/Img.vue +38 -0
- package/app/components/util/Link.vue +11 -0
- package/app/components/widget/BlogLog.vue +27 -0
- package/app/components/widget/BlogStats.vue +39 -0
- package/app/components/widget/BlogTech.vue +65 -0
- package/app/components/widget/Empty.vue +3 -0
- package/app/components/widget/Toc.vue +129 -0
- package/app/composables/useArticle.ts +118 -0
- package/app/composables/useAvoid.ts +87 -0
- package/app/composables/useCopy.ts +21 -0
- package/app/composables/usePagination.ts +70 -0
- package/app/composables/useShiki.ts +129 -0
- package/app/composables/useToc.ts +57 -0
- package/app/composables/useWidgets.ts +56 -0
- package/app/error.vue +34 -0
- package/app/layouts/default.vue +66 -0
- package/app/pages/[...slug].vue +59 -0
- package/app/pages/archive.vue +207 -0
- package/app/pages/index.vue +83 -0
- package/app/pages/link.vue +65 -0
- package/app/pages/preview.vue +59 -0
- package/app/plugins/easter-egg.ts +23 -0
- package/app/plugins/init.ts +6 -0
- package/app/plugins/tippy.ts +22 -0
- package/app/shiki.config.ts +8 -0
- package/app/stores/layout.ts +34 -0
- package/app/stores/search.ts +37 -0
- package/app/types/article.ts +16 -0
- package/app/types/feed.ts +1 -0
- package/app/types/index.ts +14 -0
- package/app/types/modal.ts +16 -0
- package/app/types/nav.ts +11 -0
- package/app/utils/anim.ts +37 -0
- package/app/utils/img.ts +83 -0
- package/config/app.ts +88 -0
- package/config/content.d.mts +1 -0
- package/config/content.mjs +69 -0
- package/config/content.ts +91 -0
- package/config/define.mjs +21 -0
- package/config/define.ts +23 -0
- package/config/feed.ts +42 -0
- package/config/feeds.empty.ts +4 -0
- package/config/index.d.mts +1 -0
- package/config/index.mjs +6 -0
- package/config/index.ts +6 -0
- package/config/public.ts +71 -0
- package/config/schema.d.mts +1 -0
- package/config/schema.mjs +103 -0
- package/config/schema.ts +129 -0
- package/config/server.ts +35 -0
- package/img/index.d.mts +1 -0
- package/img/index.mjs +78 -0
- package/img/index.ts +8 -0
- package/modules/clarity-config/anti-mirror-client.ts +11 -0
- package/modules/clarity-config/index.ts +324 -0
- package/nuxt.config.ts +225 -0
- package/package.json +148 -0
- package/public/assets/atom.css +274 -0
- package/public/assets/atom.xsl +110 -0
- package/public/fonts/AlimamaFangYuanTi.woff2 +0 -0
- package/remark-plugins/rehype-meta-slots.d.mts +1 -0
- package/remark-plugins/rehype-meta-slots.mjs +49 -0
- package/remark-plugins/rehype-meta-slots.ts +58 -0
- package/remark-plugins/remark-code-component.d.mts +1 -0
- package/remark-plugins/remark-code-component.mjs +25 -0
- package/remark-plugins/remark-code-component.ts +41 -0
- package/server/api/stats.get.ts +109 -0
- package/server/routes/atom.xml.get.ts +101 -0
- package/server/routes/subscriptions.opml.get.ts +70 -0
- package/server/utils/clarity.ts +11 -0
- package/shared/types/content.ts +30 -0
- package/shared/utils/clarity.ts +34 -0
- package/shared/utils/icon.ts +218 -0
- package/shared/utils/link.ts +52 -0
- package/shared/utils/str.ts +88 -0
- package/shared/utils/time.ts +116 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import type { Nuxt } from '@nuxt/schema'
|
|
2
|
+
import type { ClarityConfig } from '../../config/schema'
|
|
3
|
+
import { existsSync } from 'node:fs'
|
|
4
|
+
import { dirname, relative, resolve } from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import { createJiti } from 'jiti'
|
|
7
|
+
import {
|
|
8
|
+
addImportsDir,
|
|
9
|
+
addTypeTemplate,
|
|
10
|
+
defineNuxtModule,
|
|
11
|
+
updateAppConfig,
|
|
12
|
+
useLogger,
|
|
13
|
+
} from 'nuxt/kit'
|
|
14
|
+
import nuxtPkg from 'nuxt/package.json'
|
|
15
|
+
import { minify } from 'oxc-minify'
|
|
16
|
+
import vuePkg from 'vue/package.json'
|
|
17
|
+
import { toPublicClarityConfig } from '../../config/public'
|
|
18
|
+
import { toServerClarityConfig } from '../../config/server'
|
|
19
|
+
import { clarityConfigSchema } from '../../config/schema'
|
|
20
|
+
import handleMirror from './anti-mirror-client'
|
|
21
|
+
|
|
22
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url))
|
|
23
|
+
const themeDir = resolve(moduleDir, '../..')
|
|
24
|
+
|
|
25
|
+
/** app/app.config.ts 的 clarity 键只允许 UI 覆盖(站点级字段属于 clarity.config.ts) */
|
|
26
|
+
const uiConfigKeys = new Set(['component', 'footer', 'header', 'link', 'nav', 'pagination', 'themes'])
|
|
27
|
+
|
|
28
|
+
export interface ModuleOptions {
|
|
29
|
+
/** 消费项目中 clarity 配置文件路径(相对 rootDir) */
|
|
30
|
+
configFile?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default defineNuxtModule<ModuleOptions>({
|
|
34
|
+
meta: {
|
|
35
|
+
name: 'clarity-config',
|
|
36
|
+
configKey: 'clarityConfig',
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
async setup(options, nuxt) {
|
|
40
|
+
const logger = useLogger('clarity-config')
|
|
41
|
+
const rootDir = nuxt.options.rootDir
|
|
42
|
+
|
|
43
|
+
const configPath = resolve(rootDir, options.configFile ?? findConfigFile(rootDir))
|
|
44
|
+
const feedsPath = findFeedsFile(rootDir, themeDir)
|
|
45
|
+
|
|
46
|
+
// ---- 依赖注入层:Theme 内部不感知消费项目的文件布局 ----
|
|
47
|
+
Object.assign(nuxt.options.alias, {
|
|
48
|
+
'#clarity/config': configPath,
|
|
49
|
+
'#clarity/feeds': feedsPath.resolved,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// @pinia/nuxt 不会自动扫描 Layer 的 stores 目录,需显式注册
|
|
53
|
+
addImportsDir(resolve(themeDir, 'app/stores'))
|
|
54
|
+
|
|
55
|
+
// ---- AppConfig 类型化:消费者 defineAppConfig({ clarity: ... }) 获得完整类型提示 ----
|
|
56
|
+
// 注意:类型文件生成于 buildDir/types/ 下,相对路径必须以其为基准计算,
|
|
57
|
+
// 否则导入解析失败会因 skipLibCheck 静默退化为 any(审计发现 #9)。
|
|
58
|
+
const appConfigTypeDir = resolve(nuxt.options.buildDir, 'types')
|
|
59
|
+
const appConfigTypePath = relative(appConfigTypeDir, resolve(themeDir, 'config/app.ts')).replaceAll('\\', '/')
|
|
60
|
+
const globalsTypePath = relative(appConfigTypeDir, resolve(themeDir, 'app/types/index.ts')).replaceAll('\\', '/')
|
|
61
|
+
addTypeTemplate({
|
|
62
|
+
filename: 'types/clarity-app-config.d.ts',
|
|
63
|
+
getContents: () => [
|
|
64
|
+
// app/types/index.ts 的 Window.twikoo 等环境声明在 node_modules Layer 中
|
|
65
|
+
// 不会被 tsconfig include 命中(被 node_modules exclude 过滤),需显式导入
|
|
66
|
+
`import '${globalsTypePath}'`,
|
|
67
|
+
`import type { ClarityAppConfig, ClarityUiConfigInput } from '${appConfigTypePath}'`,
|
|
68
|
+
'',
|
|
69
|
+
'declare module \'@nuxt/schema\' {',
|
|
70
|
+
' // 读取侧:unknown 使 MergedAppConfig 回落到 Theme 注入的完整 Resolved 类型',
|
|
71
|
+
' interface CustomAppConfig {',
|
|
72
|
+
' clarity?: unknown',
|
|
73
|
+
' }',
|
|
74
|
+
' // 输入侧:完整配置(Theme 内部)或 UI 部分覆盖(消费项目 app/app.config.ts)',
|
|
75
|
+
' interface AppConfigInput {',
|
|
76
|
+
' clarity?: ClarityAppConfig | ClarityUiConfigInput',
|
|
77
|
+
' }',
|
|
78
|
+
'}',
|
|
79
|
+
'',
|
|
80
|
+
].join('\n'),
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// @bikariya/shiki 运行时固定导入 ~/shiki.config,
|
|
84
|
+
// 消费项目未提供时回退到 Theme 内置配置;
|
|
85
|
+
// 需置于 '~' 之前以保证 Vite 别名前缀优先匹配
|
|
86
|
+
if (!existsSync(resolve(nuxt.options.srcDir, 'shiki.config.ts'))) {
|
|
87
|
+
nuxt.options.alias = {
|
|
88
|
+
'~/shiki.config': resolve(themeDir, 'app/shiki.config.ts'),
|
|
89
|
+
...nuxt.options.alias,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!existsSync(configPath)) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`[clarity-config] 未找到 ${configPath}。`
|
|
96
|
+
+ '请在消费项目根目录创建 clarity.config.ts(可参考 playground/clarity.config.ts)。',
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
if (!feedsPath.found) {
|
|
100
|
+
logger.warn('未找到 feeds.ts,友链页面与 OPML 订阅将输出空数据。')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---- 读取并校验站点配置(错误在构建开始前暴露) ----
|
|
104
|
+
const jiti = createJiti(import.meta.url, { moduleCache: false, interopDefault: true })
|
|
105
|
+
const configModule = await importConfigFile(jiti, configPath)
|
|
106
|
+
const config = parseClarityConfig((configModule as { default?: unknown })?.default ?? configModule, configPath)
|
|
107
|
+
const { site, article, integrations, features } = config
|
|
108
|
+
|
|
109
|
+
// ---- 边界审计:app/app.config.ts 中不应出现站点级字段(其优先级更高,会覆盖站点配置注入) ----
|
|
110
|
+
await warnNonUiAppConfigOverrides(nuxt, jiti, logger)
|
|
111
|
+
|
|
112
|
+
// ---- Site Config → App Config 桥梁 ----
|
|
113
|
+
// 注意 Nuxt appConfig 合并优先级:消费项目 app.config > Theme app.config > 模块注入,
|
|
114
|
+
// 因此站点派生默认值(header.logo 等)不放入 Theme 的 app.config.ts,
|
|
115
|
+
// 由这里注入后仍可被消费项目覆盖。
|
|
116
|
+
updateAppConfig({
|
|
117
|
+
clarity: {
|
|
118
|
+
...toPublicClarityConfig(config),
|
|
119
|
+
header: {
|
|
120
|
+
logo: site.author.avatar ?? '',
|
|
121
|
+
subtitle: site.subtitle ?? '',
|
|
122
|
+
},
|
|
123
|
+
footer: {
|
|
124
|
+
copyright: site.copyright?.name
|
|
125
|
+
? `© ${new Date().getFullYear()} ${site.author.name} · ${site.copyright.name}`
|
|
126
|
+
: `© ${new Date().getFullYear()} ${site.author.name}`,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
// ---- 服务端专用配置:注入 Nitro 私有 runtimeConfig(不进入客户端 bundle)----
|
|
132
|
+
// Atom/OPML/stats 处理器与 feature 路由守卫从这里读取完整配置。
|
|
133
|
+
nuxt.options.runtimeConfig.clarity = toServerClarityConfig(config)
|
|
134
|
+
|
|
135
|
+
// ---- SEO / site / robots / llms ----
|
|
136
|
+
nuxt.options.site = {
|
|
137
|
+
...nuxt.options.site,
|
|
138
|
+
name: site.title,
|
|
139
|
+
url: site.url,
|
|
140
|
+
defaultLocale: site.language,
|
|
141
|
+
} as typeof nuxt.options.site
|
|
142
|
+
|
|
143
|
+
nuxt.options.robots = {
|
|
144
|
+
...nuxt.options.robots,
|
|
145
|
+
disallow: article.robotsNotIndex,
|
|
146
|
+
} as typeof nuxt.options.robots
|
|
147
|
+
|
|
148
|
+
// nuxt-llms 的配置类型由该模块按需合并,此处运行时注入
|
|
149
|
+
const llmsOptions = (nuxt.options as any).llms ?? {}
|
|
150
|
+
;(nuxt.options as any).llms = {
|
|
151
|
+
...llmsOptions,
|
|
152
|
+
domain: site.url,
|
|
153
|
+
title: site.title,
|
|
154
|
+
description: site.description,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ---- Head 元数据 ----
|
|
158
|
+
const head = nuxt.options.app.head
|
|
159
|
+
head.meta ??= []
|
|
160
|
+
head.meta.push({ name: 'author', content: [site.author.name, site.author.email].filter(Boolean).join(', ') })
|
|
161
|
+
head.link ??= []
|
|
162
|
+
head.link.push({ rel: 'icon', href: site.favicon })
|
|
163
|
+
if (features.atom) {
|
|
164
|
+
head.link.push({ rel: 'alternate', type: 'application/atom+xml', href: '/atom.xml' })
|
|
165
|
+
}
|
|
166
|
+
const twikooPreload = integrations.twikoo?.preload ?? integrations.twikoo?.envId
|
|
167
|
+
if (twikooPreload) {
|
|
168
|
+
head.link.push({ rel: 'preconnect', href: twikooPreload })
|
|
169
|
+
}
|
|
170
|
+
head.script ??= []
|
|
171
|
+
head.script.push(...integrations.scripts as any[])
|
|
172
|
+
head.titleTemplate = `%s %separator ${site.title}`
|
|
173
|
+
|
|
174
|
+
// ---- 构建信息(供 BlogTech 等 Widget 使用) ----
|
|
175
|
+
const consumerPkg = await loadJson(resolve(rootDir, 'package.json'), jiti)
|
|
176
|
+
const themePkg = await loadJson(resolve(themeDir, 'package.json'), jiti)
|
|
177
|
+
nuxt.options.runtimeConfig.public.clarity = {
|
|
178
|
+
theme: 'Clarity',
|
|
179
|
+
themeVersion: String(themePkg?.version ?? ''),
|
|
180
|
+
themeHomepage: String(themePkg?.homepage ?? ''),
|
|
181
|
+
siteVersion: String(consumerPkg?.version ?? ''),
|
|
182
|
+
sitePackageManager: String(consumerPkg?.packageManager ?? ''),
|
|
183
|
+
nuxtVersion: nuxtPkg.version,
|
|
184
|
+
vueVersion: vuePkg.version,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ---- 路由规则 ----
|
|
188
|
+
const routeRules = nuxt.options.routeRules ??= {}
|
|
189
|
+
if (features.stats) {
|
|
190
|
+
routeRules['/api/stats'] = { prerender: true, headers: { 'Content-Type': 'application/json' } }
|
|
191
|
+
}
|
|
192
|
+
if (features.atom) {
|
|
193
|
+
routeRules['/atom.xml'] = { prerender: true, headers: { 'Content-Type': 'application/xml' } }
|
|
194
|
+
}
|
|
195
|
+
if (features.opml) {
|
|
196
|
+
routeRules['/subscriptions.opml'] = { prerender: true, headers: { 'Content-Type': 'application/xml' } }
|
|
197
|
+
}
|
|
198
|
+
routeRules['/favicon.ico'] = { redirect: { to: site.favicon } }
|
|
199
|
+
|
|
200
|
+
// ---- 自定义链接与 /posts 前缀处理 ----
|
|
201
|
+
nuxt.hook('content:file:afterParse', (ctx) => {
|
|
202
|
+
const { permalink, path } = ctx.content as Record<string, string | undefined>
|
|
203
|
+
if (permalink) {
|
|
204
|
+
ctx.content.path = permalink
|
|
205
|
+
}
|
|
206
|
+
else if (article.hidePostPrefix && path?.startsWith('/posts/')) {
|
|
207
|
+
ctx.content.path = path.slice('/posts'.length)
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
// ---- 可选功能:anti-mirror ----
|
|
212
|
+
// 黑名单完全由站点配置提供;Theme 不携带任何上游默认域名(审计发现 #5)。
|
|
213
|
+
if (features.antiMirror !== false) {
|
|
214
|
+
const blacklist = typeof features.antiMirror === 'boolean' ? [] : features.antiMirror.blacklist
|
|
215
|
+
if (blacklist.length === 0) {
|
|
216
|
+
logger.warn('features.antiMirror 已启用,但未配置 blacklist,已跳过反镜像脚本注入。')
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
await injectAntiMirror(nuxt, blacklist, site.url)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
async function importConfigFile(jiti: ReturnType<typeof createJiti>, configPath: string) {
|
|
226
|
+
try {
|
|
227
|
+
return await jiti.import(configPath) as unknown
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`[clarity-config] 加载 ${configPath} 失败:${error instanceof Error ? error.message : String(error)}`,
|
|
232
|
+
{ cause: error },
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function parseClarityConfig(raw: unknown, configPath: string): ClarityConfig {
|
|
238
|
+
const result = clarityConfigSchema.safeParse(raw)
|
|
239
|
+
if (!result.success) {
|
|
240
|
+
const issues = result.error.issues
|
|
241
|
+
.map(issue => ` - ${issue.path.join('.') || '(根对象)'}: ${issue.message}`)
|
|
242
|
+
.join('\n')
|
|
243
|
+
throw new Error(`[clarity-config] ${configPath} 校验失败:\n${issues}`)
|
|
244
|
+
}
|
|
245
|
+
return result.data
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** 消费项目 app/app.config.ts 若覆盖站点级字段,会在 defu 合并中压过 clarity.config.ts 注入,需要提示迁移 */
|
|
249
|
+
async function warnNonUiAppConfigOverrides(nuxt: Nuxt, jiti: ReturnType<typeof createJiti>, logger: ReturnType<typeof useLogger>) {
|
|
250
|
+
const candidates = [
|
|
251
|
+
resolve(nuxt.options.srcDir, 'app.config.ts'),
|
|
252
|
+
resolve(nuxt.options.rootDir, 'app.config.ts'),
|
|
253
|
+
]
|
|
254
|
+
for (const appConfigPath of candidates) {
|
|
255
|
+
if (!existsSync(appConfigPath)) {
|
|
256
|
+
continue
|
|
257
|
+
}
|
|
258
|
+
// app.config.ts 依赖 defineAppConfig 全局函数,jiti 裸加载时需要临时注入(运行时它只是恒等函数)
|
|
259
|
+
const globalScope = globalThis as { defineAppConfig?: (config: unknown) => unknown }
|
|
260
|
+
const previousDefine = globalScope.defineAppConfig
|
|
261
|
+
globalScope.defineAppConfig ??= (config: unknown) => config
|
|
262
|
+
try {
|
|
263
|
+
const mod = await jiti.import(appConfigPath) as unknown
|
|
264
|
+
const clarity = ((mod as { default?: unknown })?.default ?? mod) as { clarity?: Record<string, unknown> } | undefined
|
|
265
|
+
if (!clarity?.clarity || typeof clarity.clarity !== 'object') {
|
|
266
|
+
continue
|
|
267
|
+
}
|
|
268
|
+
const offenders = Object.keys(clarity.clarity).filter(key => !uiConfigKeys.has(key))
|
|
269
|
+
if (offenders.length) {
|
|
270
|
+
logger.warn(
|
|
271
|
+
`app/app.config.ts 的 clarity 中出现站点级字段 [${offenders.join(', ')}],`
|
|
272
|
+
+ '它们会覆盖 clarity.config.ts 的注入结果;请将站点配置迁移到 clarity.config.ts。',
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
// 尽力而为的边界提示:app.config 加载失败时交给 Nuxt 自身报错
|
|
278
|
+
}
|
|
279
|
+
finally {
|
|
280
|
+
if (previousDefine === undefined) {
|
|
281
|
+
delete globalScope.defineAppConfig
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
globalScope.defineAppConfig = previousDefine
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function injectAntiMirror(nuxt: Nuxt, blacklist: string[], target: string) {
|
|
291
|
+
const source = `(${handleMirror.toString()})(${JSON.stringify(blacklist.map(btoa))},${JSON.stringify(btoa(target))})`
|
|
292
|
+
const { code, errors } = await minify('clarity-anti-mirror.iife.js', source)
|
|
293
|
+
if (errors.length > 0) {
|
|
294
|
+
throw new Error(`[clarity-config] anti-mirror 脚本压缩失败:\n${errors.map(error => error.message ?? String(error)).join('\n')}`)
|
|
295
|
+
}
|
|
296
|
+
;(nuxt.options.app.head.script ??= []).push({ innerHTML: code })
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function findConfigFile(rootDir: string) {
|
|
300
|
+
for (const name of ['clarity.config.ts', 'clarity.config.mjs', 'clarity.config.js']) {
|
|
301
|
+
if (existsSync(resolve(rootDir, name))) {
|
|
302
|
+
return name
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return 'clarity.config.ts'
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function findFeedsFile(rootDir: string, themeDir: string) {
|
|
309
|
+
for (const name of ['feeds.ts', 'feeds.mjs', 'feeds.js']) {
|
|
310
|
+
if (existsSync(resolve(rootDir, name))) {
|
|
311
|
+
return { found: true, resolved: resolve(rootDir, name) }
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return { found: false, resolved: resolve(themeDir, 'config/feeds.empty.ts') }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function loadJson(path: string, jiti: ReturnType<typeof createJiti>) {
|
|
318
|
+
try {
|
|
319
|
+
return ((await jiti.import(path)) as { default?: Record<string, unknown> } | undefined)?.default
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
return undefined
|
|
323
|
+
}
|
|
324
|
+
}
|
package/nuxt.config.ts
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { dirname, join } from 'node:path'
|
|
2
|
+
import { arch, env, version as nodeVersion, platform } from 'node:process'
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
4
|
+
import { name as ciName, CLOUDFLARE_PAGES, GITHUB_ACTIONS, NETLIFY } from 'ci-info'
|
|
5
|
+
import { Temporal } from 'temporal-polyfill'
|
|
6
|
+
import { homepage, name as themeName, version as themeVersion } from './package.json'
|
|
7
|
+
|
|
8
|
+
/** Theme 根目录(Layer 本地路径基准,兼容 npm 包 / git 包 / 本地目录安装) */
|
|
9
|
+
const themeDir = dirname(fileURLToPath(import.meta.url))
|
|
10
|
+
const toThemePath = (path: string) => join(themeDir, path).replaceAll('\\', '/')
|
|
11
|
+
|
|
12
|
+
function pluginPath(path: string) {
|
|
13
|
+
// 插件必须是 .mjs:@nuxt/content 以 Node 原生方式加载,
|
|
14
|
+
// 不允许 node_modules 内的 TS 文件(TS 剥离限制)
|
|
15
|
+
return pathToFileURL(join(themeDir, 'remark-plugins', `${path}.mjs`)).href
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Clarity Theme Layer 配置
|
|
20
|
+
*
|
|
21
|
+
* 站点数据(SEO、favicon、scripts 等)由 modules/clarity-config
|
|
22
|
+
* 读取消费项目的 clarity.config.ts 后统一注入,此处仅包含 Theme 自身能力。
|
|
23
|
+
*/
|
|
24
|
+
export default defineNuxtConfig({
|
|
25
|
+
app: {
|
|
26
|
+
head: {
|
|
27
|
+
meta: [
|
|
28
|
+
{ name: 'color-scheme', content: 'light dark' },
|
|
29
|
+
{ 'name': 'generator', 'content': `Clarity Theme ${themeVersion}`, 'data-github-repo': homepage },
|
|
30
|
+
{ name: 'mobile-web-app-capable', content: 'yes' },
|
|
31
|
+
],
|
|
32
|
+
link: [
|
|
33
|
+
{ rel: 'stylesheet', href: 'https://s4.zstatic.net/npm/katex@0.16.44/dist/katex.min.css' },
|
|
34
|
+
// "InterVariable", "Inter"
|
|
35
|
+
{ rel: 'stylesheet', href: 'https://s4.zstatic.net/npm/inter-ui@4.1.1/inter-variable.css' },
|
|
36
|
+
{ rel: 'stylesheet', href: 'https://s4.zstatic.net/npm/inter-ui@4.1.1/inter.css' },
|
|
37
|
+
// "JetBrains Mono", 思源宋体 "Noto Serif SC"
|
|
38
|
+
{ rel: 'preconnect', href: 'https://fonts.gstatic.cn', crossorigin: '' },
|
|
39
|
+
{ rel: 'stylesheet', href: 'https://fonts.googleapis.cn/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Noto+Serif+SC:wght@200..900&display=swap' },
|
|
40
|
+
],
|
|
41
|
+
templateParams: {
|
|
42
|
+
separator: '|',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
rootAttrs: {
|
|
46
|
+
id: 'blog-root',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
compatibilityDate: '2024-08-03',
|
|
51
|
+
|
|
52
|
+
components: [
|
|
53
|
+
{ path: toThemePath('app/components/partial'), prefix: 'Z' },
|
|
54
|
+
toThemePath('app/components'),
|
|
55
|
+
],
|
|
56
|
+
|
|
57
|
+
// @keep-sorted
|
|
58
|
+
css: [
|
|
59
|
+
toThemePath('app/assets/css/animation.scss'),
|
|
60
|
+
toThemePath('app/assets/css/article.scss'),
|
|
61
|
+
toThemePath('app/assets/css/color.scss'),
|
|
62
|
+
toThemePath('app/assets/css/font.scss'),
|
|
63
|
+
toThemePath('app/assets/css/main.scss'),
|
|
64
|
+
toThemePath('app/assets/css/reusable.scss'),
|
|
65
|
+
],
|
|
66
|
+
|
|
67
|
+
// @keep-sorted
|
|
68
|
+
experimental: {
|
|
69
|
+
extractAsyncDataHandlers: true,
|
|
70
|
+
typescriptPlugin: true,
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// @keep-sorted
|
|
74
|
+
modules: [
|
|
75
|
+
'@bikariya/image-viewer',
|
|
76
|
+
'@bikariya/modals',
|
|
77
|
+
'@bikariya/shiki',
|
|
78
|
+
'@nuxt/content',
|
|
79
|
+
'@nuxt/icon',
|
|
80
|
+
'@nuxt/image',
|
|
81
|
+
'@nuxtjs/color-mode',
|
|
82
|
+
'@nuxtjs/seo',
|
|
83
|
+
'@pinia/nuxt',
|
|
84
|
+
'@vueuse/nuxt',
|
|
85
|
+
// Layer 配置中的相对 module 路径会以消费项目为基准解析,必须使用绝对路径;
|
|
86
|
+
// 且 clarity-config 必须先于 nuxt-llms 运行:后者在 setup 时读取注入的 llms 站点配置
|
|
87
|
+
toThemePath('modules/clarity-config'),
|
|
88
|
+
'nuxt-llms',
|
|
89
|
+
],
|
|
90
|
+
|
|
91
|
+
nitro: {
|
|
92
|
+
prerender: {
|
|
93
|
+
// 修复部分平台会在文章路径后添加 `/`,导致闪现 404 错误
|
|
94
|
+
// https://github.com/nuxt/content/issues/2378
|
|
95
|
+
autoSubfolderIndex: CLOUDFLARE_PAGES || GITHUB_ACTIONS || NETLIFY ? false : undefined,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
runtimeConfig: {
|
|
100
|
+
// @keep-sorted
|
|
101
|
+
public: {
|
|
102
|
+
arch,
|
|
103
|
+
buildTime: Temporal.Now.zonedDateTimeISO().toString(),
|
|
104
|
+
// EdgeOne 检测暂时不可用
|
|
105
|
+
ci: env.TENCENTCLOUD_RUNENV === 'SCF' ? 'EdgeOne' : ciName || '',
|
|
106
|
+
nodeVersion,
|
|
107
|
+
platform,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
typescript: {
|
|
112
|
+
nodeTsConfig: {
|
|
113
|
+
// @keep-sorted
|
|
114
|
+
include: [
|
|
115
|
+
'../config/**/*.ts',
|
|
116
|
+
'../remark-plugins/**/*.ts',
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
vite: {
|
|
122
|
+
css: {
|
|
123
|
+
preprocessorOptions: {
|
|
124
|
+
scss: {
|
|
125
|
+
additionalData: `@use "${toThemePath('app/assets/css/_variable.scss')}" as *;`,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
// @keep-sorted
|
|
130
|
+
optimizeDeps: {
|
|
131
|
+
include: ['@shikijs/colorized-brackets', '@shikijs/transformers', '@unhead/schema-org/vue', '@vue/devtools-core', '@vue/devtools-kit', 'embla-carousel-autoplay', 'embla-carousel-vue', 'embla-carousel-wheel-gestures', 'es-toolkit/array', 'es-toolkit/math', 'es-toolkit/object', 'es-toolkit/promise', 'es-toolkit/string', 'minisearch', 'parse-domain', 'plain-shiki', 'shiki/themes/catppuccin-latte.mjs', 'shiki/themes/one-dark-pro.mjs', 'temporal-polyfill', 'vue-tippy'],
|
|
132
|
+
},
|
|
133
|
+
server: {
|
|
134
|
+
allowedHosts: true,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
colorMode: {
|
|
139
|
+
preference: 'system',
|
|
140
|
+
fallback: 'light',
|
|
141
|
+
classSuffix: '',
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
content: {
|
|
145
|
+
build: {
|
|
146
|
+
markdown: {
|
|
147
|
+
highlight: false,
|
|
148
|
+
// @keep-sorted
|
|
149
|
+
remarkPlugins: {
|
|
150
|
+
[pluginPath('remark-code-component')]: {
|
|
151
|
+
options: {
|
|
152
|
+
'mermaid': { component: 'mermaid', prop: 'code' },
|
|
153
|
+
'music-abc': { component: 'music-score', prop: 'abc' },
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
'remark-math': {},
|
|
157
|
+
'remark-reading-time': {},
|
|
158
|
+
},
|
|
159
|
+
// @keep-sorted
|
|
160
|
+
rehypePlugins: {
|
|
161
|
+
[pluginPath('rehype-meta-slots')]: {},
|
|
162
|
+
'rehype-katex': {},
|
|
163
|
+
},
|
|
164
|
+
toc: { depth: 4, searchDepth: 4 },
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
experimental: {
|
|
168
|
+
sqliteConnector: 'native',
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
dxup: {
|
|
173
|
+
features: {
|
|
174
|
+
// 页面通过 <template #aside> 向布局具名插槽传参
|
|
175
|
+
namedLayoutSlots: true,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
hooks: {
|
|
180
|
+
ready: () => {
|
|
181
|
+
console.info(`
|
|
182
|
+
================================
|
|
183
|
+
${themeName} ${themeVersion}
|
|
184
|
+
${homepage}
|
|
185
|
+
================================
|
|
186
|
+
`)
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
icon: {
|
|
191
|
+
customCollections: [
|
|
192
|
+
{ prefix: 'clarity', dir: toThemePath('app/assets/icons') },
|
|
193
|
+
],
|
|
194
|
+
clientBundle: {
|
|
195
|
+
scan: {
|
|
196
|
+
globInclude: ['**\/*.{vue,jsx,tsx,ts,md,mdc,mdx}'],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
image: {
|
|
202
|
+
densities: [1, 1.5, 2],
|
|
203
|
+
format: ['avif', 'webp'],
|
|
204
|
+
// Netlify 下 netlify 处理器无法显示站外图片,ipx 处理器无法显示站内图片,需彻底禁用
|
|
205
|
+
// https://github.com/nuxt/image/issues/1353
|
|
206
|
+
provider: NETLIFY ? 'none' : undefined,
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
linkChecker: {
|
|
210
|
+
// @keep-sorted
|
|
211
|
+
skipInspections: [
|
|
212
|
+
'no-baseless',
|
|
213
|
+
'no-non-ascii-chars',
|
|
214
|
+
'no-uppercase-chars',
|
|
215
|
+
],
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
ogImage: {
|
|
219
|
+
enabled: false,
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
robots: {
|
|
223
|
+
disableNuxtContentIntegration: true,
|
|
224
|
+
},
|
|
225
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clarity-theme",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"packageManager": "pnpm@12.4.1",
|
|
6
|
+
"description": "Clarity Theme — 从 blog-v3 提取的可复用 Nuxt 4 Layer 博客主题",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/iicemeta/clarity-theme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/iicemeta/clarity-theme.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/iicemeta/clarity-theme/issues"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./nuxt.config.ts",
|
|
18
|
+
"./config": {
|
|
19
|
+
"types": "./config/index.d.mts",
|
|
20
|
+
"default": "./config/index.mjs"
|
|
21
|
+
},
|
|
22
|
+
"./content": {
|
|
23
|
+
"types": "./config/content.d.mts",
|
|
24
|
+
"default": "./config/content.mjs"
|
|
25
|
+
},
|
|
26
|
+
"./img": {
|
|
27
|
+
"types": "./img/index.d.mts",
|
|
28
|
+
"default": "./img/index.mjs"
|
|
29
|
+
},
|
|
30
|
+
"./schema": {
|
|
31
|
+
"types": "./config/schema.d.mts",
|
|
32
|
+
"default": "./config/schema.mjs"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"main": "./nuxt.config.ts",
|
|
36
|
+
"files": [
|
|
37
|
+
"LICENSE",
|
|
38
|
+
"README.md",
|
|
39
|
+
"app",
|
|
40
|
+
"config",
|
|
41
|
+
"img",
|
|
42
|
+
"modules",
|
|
43
|
+
"nuxt.config.ts",
|
|
44
|
+
"package.json",
|
|
45
|
+
"public",
|
|
46
|
+
"remark-plugins",
|
|
47
|
+
"server",
|
|
48
|
+
"shared"
|
|
49
|
+
],
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": "^22.19 || ^24.11 || >=26"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"dev": "pnpm --dir playground dev",
|
|
55
|
+
"build": "pnpm --dir playground build",
|
|
56
|
+
"generate": "pnpm --dir playground generate",
|
|
57
|
+
"lint": "eslint && stylelint \"app/**/*.{vue,scss}\" \"playground/app/**/*.vue\"",
|
|
58
|
+
"lint:fix": "eslint --fix && stylelint \"app/**/*.{vue,scss}\" \"playground/app/**/*.vue\" --fix",
|
|
59
|
+
"typecheck": "pnpm --dir playground typecheck",
|
|
60
|
+
"verify": "node scripts/verify-theme.mjs",
|
|
61
|
+
"test:sync": "node --test tests/sync-upstream.test.mjs",
|
|
62
|
+
"test:migration": "node --test tests/migration-skill.test.mjs",
|
|
63
|
+
"test:contract": "node scripts/test-compatibility.mjs --contract-only",
|
|
64
|
+
"test:release": "pnpm verify && pnpm test:consumer && pnpm test:compatibility",
|
|
65
|
+
"test:consumer": "node scripts/test-consumer.mjs",
|
|
66
|
+
"test:compatibility": "node scripts/test-compatibility.mjs",
|
|
67
|
+
"sync:check": "node scripts/sync-upstream.mjs check",
|
|
68
|
+
"sync:diff": "node scripts/sync-upstream.mjs diff",
|
|
69
|
+
"sync:apply": "node scripts/sync-upstream.mjs apply",
|
|
70
|
+
"sync:verify": "node scripts/sync-upstream.mjs verify"
|
|
71
|
+
},
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"nuxt": "^4.5.2",
|
|
74
|
+
"vue": "^3.5.42"
|
|
75
|
+
},
|
|
76
|
+
"dependencies": {
|
|
77
|
+
"@bikariya/image-viewer": "^0.0.8",
|
|
78
|
+
"@bikariya/modals": "^0.0.8",
|
|
79
|
+
"@bikariya/shiki": "^0.0.8",
|
|
80
|
+
"@iconify-json/catppuccin": "^1.2.17",
|
|
81
|
+
"@iconify-json/line-md": "^1.2.16",
|
|
82
|
+
"@iconify-json/ri": "^1.2.10",
|
|
83
|
+
"@iconify-json/simple-icons": "^1.2.94",
|
|
84
|
+
"@iconify-json/tabler": "^1.2.38",
|
|
85
|
+
"@nuxt/content": "^3.16.0",
|
|
86
|
+
"@nuxt/icon": "^2.5.1",
|
|
87
|
+
"@nuxt/image": "^2.1.0",
|
|
88
|
+
"@nuxtjs/color-mode": "^4.0.1",
|
|
89
|
+
"@nuxtjs/seo": "^5.3.14",
|
|
90
|
+
"@nuxtjs/sitemap": "^8.5.1",
|
|
91
|
+
"@pinia/nuxt": "^1.0.2",
|
|
92
|
+
"@shikijs/colorized-brackets": "^4.4.3",
|
|
93
|
+
"@shikijs/transformers": "^4.4.3",
|
|
94
|
+
"@vueuse/core": "^14.4.0",
|
|
95
|
+
"@vueuse/nuxt": "^14.4.0",
|
|
96
|
+
"@vueuse/router": "^14.4.0",
|
|
97
|
+
"abcjs": "^6.7.0",
|
|
98
|
+
"ci-info": "^4.4.0",
|
|
99
|
+
"embla-carousel-autoplay": "^8.6.0",
|
|
100
|
+
"embla-carousel-vue": "^8.6.0",
|
|
101
|
+
"embla-carousel-wheel-gestures": "^8.1.0",
|
|
102
|
+
"es-toolkit": "^1.52.0",
|
|
103
|
+
"fast-xml-builder": "^1.3.1",
|
|
104
|
+
"jiti": "^2.3.0",
|
|
105
|
+
"mermaid": "^11.17.2",
|
|
106
|
+
"minisearch": "^7.2.0",
|
|
107
|
+
"nuxt-llms": "^0.2.0",
|
|
108
|
+
"oxc-minify": "^0.148.0",
|
|
109
|
+
"parse-domain": "^8.4.0",
|
|
110
|
+
"pinia": "^4.0.3",
|
|
111
|
+
"plain-shiki": "^0.3.2",
|
|
112
|
+
"reading-time": "^1.5.0",
|
|
113
|
+
"rehype-katex": "^7.0.1",
|
|
114
|
+
"remark-math": "^6.0.0",
|
|
115
|
+
"remark-reading-time": "^2.1.0",
|
|
116
|
+
"sass-embedded": "^1.104.0",
|
|
117
|
+
"shiki": "^4.4.3",
|
|
118
|
+
"site-config-stack": "^4.2.3",
|
|
119
|
+
"temporal-polyfill": "^1.0.4",
|
|
120
|
+
"ufo": "^1.6.0",
|
|
121
|
+
"unist-util-visit": "^5.1.0",
|
|
122
|
+
"vfile": "^6.0.3",
|
|
123
|
+
"vue-tippy": "^6.7.1",
|
|
124
|
+
"zod": "^4.5.4"
|
|
125
|
+
},
|
|
126
|
+
"devDependencies": {
|
|
127
|
+
"@antfu/eslint-config": "^9.5.1",
|
|
128
|
+
"@nuxt/a11y": "1.0.0-alpha.1",
|
|
129
|
+
"@nuxt/hints": "^1.1.4",
|
|
130
|
+
"@stylistic/stylelint-plugin": "^5.2.1",
|
|
131
|
+
"@types/hast": "^3.0.5",
|
|
132
|
+
"@zinkawaii/stylelint-config": "^0.5.2",
|
|
133
|
+
"cspell": "^10.2.2",
|
|
134
|
+
"eslint": "^10.10.0",
|
|
135
|
+
"minimark": "0.2.0",
|
|
136
|
+
"nuxt": "^4.5.2",
|
|
137
|
+
"postcss-html": "^1.8.0",
|
|
138
|
+
"postcss-scss": "^4.0.9",
|
|
139
|
+
"rimraf": "^6.1.3",
|
|
140
|
+
"stylelint": "^17.15.0",
|
|
141
|
+
"stylelint-config-html": "^1.1.0",
|
|
142
|
+
"stylelint-config-standard": "^40.0.0",
|
|
143
|
+
"stylelint-order": "^8.1.1",
|
|
144
|
+
"typescript": "^6.0.3",
|
|
145
|
+
"vue": "^3.5.42",
|
|
146
|
+
"vue-router": "^5.3.1"
|
|
147
|
+
}
|
|
148
|
+
}
|