clarity-theme 0.1.0 → 0.1.2
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 +20 -20
- package/README.md +70 -4
- package/README.zh-CN.md +67 -4
- package/nuxt.config.ts +217 -214
- package/package.json +35 -37
- package/{app → src}/app.config.ts +110 -110
- package/{app → src}/app.vue +5 -5
- package/{app → src}/components/blog/BlogAside.vue +25 -25
- package/{app → src}/components/blog/BlogFooter.vue +25 -25
- package/{app → src}/components/blog/BlogHeader.global.vue +48 -48
- package/{app → src}/components/blog/BlogPanel.vue +43 -43
- package/{app → src}/components/blog/BlogSidebar.vue +53 -53
- package/{app → src}/components/blog/BlogWidget.vue +42 -42
- package/{app → src}/components/blog/Mask.vue +25 -25
- package/{app → src}/components/blog/SkipToContent.vue +9 -9
- package/{app → src}/components/blog/ThemeToggle.vue +22 -22
- package/{app → src}/components/content/Badge.vue +45 -45
- package/{app → src}/components/content/Blur.vue +14 -14
- package/{app → src}/components/content/CardList.vue +8 -8
- package/{app → src}/components/content/Chat.vue +41 -41
- package/{app → src}/components/content/Copy.vue +71 -71
- package/{app → src}/components/content/EmojiClock.vue +49 -49
- package/{app → src}/components/content/FeedCard.vue +96 -96
- package/{app → src}/components/content/FeedGroup.vue +61 -61
- package/{app → src}/components/content/Folding.vue +19 -19
- package/{app → src}/components/content/Key.vue +143 -143
- package/{app → src}/components/content/LinkBanner.vue +27 -27
- package/{app → src}/components/content/LinkCard.vue +28 -28
- package/{app → src}/components/content/MdTitle.vue +8 -8
- package/{app → src}/components/content/Mermaid.vue +99 -99
- package/{app → src}/components/content/MusicScore.vue +63 -63
- package/{app → src}/components/content/Pic.vue +49 -49
- package/{app → src}/components/content/Poetry.vue +27 -27
- package/{app → src}/components/content/ProseA.vue +24 -24
- package/{app → src}/components/content/ProseCode.vue +16 -16
- package/{app → src}/components/content/ProsePre.vue +117 -117
- package/{app → src}/components/content/ProseTable.vue +23 -23
- package/{app → src}/components/content/Quote.vue +21 -21
- package/{app → src}/components/content/Tab.vue +32 -32
- package/{app → src}/components/content/Timeline.vue +33 -33
- package/{app → src}/components/content/Tip.vue +40 -40
- package/{app → src}/components/content/VideoEmbed.vue +112 -112
- package/{app → src}/components/partial/Button.vue +36 -36
- package/{app → src}/components/partial/DlGroup.vue +31 -31
- package/{app → src}/components/partial/Dropdown.vue +21 -21
- package/{app → src}/components/partial/Error.vue +39 -39
- package/{app → src}/components/partial/Expand.vue +26 -26
- package/{app → src}/components/partial/IconNavList.vue +24 -24
- package/{app → src}/components/partial/Pagination.vue +62 -62
- package/{app → src}/components/partial/RadioGroup.vue +30 -30
- package/{app → src}/components/partial/Secret.vue +11 -11
- package/{app → src}/components/partial/Slider.vue +48 -48
- package/{app → src}/components/partial/Toggle.vue +17 -17
- package/{app → src}/components/popover/Lightbox.vue +44 -44
- package/{app → src}/components/popover/Search.vue +143 -143
- package/{app → src}/components/popover/SearchItem.vue +28 -28
- package/{app → src}/components/post/Archive.vue +40 -40
- package/{app → src}/components/post/Article.vue +49 -49
- package/{app → src}/components/post/Comment.vue +119 -119
- package/{app → src}/components/post/Excerpt.vue +40 -40
- package/{app → src}/components/post/OrderToggle.vue +70 -70
- package/{app → src}/components/post/PostFooter.vue +52 -52
- package/{app → src}/components/post/PostHeader.vue +66 -66
- package/{app → src}/components/post/PostSurround.vue +51 -51
- package/{app → src}/components/post/Slide.vue +85 -85
- package/{app → src}/components/util/Date.vue +62 -62
- package/{app → src}/components/util/HydrateSafe.vue +10 -10
- package/{app → src}/components/util/Img.vue +38 -38
- package/{app → src}/components/util/Link.vue +11 -11
- package/{app → src}/components/widget/BlogLog.vue +23 -23
- package/{app → src}/components/widget/BlogStats.vue +38 -38
- package/{app → src}/components/widget/BlogTech.vue +57 -57
- package/{app → src}/components/widget/Empty.vue +3 -3
- package/{app → src}/components/widget/Toc.vue +67 -67
- package/{app → src}/composables/useArticle.ts +115 -115
- package/{app → src}/composables/useAvoid.ts +87 -87
- package/{app → src}/composables/useCopy.ts +21 -21
- package/{app → src}/composables/usePagination.ts +70 -70
- package/{app → src}/composables/useShiki.ts +129 -129
- package/{app → src}/composables/useToc.ts +57 -57
- package/{app → src}/composables/useWidgets.ts +56 -56
- package/{config → src/config}/app.ts +62 -62
- package/{config → src/config}/content.ts +75 -75
- package/{config → src/config}/define.ts +3 -3
- package/src/config/feeds.empty.ts +6 -0
- package/{config → src/config}/index.ts +1 -1
- package/{config → src/config}/schema.ts +89 -89
- package/{app → src}/error.vue +31 -31
- package/{img → src/img}/index.ts +2 -2
- package/{app → src}/layouts/default.vue +25 -25
- package/{modules → src/modules}/clarity-config/index.ts +168 -162
- package/src/modules/clarity-source-layout/index.ts +78 -0
- package/{app → src}/pages/[...slug].vue +59 -59
- package/{app → src}/pages/archive.vue +129 -129
- package/{app → src}/pages/index.vue +76 -76
- package/{app → src}/pages/link.vue +62 -62
- package/{app → src}/pages/preview.vue +46 -46
- package/{app → src}/plugins/easter-egg.ts +23 -23
- package/{app → src}/plugins/init.ts +6 -6
- package/{app → src}/plugins/tippy.ts +22 -22
- package/{public → src/public}/assets/atom.css +274 -274
- package/{public → src/public}/assets/atom.xsl +109 -109
- package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.ts +58 -58
- package/{remark-plugins → src/remark-plugins}/remark-code-component.ts +41 -41
- package/{server → src/server}/api/stats.get.ts +84 -83
- package/{server → src/server}/routes/atom.xml.get.ts +85 -85
- package/{server → src/server}/routes/subscriptions.opml.get.ts +64 -64
- package/{shared → src/shared}/utils/clarity.ts +24 -24
- package/{shared → src/shared}/utils/icon.ts +218 -218
- package/{shared → src/shared}/utils/link.ts +52 -52
- package/{shared → src/shared}/utils/str.ts +88 -88
- package/{shared → src/shared}/utils/time.ts +115 -115
- package/{app → src}/shiki.config.ts +8 -8
- package/{app → src}/stores/layout.ts +34 -34
- package/{app → src}/stores/search.ts +37 -37
- package/{app → src}/types/article.ts +16 -16
- package/src/types/feed.ts +1 -0
- package/{app → src}/types/index.ts +14 -14
- package/{app → src}/types/nav.ts +11 -11
- package/{app → src}/utils/anim.ts +37 -37
- package/{app → src}/utils/img.ts +83 -83
- package/app/types/feed.ts +0 -1
- package/config/feeds.empty.ts +0 -4
- package/{app → src}/assets/css/_variable.scss +0 -0
- package/{app → src}/assets/css/animation.scss +0 -0
- package/{app → src}/assets/css/article.scss +0 -0
- package/{app → src}/assets/css/color.scss +0 -0
- package/{app → src}/assets/css/font.scss +0 -0
- package/{app → src}/assets/css/main.scss +0 -0
- package/{app → src}/assets/css/reusable.scss +0 -0
- package/{app → src}/assets/icons/solar-rewind-back-bold-duotone.svg +0 -0
- package/{app → src}/assets/icons/solar-rewind-forward-bold-duotone.svg +0 -0
- package/{app → src}/components/content/Alert.vue +0 -0
- package/{config → src/config}/content.d.mts +0 -0
- package/{config → src/config}/content.mjs +0 -0
- package/{config → src/config}/define.mjs +0 -0
- package/{config → src/config}/feed.ts +0 -0
- package/{config → src/config}/index.d.mts +0 -0
- package/{config → src/config}/index.mjs +0 -0
- package/{config → src/config}/public.ts +1 -1
- package/{config → src/config}/schema.d.mts +0 -0
- package/{config → src/config}/schema.mjs +0 -0
- package/{config → src/config}/server.ts +1 -1
- /package/{img → src/img}/index.d.mts +0 -0
- /package/{img → src/img}/index.mjs +0 -0
- /package/{modules → src/modules}/clarity-config/anti-mirror-client.ts +0 -0
- /package/{public → src/public}/fonts/AlimamaFangYuanTi.woff2 +0 -0
- /package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.d.mts +0 -0
- /package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.mjs +0 -0
- /package/{remark-plugins → src/remark-plugins}/remark-code-component.d.mts +0 -0
- /package/{remark-plugins → src/remark-plugins}/remark-code-component.mjs +0 -0
- /package/{server → src/server}/utils/clarity.ts +0 -0
- /package/{shared → src/shared}/types/content.ts +0 -0
- /package/{app → src}/types/modal.ts +0 -0
|
@@ -1,129 +1,129 @@
|
|
|
1
|
-
import type { BundledLanguage, CodeToHastOptions, HighlighterCore, ShikiTransformer } from 'shiki'
|
|
2
|
-
import { transformerColorizedBrackets } from '@shikijs/colorized-brackets'
|
|
3
|
-
import { transformerNotationDiff, transformerNotationErrorLevel, transformerNotationFocus, transformerNotationHighlight, transformerNotationWordHighlight, transformerRenderIndentGuides, transformerRenderWhitespace } from '@shikijs/transformers'
|
|
4
|
-
|
|
5
|
-
type TransformerOptions = Array<
|
|
6
|
-
| 'ignoreColorizedBrackets'
|
|
7
|
-
| 'ignoreRenderWhitespace'
|
|
8
|
-
| 'ignoreRenderIndentGuides'
|
|
9
|
-
>
|
|
10
|
-
type ShikiOptions = CodeToHastOptions<BundledLanguage, string>
|
|
11
|
-
|
|
12
|
-
const plainTextLanguages = new Set(['ansi', 'log', 'text'])
|
|
13
|
-
|
|
14
|
-
interface ShikiCodeOptions {
|
|
15
|
-
language: string
|
|
16
|
-
transformerOptions?: TransformerOptions
|
|
17
|
-
shikiOptions?: Omit<ShikiOptions, 'lang'>
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
type ShikiHtmlOptions = ShikiCodeOptions & {
|
|
21
|
-
embeddedLanguages?: boolean
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function getEmbeddedMarkdownLanguages(code: string, language: string) {
|
|
25
|
-
if (language !== 'markdown' && !language.startsWith('md'))
|
|
26
|
-
return []
|
|
27
|
-
|
|
28
|
-
// 处理 Markdown 高亮内代码块中的语言
|
|
29
|
-
// 加载 TeX 语言有概率导致 LaTeX 语言高亮炸掉
|
|
30
|
-
const mdLangRegex = /^\s*`{3,}(\S+)/gm
|
|
31
|
-
return [...new Set(
|
|
32
|
-
Array
|
|
33
|
-
.from(code.matchAll(mdLangRegex), match => match[1])
|
|
34
|
-
.filter(lang => lang !== undefined),
|
|
35
|
-
)]
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function transformerUnwrap(): ShikiTransformer {
|
|
39
|
-
return {
|
|
40
|
-
root: (hast) => {
|
|
41
|
-
const lines = (hast.children[0] as any).children[0].children
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
type: 'root',
|
|
45
|
-
children: lines,
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
line(node, line) {
|
|
49
|
-
node.properties['data-line'] = line
|
|
50
|
-
},
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function getTransformers(options: ShikiCodeOptions): ShikiTransformer[] {
|
|
55
|
-
const ignored = new Set(options.transformerOptions)
|
|
56
|
-
const inline = options.shikiOptions?.structure === 'inline'
|
|
57
|
-
const ignoreInvisibleChars = inline || plainTextLanguages.has(options.language)
|
|
58
|
-
|
|
59
|
-
return [
|
|
60
|
-
transformerNotationDiff(),
|
|
61
|
-
transformerNotationHighlight(),
|
|
62
|
-
transformerNotationWordHighlight(),
|
|
63
|
-
transformerNotationFocus(),
|
|
64
|
-
transformerNotationErrorLevel(),
|
|
65
|
-
ignored.has('ignoreRenderIndentGuides') || ignoreInvisibleChars ? {} : transformerRenderIndentGuides(),
|
|
66
|
-
ignored.has('ignoreRenderWhitespace') || ignoreInvisibleChars ? {} : transformerRenderWhitespace(),
|
|
67
|
-
ignored.has('ignoreColorizedBrackets') ? {} : transformerColorizedBrackets(),
|
|
68
|
-
inline ? {} : transformerUnwrap(),
|
|
69
|
-
]
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export default function useShiki() {
|
|
73
|
-
const shikiStore = useShikiStore()
|
|
74
|
-
let shiki: HighlighterCore
|
|
75
|
-
|
|
76
|
-
function getOptions(options: ShikiCodeOptions): ShikiOptions {
|
|
77
|
-
// Shiki 未收录的语言会在高亮时抛错,回退为纯文本
|
|
78
|
-
// ansi 是内置特殊语言,不在已加载语法列表中
|
|
79
|
-
const language = options.language === 'ansi' || shiki.getLoadedLanguages().includes(options.language) ? options.language : 'text'
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
...shikiStore.options,
|
|
83
|
-
lang: language,
|
|
84
|
-
transformers: getTransformers({ ...options, language }),
|
|
85
|
-
...options.shikiOptions,
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async function codeToHtml(code: string, language: string): Promise<string>
|
|
90
|
-
async function codeToHtml(code: string, options: ShikiHtmlOptions): Promise<string>
|
|
91
|
-
async function codeToHtml(code: string, languageOrOptions: string | ShikiHtmlOptions): Promise<string> {
|
|
92
|
-
const options = typeof languageOrOptions === 'string' ? { language: languageOrOptions } : languageOrOptions
|
|
93
|
-
shiki = await shikiStore.load()
|
|
94
|
-
await shikiStore.loadLang(options.language)
|
|
95
|
-
|
|
96
|
-
if (typeof languageOrOptions !== 'string' && languageOrOptions.embeddedLanguages)
|
|
97
|
-
await shikiStore.loadLang(...getEmbeddedMarkdownLanguages(code, options.language))
|
|
98
|
-
|
|
99
|
-
return shiki.codeToHtml(code, getOptions(options))
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async function mountPlain(target: HTMLElement, language: string): Promise<void>
|
|
103
|
-
async function mountPlain(target: HTMLElement, options: ShikiCodeOptions): Promise<void>
|
|
104
|
-
async function mountPlain(target: HTMLElement, languageOrOptions: string | ShikiCodeOptions): Promise<void> {
|
|
105
|
-
const options = typeof languageOrOptions === 'string' ? { language: languageOrOptions } : languageOrOptions
|
|
106
|
-
shiki = await shikiStore.load()
|
|
107
|
-
const { createPlainShiki } = await import('plain-shiki')
|
|
108
|
-
|
|
109
|
-
await shikiStore.loadLang(options.language)
|
|
110
|
-
createPlainShiki(shiki).mount(target, getOptions(options))
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async function mountInline(target: HTMLElement, code: string, options: ShikiCodeOptions): Promise<void> {
|
|
114
|
-
target.classList.add('shiki')
|
|
115
|
-
target.insertAdjacentHTML('afterbegin', await codeToHtml(code, {
|
|
116
|
-
...options,
|
|
117
|
-
shikiOptions: {
|
|
118
|
-
...options.shikiOptions,
|
|
119
|
-
structure: 'inline',
|
|
120
|
-
},
|
|
121
|
-
}))
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
codeToHtml,
|
|
126
|
-
mountInline,
|
|
127
|
-
mountPlain,
|
|
128
|
-
}
|
|
129
|
-
}
|
|
1
|
+
import type { BundledLanguage, CodeToHastOptions, HighlighterCore, ShikiTransformer } from 'shiki'
|
|
2
|
+
import { transformerColorizedBrackets } from '@shikijs/colorized-brackets'
|
|
3
|
+
import { transformerNotationDiff, transformerNotationErrorLevel, transformerNotationFocus, transformerNotationHighlight, transformerNotationWordHighlight, transformerRenderIndentGuides, transformerRenderWhitespace } from '@shikijs/transformers'
|
|
4
|
+
|
|
5
|
+
type TransformerOptions = Array<
|
|
6
|
+
| 'ignoreColorizedBrackets'
|
|
7
|
+
| 'ignoreRenderWhitespace'
|
|
8
|
+
| 'ignoreRenderIndentGuides'
|
|
9
|
+
>
|
|
10
|
+
type ShikiOptions = CodeToHastOptions<BundledLanguage, string>
|
|
11
|
+
|
|
12
|
+
const plainTextLanguages = new Set(['ansi', 'log', 'text'])
|
|
13
|
+
|
|
14
|
+
interface ShikiCodeOptions {
|
|
15
|
+
language: string
|
|
16
|
+
transformerOptions?: TransformerOptions
|
|
17
|
+
shikiOptions?: Omit<ShikiOptions, 'lang'>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type ShikiHtmlOptions = ShikiCodeOptions & {
|
|
21
|
+
embeddedLanguages?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getEmbeddedMarkdownLanguages(code: string, language: string) {
|
|
25
|
+
if (language !== 'markdown' && !language.startsWith('md'))
|
|
26
|
+
return []
|
|
27
|
+
|
|
28
|
+
// 处理 Markdown 高亮内代码块中的语言
|
|
29
|
+
// 加载 TeX 语言有概率导致 LaTeX 语言高亮炸掉
|
|
30
|
+
const mdLangRegex = /^\s*`{3,}(\S+)/gm
|
|
31
|
+
return [...new Set(
|
|
32
|
+
Array
|
|
33
|
+
.from(code.matchAll(mdLangRegex), match => match[1])
|
|
34
|
+
.filter(lang => lang !== undefined),
|
|
35
|
+
)]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function transformerUnwrap(): ShikiTransformer {
|
|
39
|
+
return {
|
|
40
|
+
root: (hast) => {
|
|
41
|
+
const lines = (hast.children[0] as any).children[0].children
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
type: 'root',
|
|
45
|
+
children: lines,
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
line(node, line) {
|
|
49
|
+
node.properties['data-line'] = line
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getTransformers(options: ShikiCodeOptions): ShikiTransformer[] {
|
|
55
|
+
const ignored = new Set(options.transformerOptions)
|
|
56
|
+
const inline = options.shikiOptions?.structure === 'inline'
|
|
57
|
+
const ignoreInvisibleChars = inline || plainTextLanguages.has(options.language)
|
|
58
|
+
|
|
59
|
+
return [
|
|
60
|
+
transformerNotationDiff(),
|
|
61
|
+
transformerNotationHighlight(),
|
|
62
|
+
transformerNotationWordHighlight(),
|
|
63
|
+
transformerNotationFocus(),
|
|
64
|
+
transformerNotationErrorLevel(),
|
|
65
|
+
ignored.has('ignoreRenderIndentGuides') || ignoreInvisibleChars ? {} : transformerRenderIndentGuides(),
|
|
66
|
+
ignored.has('ignoreRenderWhitespace') || ignoreInvisibleChars ? {} : transformerRenderWhitespace(),
|
|
67
|
+
ignored.has('ignoreColorizedBrackets') ? {} : transformerColorizedBrackets(),
|
|
68
|
+
inline ? {} : transformerUnwrap(),
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default function useShiki() {
|
|
73
|
+
const shikiStore = useShikiStore()
|
|
74
|
+
let shiki: HighlighterCore
|
|
75
|
+
|
|
76
|
+
function getOptions(options: ShikiCodeOptions): ShikiOptions {
|
|
77
|
+
// Shiki 未收录的语言会在高亮时抛错,回退为纯文本
|
|
78
|
+
// ansi 是内置特殊语言,不在已加载语法列表中
|
|
79
|
+
const language = options.language === 'ansi' || shiki.getLoadedLanguages().includes(options.language) ? options.language : 'text'
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
...shikiStore.options,
|
|
83
|
+
lang: language,
|
|
84
|
+
transformers: getTransformers({ ...options, language }),
|
|
85
|
+
...options.shikiOptions,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function codeToHtml(code: string, language: string): Promise<string>
|
|
90
|
+
async function codeToHtml(code: string, options: ShikiHtmlOptions): Promise<string>
|
|
91
|
+
async function codeToHtml(code: string, languageOrOptions: string | ShikiHtmlOptions): Promise<string> {
|
|
92
|
+
const options = typeof languageOrOptions === 'string' ? { language: languageOrOptions } : languageOrOptions
|
|
93
|
+
shiki = await shikiStore.load()
|
|
94
|
+
await shikiStore.loadLang(options.language)
|
|
95
|
+
|
|
96
|
+
if (typeof languageOrOptions !== 'string' && languageOrOptions.embeddedLanguages)
|
|
97
|
+
await shikiStore.loadLang(...getEmbeddedMarkdownLanguages(code, options.language))
|
|
98
|
+
|
|
99
|
+
return shiki.codeToHtml(code, getOptions(options))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function mountPlain(target: HTMLElement, language: string): Promise<void>
|
|
103
|
+
async function mountPlain(target: HTMLElement, options: ShikiCodeOptions): Promise<void>
|
|
104
|
+
async function mountPlain(target: HTMLElement, languageOrOptions: string | ShikiCodeOptions): Promise<void> {
|
|
105
|
+
const options = typeof languageOrOptions === 'string' ? { language: languageOrOptions } : languageOrOptions
|
|
106
|
+
shiki = await shikiStore.load()
|
|
107
|
+
const { createPlainShiki } = await import('plain-shiki')
|
|
108
|
+
|
|
109
|
+
await shikiStore.loadLang(options.language)
|
|
110
|
+
createPlainShiki(shiki).mount(target, getOptions(options))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function mountInline(target: HTMLElement, code: string, options: ShikiCodeOptions): Promise<void> {
|
|
114
|
+
target.classList.add('shiki')
|
|
115
|
+
target.insertAdjacentHTML('afterbegin', await codeToHtml(code, {
|
|
116
|
+
...options,
|
|
117
|
+
shikiOptions: {
|
|
118
|
+
...options.shikiOptions,
|
|
119
|
+
structure: 'inline',
|
|
120
|
+
},
|
|
121
|
+
}))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
codeToHtml,
|
|
126
|
+
mountInline,
|
|
127
|
+
mountPlain,
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
import type { Toc, TocLink } from '@nuxt/content'
|
|
2
|
-
import type { MaybeComputedElementRef } from '@vueuse/core'
|
|
3
|
-
|
|
4
|
-
interface TocList {
|
|
5
|
-
id: string
|
|
6
|
-
offsetTop: number | undefined
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function useToc(toc: MaybeRefOrGetter<Toc | undefined>, scrollableEl?: MaybeComputedElementRef) {
|
|
10
|
-
const { height: bodyHeight } = useElementSize(document?.body)
|
|
11
|
-
const { y: scrollY } = useWindowScroll()
|
|
12
|
-
|
|
13
|
-
function flattenToc(tocTree: TocLink[], tocList: TocList[] = []) {
|
|
14
|
-
tocTree.forEach(({ id, children }) => {
|
|
15
|
-
const headingOffset = document?.getElementById(id)?.offsetTop
|
|
16
|
-
tocList.push({ id, offsetTop: headingOffset })
|
|
17
|
-
children && flattenToc(children, tocList)
|
|
18
|
-
})
|
|
19
|
-
return tocList
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const tocOffsets = computedWithControl(
|
|
23
|
-
refDebounced(bodyHeight),
|
|
24
|
-
() => flattenToc(toValue(toc)?.links || []).reverse(),
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
function getActiveHeading() {
|
|
28
|
-
const scrollMargin = Number.parseFloat(getComputedStyle(document.documentElement).getPropertyValue('scroll-margin-top'))
|
|
29
|
-
const scrollOffset = scrollY.value + (scrollMargin || 64)
|
|
30
|
-
// 为兼容性不使用 findLast,而是使用倒序的 tocOffsets
|
|
31
|
-
return tocOffsets.value.find(item => item.offsetTop && item.offsetTop <= scrollOffset)?.id
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const activeHeadingId = computedWithControl(
|
|
35
|
-
refThrottled(scrollY, undefined, true),
|
|
36
|
-
() => document && getActiveHeading(),
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
function scrollToActiveTocItem() {
|
|
40
|
-
const el = unrefElement(scrollableEl)
|
|
41
|
-
const active = el?.querySelector<HTMLLinkElement>(`a[href="#${activeHeadingId.value}"]`)
|
|
42
|
-
|
|
43
|
-
if (el && active) {
|
|
44
|
-
el.scrollTo({
|
|
45
|
-
top: active.offsetTop - el.clientHeight / 4,
|
|
46
|
-
behavior: 'smooth',
|
|
47
|
-
})
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
watch(activeHeadingId, scrollToActiveTocItem)
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
tocOffsets,
|
|
55
|
-
activeHeadingId,
|
|
56
|
-
}
|
|
57
|
-
}
|
|
1
|
+
import type { Toc, TocLink } from '@nuxt/content'
|
|
2
|
+
import type { MaybeComputedElementRef } from '@vueuse/core'
|
|
3
|
+
|
|
4
|
+
interface TocList {
|
|
5
|
+
id: string
|
|
6
|
+
offsetTop: number | undefined
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function useToc(toc: MaybeRefOrGetter<Toc | undefined>, scrollableEl?: MaybeComputedElementRef) {
|
|
10
|
+
const { height: bodyHeight } = useElementSize(document?.body)
|
|
11
|
+
const { y: scrollY } = useWindowScroll()
|
|
12
|
+
|
|
13
|
+
function flattenToc(tocTree: TocLink[], tocList: TocList[] = []) {
|
|
14
|
+
tocTree.forEach(({ id, children }) => {
|
|
15
|
+
const headingOffset = document?.getElementById(id)?.offsetTop
|
|
16
|
+
tocList.push({ id, offsetTop: headingOffset })
|
|
17
|
+
children && flattenToc(children, tocList)
|
|
18
|
+
})
|
|
19
|
+
return tocList
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const tocOffsets = computedWithControl(
|
|
23
|
+
refDebounced(bodyHeight),
|
|
24
|
+
() => flattenToc(toValue(toc)?.links || []).reverse(),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
function getActiveHeading() {
|
|
28
|
+
const scrollMargin = Number.parseFloat(getComputedStyle(document.documentElement).getPropertyValue('scroll-margin-top'))
|
|
29
|
+
const scrollOffset = scrollY.value + (scrollMargin || 64)
|
|
30
|
+
// 为兼容性不使用 findLast,而是使用倒序的 tocOffsets
|
|
31
|
+
return tocOffsets.value.find(item => item.offsetTop && item.offsetTop <= scrollOffset)?.id
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const activeHeadingId = computedWithControl(
|
|
35
|
+
refThrottled(scrollY, undefined, true),
|
|
36
|
+
() => document && getActiveHeading(),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
function scrollToActiveTocItem() {
|
|
40
|
+
const el = unrefElement(scrollableEl)
|
|
41
|
+
const active = el?.querySelector<HTMLLinkElement>(`a[href="#${activeHeadingId.value}"]`)
|
|
42
|
+
|
|
43
|
+
if (el && active) {
|
|
44
|
+
el.scrollTo({
|
|
45
|
+
top: active.offsetTop - el.clientHeight / 4,
|
|
46
|
+
behavior: 'smooth',
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
watch(activeHeadingId, scrollToActiveTocItem)
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
tocOffsets,
|
|
55
|
+
activeHeadingId,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
import { pascalCase } from 'es-toolkit/string'
|
|
2
|
-
import {
|
|
3
|
-
ContentRenderer,
|
|
4
|
-
LazyBlogWidget,
|
|
5
|
-
LazyWidgetBlogLog,
|
|
6
|
-
LazyWidgetBlogStats,
|
|
7
|
-
LazyWidgetBlogTech,
|
|
8
|
-
LazyWidgetEmpty,
|
|
9
|
-
LazyWidgetToc,
|
|
10
|
-
} from '#components'
|
|
11
|
-
|
|
12
|
-
// @keep-sorted
|
|
13
|
-
const rawWidgets = {
|
|
14
|
-
LazyWidgetBlogLog,
|
|
15
|
-
LazyWidgetBlogStats,
|
|
16
|
-
LazyWidgetBlogTech,
|
|
17
|
-
LazyWidgetEmpty,
|
|
18
|
-
LazyWidgetToc,
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
type RawWidgetName = keyof typeof rawWidgets
|
|
22
|
-
|
|
23
|
-
/** 若首字母大写还需移除`-`前缀 */
|
|
24
|
-
type KebabCase<S extends string> = S extends `${infer First}${infer Rest}`
|
|
25
|
-
? `${First extends Capitalize<First> ? '-' : ''}${Lowercase<First>}${KebabCase<Rest>}`
|
|
26
|
-
: ''
|
|
27
|
-
|
|
28
|
-
type RemovePrefix<S extends string, Prefix extends string> = S extends `${Prefix}${infer Rest}` ? Rest : S
|
|
29
|
-
|
|
30
|
-
export type WidgetName = RemovePrefix<KebabCase<RawWidgetName>, '-lazy-widget-'> | `meta-aside-${string}`
|
|
31
|
-
|
|
32
|
-
export default function useWidgets(widgetList: MaybeRefOrGetter<WidgetName[]>) {
|
|
33
|
-
const { metaSlots } = useArticle()
|
|
34
|
-
|
|
35
|
-
function renderMetaSlots(widgetName: WidgetName) {
|
|
36
|
-
const slotsTree = metaSlots.value?.[widgetName.slice('meta-'.length)]
|
|
37
|
-
return h(
|
|
38
|
-
LazyBlogWidget,
|
|
39
|
-
{ card: !slotsTree, ...slotsTree?.props },
|
|
40
|
-
() => slotsTree
|
|
41
|
-
? h(ContentRenderer, { value: slotsTree })
|
|
42
|
-
: `${widgetName} 不存在`,
|
|
43
|
-
)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const widgets = computed(() => toValue(widgetList).map(widgetName => ({
|
|
47
|
-
name: widgetName,
|
|
48
|
-
comp: widgetName.startsWith('meta-aside-')
|
|
49
|
-
? renderMetaSlots(widgetName)
|
|
50
|
-
: rawWidgets[`LazyWidget${pascalCase(widgetName)}` as RawWidgetName],
|
|
51
|
-
})))
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
widgets,
|
|
55
|
-
}
|
|
56
|
-
}
|
|
1
|
+
import { pascalCase } from 'es-toolkit/string'
|
|
2
|
+
import {
|
|
3
|
+
ContentRenderer,
|
|
4
|
+
LazyBlogWidget,
|
|
5
|
+
LazyWidgetBlogLog,
|
|
6
|
+
LazyWidgetBlogStats,
|
|
7
|
+
LazyWidgetBlogTech,
|
|
8
|
+
LazyWidgetEmpty,
|
|
9
|
+
LazyWidgetToc,
|
|
10
|
+
} from '#components'
|
|
11
|
+
|
|
12
|
+
// @keep-sorted
|
|
13
|
+
const rawWidgets = {
|
|
14
|
+
LazyWidgetBlogLog,
|
|
15
|
+
LazyWidgetBlogStats,
|
|
16
|
+
LazyWidgetBlogTech,
|
|
17
|
+
LazyWidgetEmpty,
|
|
18
|
+
LazyWidgetToc,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type RawWidgetName = keyof typeof rawWidgets
|
|
22
|
+
|
|
23
|
+
/** 若首字母大写还需移除`-`前缀 */
|
|
24
|
+
type KebabCase<S extends string> = S extends `${infer First}${infer Rest}`
|
|
25
|
+
? `${First extends Capitalize<First> ? '-' : ''}${Lowercase<First>}${KebabCase<Rest>}`
|
|
26
|
+
: ''
|
|
27
|
+
|
|
28
|
+
type RemovePrefix<S extends string, Prefix extends string> = S extends `${Prefix}${infer Rest}` ? Rest : S
|
|
29
|
+
|
|
30
|
+
export type WidgetName = RemovePrefix<KebabCase<RawWidgetName>, '-lazy-widget-'> | `meta-aside-${string}`
|
|
31
|
+
|
|
32
|
+
export default function useWidgets(widgetList: MaybeRefOrGetter<WidgetName[]>) {
|
|
33
|
+
const { metaSlots } = useArticle()
|
|
34
|
+
|
|
35
|
+
function renderMetaSlots(widgetName: WidgetName) {
|
|
36
|
+
const slotsTree = metaSlots.value?.[widgetName.slice('meta-'.length)]
|
|
37
|
+
return h(
|
|
38
|
+
LazyBlogWidget,
|
|
39
|
+
{ card: !slotsTree, ...slotsTree?.props },
|
|
40
|
+
() => slotsTree
|
|
41
|
+
? h(ContentRenderer, { value: slotsTree })
|
|
42
|
+
: `${widgetName} 不存在`,
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const widgets = computed(() => toValue(widgetList).map(widgetName => ({
|
|
47
|
+
name: widgetName,
|
|
48
|
+
comp: widgetName.startsWith('meta-aside-')
|
|
49
|
+
? renderMetaSlots(widgetName)
|
|
50
|
+
: rawWidgets[`LazyWidget${pascalCase(widgetName)}` as RawWidgetName],
|
|
51
|
+
})))
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
widgets,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
import type { NavItem } from '../
|
|
1
|
+
import type { NavItem } from '../types/nav'
|
|
2
2
|
import type { ClarityPublicConfig } from './public'
|
|
3
3
|
|
|
4
4
|
export interface NavGroup {
|
|
5
5
|
title: string
|
|
6
6
|
items: NavItem[]
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
/** Theme 提供的 UI 默认值,全部可由消费项目在 app/app.config.ts 中覆盖 */
|
|
10
|
-
export interface ClarityUiConfig {
|
|
11
|
-
component: {
|
|
12
|
-
alert: {
|
|
13
|
-
defaultStyle: 'card' | 'flat'
|
|
14
|
-
}
|
|
15
|
-
codeblock: {
|
|
16
|
-
triggerRows: number
|
|
17
|
-
collapsedRows: number
|
|
18
|
-
enableIndentGuide: boolean
|
|
19
|
-
indent: number
|
|
20
|
-
tabSize: number
|
|
21
|
-
}
|
|
22
|
-
excerpt: {
|
|
23
|
-
animation: boolean
|
|
24
|
-
caret: string
|
|
25
|
-
}
|
|
26
|
-
slide: {
|
|
27
|
-
showTitle: boolean
|
|
28
|
-
}
|
|
29
|
-
stats: {
|
|
30
|
-
/** 归档页面每年标题对应的年龄 */
|
|
31
|
-
birthYear?: number
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
footer: {
|
|
35
|
-
/** 页脚版权信息,支持 <br> 等内联 HTML */
|
|
36
|
-
copyright: string
|
|
37
|
-
/** 侧边栏底部图标导航 */
|
|
38
|
-
iconNav: NavItem[]
|
|
8
|
+
|
|
9
|
+
/** Theme 提供的 UI 默认值,全部可由消费项目在 app/app.config.ts 中覆盖 */
|
|
10
|
+
export interface ClarityUiConfig {
|
|
11
|
+
component: {
|
|
12
|
+
alert: {
|
|
13
|
+
defaultStyle: 'card' | 'flat'
|
|
14
|
+
}
|
|
15
|
+
codeblock: {
|
|
16
|
+
triggerRows: number
|
|
17
|
+
collapsedRows: number
|
|
18
|
+
enableIndentGuide: boolean
|
|
19
|
+
indent: number
|
|
20
|
+
tabSize: number
|
|
21
|
+
}
|
|
22
|
+
excerpt: {
|
|
23
|
+
animation: boolean
|
|
24
|
+
caret: string
|
|
25
|
+
}
|
|
26
|
+
slide: {
|
|
27
|
+
showTitle: boolean
|
|
28
|
+
}
|
|
29
|
+
stats: {
|
|
30
|
+
/** 归档页面每年标题对应的年龄 */
|
|
31
|
+
birthYear?: number
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
footer: {
|
|
35
|
+
/** 页脚版权信息,支持 <br> 等内联 HTML */
|
|
36
|
+
copyright: string
|
|
37
|
+
/** 侧边栏底部图标导航 */
|
|
38
|
+
iconNav: NavItem[]
|
|
39
39
|
/** 页脚站点地图 */
|
|
40
40
|
nav: NavGroup[]
|
|
41
|
-
}
|
|
42
|
-
header: {
|
|
43
|
-
/** 左侧栏顶部 Logo,默认使用 site.author.avatar */
|
|
44
|
-
logo: string
|
|
45
|
-
/** 展示标题文本,否则仅展示 Logo */
|
|
46
|
-
showTitle: boolean
|
|
47
|
-
/** 标题下的副标题,默认使用 site.subtitle */
|
|
48
|
-
subtitle: string
|
|
49
|
-
/** 标题后的随机表情 */
|
|
50
|
-
emojiTail: string[]
|
|
51
|
-
}
|
|
52
|
-
link: {
|
|
53
|
-
/** 无订阅源展示静音图标 */
|
|
54
|
-
remindNoFeed: boolean
|
|
55
|
-
/** 友链分组内随机排序 */
|
|
56
|
-
randomInGroup: boolean
|
|
57
|
-
}
|
|
41
|
+
}
|
|
42
|
+
header: {
|
|
43
|
+
/** 左侧栏顶部 Logo,默认使用 site.author.avatar */
|
|
44
|
+
logo: string
|
|
45
|
+
/** 展示标题文本,否则仅展示 Logo */
|
|
46
|
+
showTitle: boolean
|
|
47
|
+
/** 标题下的副标题,默认使用 site.subtitle */
|
|
48
|
+
subtitle: string
|
|
49
|
+
/** 标题后的随机表情 */
|
|
50
|
+
emojiTail: string[]
|
|
51
|
+
}
|
|
52
|
+
link: {
|
|
53
|
+
/** 无订阅源展示静音图标 */
|
|
54
|
+
remindNoFeed: boolean
|
|
55
|
+
/** 友链分组内随机排序 */
|
|
56
|
+
randomInGroup: boolean
|
|
57
|
+
}
|
|
58
58
|
/** 左侧栏导航 */
|
|
59
59
|
nav: NavGroup[]
|
|
60
|
-
pagination: {
|
|
61
|
-
perPage: number
|
|
62
|
-
/** 默认排序方式,需为 article.order 中的键名 */
|
|
63
|
-
sortOrder: string
|
|
64
|
-
/** 允许文章列表正序,开启后排序方式左侧图标可切换顺序 */
|
|
65
|
-
allowAscending: boolean
|
|
66
|
-
}
|
|
67
|
-
themes: Record<'light' | 'system' | 'dark', {
|
|
68
|
-
icon: string
|
|
69
|
-
tip: string
|
|
70
|
-
}>
|
|
71
|
-
}
|
|
72
|
-
|
|
60
|
+
pagination: {
|
|
61
|
+
perPage: number
|
|
62
|
+
/** 默认排序方式,需为 article.order 中的键名 */
|
|
63
|
+
sortOrder: string
|
|
64
|
+
/** 允许文章列表正序,开启后排序方式左侧图标可切换顺序 */
|
|
65
|
+
allowAscending: boolean
|
|
66
|
+
}
|
|
67
|
+
themes: Record<'light' | 'system' | 'dark', {
|
|
68
|
+
icon: string
|
|
69
|
+
tip: string
|
|
70
|
+
}>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
73
|
export type ClarityAppConfig = ClarityPublicConfig & ClarityUiConfig
|
|
74
74
|
|
|
75
75
|
/**
|