valaxy 0.0.2 → 0.0.5
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 +1 -1
- package/bin/valaxy.js +11 -0
- package/dist/build-LSWX3GTV.mjs +1 -0
- package/dist/build-QCHURQKL.js +1 -0
- package/dist/chunk-7NQGUR4O.mjs +1 -0
- package/dist/chunk-DEKAH6ZE.js +1 -0
- package/dist/chunk-TIPISWPV.mjs +10 -0
- package/dist/chunk-ZX4FV7OA.js +10 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +2 -0
- package/dist/cli.mjs +2 -0
- package/dist/index.d.ts +203 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/package.json +75 -3
- package/src/client/App.vue +16 -0
- package/src/client/components/AppLink.vue +20 -0
- package/src/client/components/PostCard.vue +69 -0
- package/src/client/components/PostList.vue +50 -0
- package/src/client/components/README.md +7 -0
- package/src/client/components/ValaxyCopyright.vue +80 -0
- package/src/client/components/ValaxyFooter.vue +53 -0
- package/src/client/components/ValaxyHamburger.vue +21 -0
- package/src/client/components/ValaxyMd.vue +71 -0
- package/src/client/components/ValaxyOverlay.vue +44 -0
- package/src/client/components/ValaxyPagination.vue +122 -0
- package/src/client/components/ValaxyRightSidebar.vue +32 -0
- package/src/client/components/ValaxySidebar.vue +35 -0
- package/src/client/components/ValaxyToc.vue +70 -0
- package/src/client/composables/category.ts +101 -0
- package/src/client/composables/comments/index.ts +1 -0
- package/src/client/composables/comments/waline.ts +60 -0
- package/src/client/composables/common.ts +27 -0
- package/src/client/composables/dark.ts +4 -0
- package/src/client/composables/features/index.ts +1 -0
- package/src/client/composables/features/katex.ts +15 -0
- package/src/client/composables/helper.ts +26 -0
- package/src/client/composables/index.ts +17 -0
- package/src/client/composables/layout.ts +7 -0
- package/src/client/composables/post.ts +96 -0
- package/src/client/composables/search/algolia.ts +114 -0
- package/src/client/composables/search/index.ts +0 -0
- package/src/client/composables/sidebar.ts +128 -0
- package/src/client/composables/tag.ts +70 -0
- package/src/client/composables/widgets/aplayer.ts +23 -0
- package/src/client/composables/widgets/backToTop.ts +28 -0
- package/src/client/composables/widgets/codepen.ts +12 -0
- package/src/client/composables/widgets/index.ts +3 -0
- package/src/client/index.html +24 -0
- package/src/client/layouts/404.vue +25 -0
- package/src/client/layouts/README.md +14 -0
- package/src/client/locales/README.md +7 -0
- package/src/client/locales/en.yml +107 -0
- package/src/client/locales/zh-CN.yml +106 -0
- package/src/client/main.ts +30 -0
- package/src/client/modules/README.md +11 -0
- package/src/client/modules/nprogress.ts +14 -0
- package/src/client/modules/pinia.ts +17 -0
- package/src/client/modules/pwa.ts +12 -0
- package/src/client/modules/valaxy.ts +42 -0
- package/src/client/pages/README.md +20 -0
- package/src/client/pages/[...all].vue +15 -0
- package/src/client/pages/about/index.md +5 -0
- package/src/client/pages/hi/[name].vue +52 -0
- package/src/client/pages/index.vue +3 -0
- package/src/client/pages/page/[page].vue +12 -0
- package/src/client/pages/posts/index.md +5 -0
- package/src/client/public/_headers +3 -0
- package/src/client/public/favicon.svg +21 -0
- package/src/client/public/pwa-192x192.png +0 -0
- package/src/client/public/pwa-512x512.png +0 -0
- package/src/client/public/safari-pinned-tab.svg +41 -0
- package/src/client/shims.d.ts +36 -0
- package/src/client/stores/app.ts +14 -0
- package/src/client/stores/user.ts +35 -0
- package/src/client/styles/common/button.scss +29 -0
- package/src/client/styles/common/code.scss +35 -0
- package/src/client/styles/common/hamburger.scss +56 -0
- package/src/client/styles/common/markdown.scss +43 -0
- package/src/client/styles/common/scrollbar.scss +34 -0
- package/src/client/styles/common/sidebar.scss +30 -0
- package/src/client/styles/common/transition.scss +23 -0
- package/src/client/styles/css-vars/dark.scss +17 -0
- package/src/client/styles/css-vars/index.scss +18 -0
- package/src/client/styles/css-vars/light.scss +9 -0
- package/src/client/styles/global/helper.scss +3 -0
- package/src/client/styles/global/index.scss +38 -0
- package/src/client/styles/global/nprogress.scss +14 -0
- package/src/client/styles/global/reset.scss +20 -0
- package/src/client/styles/index.scss +18 -0
- package/src/client/styles/mixins/config.scss +1 -0
- package/src/client/styles/mixins/index.scss +2 -0
- package/src/client/styles/mixins/size.scss +49 -0
- package/src/client/styles/mixins/variable.scss +30 -0
- package/src/client/styles/palette.scss +61 -0
- package/src/client/styles/vars.scss +39 -0
- package/src/client/styles/widgets/banner.scss +116 -0
- package/src/client/types.ts +3 -0
- package/src/client/utils/helper.ts +30 -0
- package/src/client/utils/index.ts +2 -0
- package/src/client/utils/time.ts +23 -0
- package/src/core/config.ts +51 -0
- package/src/core/index.ts +5 -0
- package/src/core/utils.ts +1 -0
- package/src/index.ts +2 -0
- package/src/node/build.ts +12 -0
- package/src/node/cli.ts +177 -0
- package/src/node/config.ts +43 -0
- package/src/node/index.ts +1 -0
- package/src/node/markdown/headings.ts +24 -0
- package/src/node/markdown/index.ts +74 -0
- package/src/node/markdown/markdown-it-container.ts +53 -0
- package/src/node/markdown/markdown-it-katex.ts +200 -0
- package/src/node/markdown/parseHeader.ts +70 -0
- package/src/node/markdown/slugify.ts +24 -0
- package/src/node/options.ts +90 -0
- package/src/node/plugins/index.ts +91 -0
- package/src/node/plugins/markdown.ts +62 -0
- package/src/node/plugins/preset.ts +171 -0
- package/src/node/plugins/unocss.ts +106 -0
- package/src/node/plugins/valaxy.ts +1 -0
- package/src/node/server.ts +21 -0
- package/src/node/shims.d.ts +23 -0
- package/src/node/utils/cli.ts +105 -0
- package/src/node/utils/index.ts +26 -0
- package/src/node/vite.ts +83 -0
- package/src/types/config.ts +250 -0
- package/src/types/index.ts +2 -0
- package/src/types/posts.ts +107 -0
- package/tsup.config.ts +17 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { isClient, useScriptTag } from '@vueuse/core'
|
|
2
|
+
import { onUnmounted, watch } from 'vue'
|
|
3
|
+
import { useI18n } from 'vue-i18n'
|
|
4
|
+
import { useRoute } from 'vue-router'
|
|
5
|
+
|
|
6
|
+
export function useWaline(options: {} = {}) {
|
|
7
|
+
const route = useRoute()
|
|
8
|
+
|
|
9
|
+
const { locale } = useI18n()
|
|
10
|
+
|
|
11
|
+
let waline: any
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* init waline
|
|
15
|
+
* @param options waline options
|
|
16
|
+
* @returns
|
|
17
|
+
*/
|
|
18
|
+
function initWaline(options: {} = {}) {
|
|
19
|
+
if (!isClient) return
|
|
20
|
+
|
|
21
|
+
const defaultOptions = {
|
|
22
|
+
el: '.comment #waline',
|
|
23
|
+
lang: locale.value,
|
|
24
|
+
dark: 'html.dark',
|
|
25
|
+
emoji: [
|
|
26
|
+
'https://cdn.jsdelivr.net/gh/walinejs/emojis@1.0.0/bilibili',
|
|
27
|
+
'https://cdn.jsdelivr.net/gh/walinejs/emojis@1.0.0/qq',
|
|
28
|
+
'https://cdn.jsdelivr.net/gh/walinejs/emojis@1.0.0/weibo',
|
|
29
|
+
],
|
|
30
|
+
path: route.path,
|
|
31
|
+
}
|
|
32
|
+
const walineOptions = Object.assign(defaultOptions, options)
|
|
33
|
+
// @ts-expect-error waline type
|
|
34
|
+
return window.Waline(walineOptions)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 直接使用 CDN
|
|
38
|
+
useScriptTag('//cdn.jsdelivr.net/npm/@waline/client', () => {
|
|
39
|
+
waline = initWaline(options)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
watch(() => route.path, (path) => {
|
|
43
|
+
if (!waline) return
|
|
44
|
+
waline.update({
|
|
45
|
+
path,
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
watch(locale, (lang) => {
|
|
50
|
+
if (!waline) return
|
|
51
|
+
waline.update({
|
|
52
|
+
lang,
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
onUnmounted(() => {
|
|
57
|
+
if (!waline) return
|
|
58
|
+
waline.destroy()
|
|
59
|
+
})
|
|
60
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useRoute } from 'vue-router'
|
|
2
|
+
import type { Post } from 'valaxy'
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
|
|
5
|
+
import { isDev, useConfig } from 'valaxy'
|
|
6
|
+
|
|
7
|
+
export function useFrontmatter() {
|
|
8
|
+
const route = useRoute()
|
|
9
|
+
const frontmatter = computed<Post>(() => route.meta.frontmatter)
|
|
10
|
+
|
|
11
|
+
// eslint-disable-next-line no-console
|
|
12
|
+
if (isDev) console.log(frontmatter.value)
|
|
13
|
+
return frontmatter
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* get full url
|
|
18
|
+
*/
|
|
19
|
+
export function useFullUrl() {
|
|
20
|
+
const config = useConfig()
|
|
21
|
+
const route = useRoute()
|
|
22
|
+
const url = computed(() => {
|
|
23
|
+
const origin = config.value.url || window?.location.origin
|
|
24
|
+
return origin + route.path
|
|
25
|
+
})
|
|
26
|
+
return url
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './katex'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useElementBounding, useIntersectionObserver } from '@vueuse/core'
|
|
2
|
+
import type { Ref } from 'vue'
|
|
3
|
+
import { ref } from 'vue'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* trigger show invisible element
|
|
7
|
+
* @param target
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
export function useInvisibleElement(target: Ref<HTMLElement>) {
|
|
11
|
+
const isVisible = ref(false)
|
|
12
|
+
const { top } = useElementBounding(target)
|
|
13
|
+
useIntersectionObserver(target, ([{ isIntersecting }]) => {
|
|
14
|
+
isVisible.value = isIntersecting
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const show = () => {
|
|
18
|
+
// scroll when collapse is not visible
|
|
19
|
+
if (!isVisible.value)
|
|
20
|
+
window.scrollTo(0, top.value)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
show,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// for classify
|
|
2
|
+
export * from './category'
|
|
3
|
+
export * from './post'
|
|
4
|
+
export * from './tag'
|
|
5
|
+
|
|
6
|
+
// common
|
|
7
|
+
export * from './common'
|
|
8
|
+
export * from './features'
|
|
9
|
+
export * from './helper'
|
|
10
|
+
export * from './dark'
|
|
11
|
+
export * from './layout'
|
|
12
|
+
export * from './widgets'
|
|
13
|
+
|
|
14
|
+
export * from './sidebar'
|
|
15
|
+
|
|
16
|
+
// comment
|
|
17
|
+
export * from './comments'
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { sortByDate } from 'valaxy'
|
|
2
|
+
import type { StyleValue } from 'vue'
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
import { useRoute, useRouter } from 'vue-router'
|
|
5
|
+
import { useThemeConfig } from '../../core/config'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* get post list
|
|
9
|
+
* todo: use vue provide/inject to global
|
|
10
|
+
* @param params
|
|
11
|
+
* @returns
|
|
12
|
+
*/
|
|
13
|
+
export function usePostList(params: {
|
|
14
|
+
type?: string
|
|
15
|
+
} = {}) {
|
|
16
|
+
const router = useRouter()
|
|
17
|
+
return computed(() => {
|
|
18
|
+
const routes = router.getRoutes()
|
|
19
|
+
.filter(i => i.path.startsWith('/posts') && i.meta.frontmatter && i.meta.frontmatter.date)
|
|
20
|
+
.filter(i => !i.path.endsWith('.html'))
|
|
21
|
+
.filter(i => !params.type || i.meta.frontmatter.type === params.type)
|
|
22
|
+
.map((i) => {
|
|
23
|
+
return Object.assign({ path: i.path, excerpt: i.meta.excerpt }, i.meta.frontmatter)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 置顶
|
|
28
|
+
*/
|
|
29
|
+
const topPosts = sortByDate(routes.filter(i => i.top)).sort((a, b) => b.top! - a.top!)
|
|
30
|
+
const otherPosts = sortByDate(routes.filter(i => !i.top))
|
|
31
|
+
|
|
32
|
+
return topPosts.concat(otherPosts)
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* get prev and next post
|
|
38
|
+
* @param path
|
|
39
|
+
* @returns
|
|
40
|
+
*/
|
|
41
|
+
export function usePrevNext(path?: string) {
|
|
42
|
+
const route = useRoute()
|
|
43
|
+
const p = computed(() => path || route.path)
|
|
44
|
+
const routes = usePostList()
|
|
45
|
+
|
|
46
|
+
const index = computed(() => {
|
|
47
|
+
let order = -1
|
|
48
|
+
routes.value.find((item, i) => {
|
|
49
|
+
if (item.path === p.value) {
|
|
50
|
+
order = i
|
|
51
|
+
return true
|
|
52
|
+
}
|
|
53
|
+
return false
|
|
54
|
+
})
|
|
55
|
+
return order
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const prev = computed(() => index.value - 1 >= 0 ? routes.value[index.value - 1] : null)
|
|
59
|
+
const next = computed(() => index.value + 1 < routes.value.length ? routes.value[index.value + 1] : null)
|
|
60
|
+
|
|
61
|
+
return [prev, next]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* get type card property
|
|
66
|
+
* todo: test reactive
|
|
67
|
+
*/
|
|
68
|
+
export function usePostProperty(type?: string) {
|
|
69
|
+
if (!type) {
|
|
70
|
+
return {
|
|
71
|
+
color: '',
|
|
72
|
+
icon: '',
|
|
73
|
+
styles: {},
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const themeConfig = useThemeConfig()
|
|
78
|
+
|
|
79
|
+
if (!(type in themeConfig.value.types))
|
|
80
|
+
type = 'link'
|
|
81
|
+
|
|
82
|
+
const color = themeConfig.value.types[type].color
|
|
83
|
+
const icon = themeConfig.value.types[type].icon
|
|
84
|
+
|
|
85
|
+
const styles = computed(() => {
|
|
86
|
+
return {
|
|
87
|
+
'--card-c-primary': type && color,
|
|
88
|
+
} as StyleValue
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
color,
|
|
93
|
+
icon,
|
|
94
|
+
styles,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { isClient, useScriptTag } from '@vueuse/core'
|
|
2
|
+
import { useI18n } from 'vue-i18n'
|
|
3
|
+
import { useRoute } from 'vue-router'
|
|
4
|
+
|
|
5
|
+
const algoliasearchUrl = 'https://cdn.jsdelivr.net/npm/algoliasearch@4/dist/algoliasearch-lite.umd.js'
|
|
6
|
+
const instantsearchUrl = 'https://cdn.jsdelivr.net/npm/instantsearch.js@4/dist/instantsearch.production.min.js'
|
|
7
|
+
|
|
8
|
+
export function useAlgoliaSearch(config: {
|
|
9
|
+
appId: string
|
|
10
|
+
apiKey: string
|
|
11
|
+
indexName: string
|
|
12
|
+
hits: {
|
|
13
|
+
per_page: number
|
|
14
|
+
}
|
|
15
|
+
}) {
|
|
16
|
+
if (!isClient) return
|
|
17
|
+
|
|
18
|
+
const route = useRoute()
|
|
19
|
+
const { t } = useI18n()
|
|
20
|
+
|
|
21
|
+
useScriptTag(algoliasearchUrl, () => {
|
|
22
|
+
useScriptTag(instantsearchUrl, () => {
|
|
23
|
+
const algoliaSettings = config
|
|
24
|
+
const { indexName, appId, apiKey } = algoliaSettings
|
|
25
|
+
|
|
26
|
+
const search = window.instantsearch({
|
|
27
|
+
indexName,
|
|
28
|
+
searchClient: window.algoliasearch(appId, apiKey),
|
|
29
|
+
searchFunction: (helper: { search: () => void }) => {
|
|
30
|
+
const searchInput = document.querySelector('.search-input') as HTMLInputElement
|
|
31
|
+
if (searchInput.value)
|
|
32
|
+
helper.search()
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// Registering Widgets
|
|
37
|
+
search.addWidgets([
|
|
38
|
+
window.instantsearch.widgets.configure({
|
|
39
|
+
hitsPerPage: algoliaSettings.hits.per_page || 8,
|
|
40
|
+
}),
|
|
41
|
+
|
|
42
|
+
window.instantsearch.widgets.searchBox({
|
|
43
|
+
container: '.search-input-container',
|
|
44
|
+
placeholder: t('search.placeholder'),
|
|
45
|
+
// Hide default icons of algolia search
|
|
46
|
+
showReset: false,
|
|
47
|
+
showSubmit: false,
|
|
48
|
+
showLoadingIndicator: false,
|
|
49
|
+
cssClasses: {
|
|
50
|
+
input: 'search-input',
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
53
|
+
|
|
54
|
+
window.instantsearch.widgets.stats({
|
|
55
|
+
container: '#algolia-stats',
|
|
56
|
+
templates: {
|
|
57
|
+
text: (data: any) => {
|
|
58
|
+
const stats = t('search.hits_time', {
|
|
59
|
+
hits: data.nbHits,
|
|
60
|
+
time: data.processingTimeMS,
|
|
61
|
+
})
|
|
62
|
+
return `<span>${stats}</span>
|
|
63
|
+
<a href="https://www.algolia.com/" target="_blank" class="algolia-powered">
|
|
64
|
+
<img src="https://simpleicons.org/icons/algolia.svg" alt="Algolia">
|
|
65
|
+
</a>
|
|
66
|
+
`
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
|
|
71
|
+
window.instantsearch.widgets.hits({
|
|
72
|
+
container: '#algolia-hits',
|
|
73
|
+
templates: {
|
|
74
|
+
item: (data: any) => {
|
|
75
|
+
const link = data.permalink ? data.permalink : route.path
|
|
76
|
+
return `<a href="${link}" class="algolia-hit-item-link">
|
|
77
|
+
${data._highlightResult.title.value}
|
|
78
|
+
<small>${data._highlightResult.slug.value}</small>
|
|
79
|
+
</a>`
|
|
80
|
+
},
|
|
81
|
+
empty: (data: any) => {
|
|
82
|
+
return `<div id="algolia-hits-empty">${t('search.empty', data.query)}</div>`
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
cssClasses: {
|
|
86
|
+
item: 'algolia-hit-item',
|
|
87
|
+
},
|
|
88
|
+
}),
|
|
89
|
+
|
|
90
|
+
window.instantsearch.widgets.pagination({
|
|
91
|
+
container: '#algolia-pagination',
|
|
92
|
+
scrollTo: false,
|
|
93
|
+
showFirst: false,
|
|
94
|
+
showLast: false,
|
|
95
|
+
templates: {
|
|
96
|
+
first: '<div i-ri-arrow-left-line></div>',
|
|
97
|
+
last: '<div i-ri-arrow-right-line></div>',
|
|
98
|
+
previous: '<div i-ri-arrow-left-s-line></div>',
|
|
99
|
+
next: '<div i-ri-arrow-right-s-line></div>',
|
|
100
|
+
},
|
|
101
|
+
cssClasses: {
|
|
102
|
+
root: 'pagination',
|
|
103
|
+
item: 'page-number',
|
|
104
|
+
link: 'page-number',
|
|
105
|
+
selectedItem: 'active',
|
|
106
|
+
disabledItem: 'disabled-item',
|
|
107
|
+
},
|
|
108
|
+
}),
|
|
109
|
+
])
|
|
110
|
+
|
|
111
|
+
search.start()
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { onMounted, onUnmounted, onUpdated } from 'vue'
|
|
2
|
+
|
|
3
|
+
// todo: refactor
|
|
4
|
+
|
|
5
|
+
export function useActiveSidebarLinks() {
|
|
6
|
+
let rootActiveLink: HTMLAnchorElement | null = null
|
|
7
|
+
let activeLink: HTMLAnchorElement | null = null
|
|
8
|
+
|
|
9
|
+
const onScroll = throttleAndDebounce(setActiveLink, 200)
|
|
10
|
+
|
|
11
|
+
function setActiveLink(): void {
|
|
12
|
+
const sidebarLinks = getSidebarLinks()
|
|
13
|
+
const anchors = getAnchors(sidebarLinks)
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < anchors.length; i++) {
|
|
16
|
+
const anchor = anchors[i]
|
|
17
|
+
const nextAnchor = anchors[i + 1]
|
|
18
|
+
|
|
19
|
+
const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor)
|
|
20
|
+
|
|
21
|
+
if (isActive) {
|
|
22
|
+
history.replaceState(null, document.title, hash || ' ')
|
|
23
|
+
activateLink(hash)
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function activateLink(hash: string | null): void {
|
|
30
|
+
deactiveLink(activeLink)
|
|
31
|
+
deactiveLink(rootActiveLink)
|
|
32
|
+
|
|
33
|
+
activeLink = document.querySelector(`.val-toc a[href="${hash}"]`)
|
|
34
|
+
|
|
35
|
+
if (!activeLink)
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
activeLink.classList.add('active')
|
|
39
|
+
|
|
40
|
+
// also add active class to parent h2 anchors
|
|
41
|
+
const rootLi = activeLink.closest('.right-sidebar-container > ul > li')
|
|
42
|
+
|
|
43
|
+
if (rootLi && rootLi !== activeLink.parentElement) {
|
|
44
|
+
rootActiveLink = rootLi.querySelector('a')
|
|
45
|
+
rootActiveLink && rootActiveLink.classList.add('active')
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
rootActiveLink = null
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function deactiveLink(link: HTMLAnchorElement | null): void {
|
|
53
|
+
link && link.classList.remove('active')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
onMounted(() => {
|
|
57
|
+
setActiveLink()
|
|
58
|
+
window.addEventListener('scroll', onScroll)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
onUpdated(() => {
|
|
62
|
+
// sidebar update means a route change
|
|
63
|
+
activateLink(decodeURIComponent(location.hash))
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
onUnmounted(() => {
|
|
67
|
+
window.removeEventListener('scroll', onScroll)
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getSidebarLinks(): HTMLAnchorElement[] {
|
|
72
|
+
return [].slice.call(
|
|
73
|
+
document.querySelectorAll('.val-toc a.toc-link-item'),
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getAnchors(sidebarLinks: HTMLAnchorElement[]): HTMLAnchorElement[] {
|
|
78
|
+
return [].slice
|
|
79
|
+
.call(document.querySelectorAll('.header-anchor'))
|
|
80
|
+
.filter((anchor: HTMLAnchorElement) =>
|
|
81
|
+
sidebarLinks.some(sidebarLink => sidebarLink.hash === anchor.hash),
|
|
82
|
+
) as HTMLAnchorElement[]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getAnchorTop(anchor: HTMLAnchorElement): number {
|
|
86
|
+
return anchor.parentElement!.offsetTop - 50
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function isAnchorActive(
|
|
90
|
+
index: number,
|
|
91
|
+
anchor: HTMLAnchorElement,
|
|
92
|
+
nextAnchor: HTMLAnchorElement,
|
|
93
|
+
): [boolean, string | null] {
|
|
94
|
+
const scrollTop = window.scrollY
|
|
95
|
+
|
|
96
|
+
if (index === 0 && scrollTop === 0)
|
|
97
|
+
return [true, null]
|
|
98
|
+
|
|
99
|
+
if (scrollTop < getAnchorTop(anchor))
|
|
100
|
+
return [false, null]
|
|
101
|
+
|
|
102
|
+
if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor))
|
|
103
|
+
return [true, decodeURIComponent(anchor.hash)]
|
|
104
|
+
|
|
105
|
+
return [false, null]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function throttleAndDebounce(fn: () => void, delay: number): () => void {
|
|
109
|
+
let timeout: number
|
|
110
|
+
let called = false
|
|
111
|
+
|
|
112
|
+
return () => {
|
|
113
|
+
if (timeout)
|
|
114
|
+
clearTimeout(timeout)
|
|
115
|
+
|
|
116
|
+
if (!called) {
|
|
117
|
+
fn()
|
|
118
|
+
called = true
|
|
119
|
+
setTimeout(() => {
|
|
120
|
+
called = false
|
|
121
|
+
}, delay)
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
// @ts-expect-error browser
|
|
125
|
+
timeout = setTimeout(fn, delay)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { TinyColor } from '@ctrl/tinycolor'
|
|
2
|
+
import { usePostList } from './post'
|
|
3
|
+
|
|
4
|
+
export type Tags = Map<string, {
|
|
5
|
+
count: number
|
|
6
|
+
}>
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* get utils about tags
|
|
10
|
+
*/
|
|
11
|
+
export function useTags() {
|
|
12
|
+
const tags = useTag()
|
|
13
|
+
|
|
14
|
+
const gray = new TinyColor('#999999')
|
|
15
|
+
const primaryColor = new TinyColor(getComputedStyle(document.documentElement).getPropertyValue('--yun-c-primary'))
|
|
16
|
+
|
|
17
|
+
const getTagStyle = (count: number) => {
|
|
18
|
+
const counts = Array.from(tags).map(([_, value]) => value.count)
|
|
19
|
+
const max = Math.max(...counts)
|
|
20
|
+
const min = Math.min(...counts)
|
|
21
|
+
const range = max - min
|
|
22
|
+
const percent = (count - min) / range
|
|
23
|
+
return {
|
|
24
|
+
'--yun-tag-color': gray.mix(primaryColor, percent * 100).toString(),
|
|
25
|
+
'fontSize': `${percent * 36 + 12}px`,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
tags,
|
|
31
|
+
getTagStyle,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* get tag map
|
|
37
|
+
* @returns
|
|
38
|
+
*/
|
|
39
|
+
export function useTag() {
|
|
40
|
+
const posts = usePostList()
|
|
41
|
+
|
|
42
|
+
const tagMap: Tags = new Map()
|
|
43
|
+
|
|
44
|
+
posts.value.forEach((post) => {
|
|
45
|
+
if (post.tags) {
|
|
46
|
+
let tags: string[]
|
|
47
|
+
if (typeof post.tags === 'string')
|
|
48
|
+
tags = [post.tags]
|
|
49
|
+
else
|
|
50
|
+
tags = post.tags
|
|
51
|
+
|
|
52
|
+
tags.forEach((tag) => {
|
|
53
|
+
if (tagMap.has(tag)) {
|
|
54
|
+
const item = tagMap.get(tag)!
|
|
55
|
+
tagMap.set(tag, {
|
|
56
|
+
...item,
|
|
57
|
+
count: item.count + 1,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
tagMap.set(tag, {
|
|
62
|
+
count: 1,
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
return tagMap
|
|
70
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useScriptTag } from '@vueuse/core'
|
|
2
|
+
import { useHead } from '@vueuse/head'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* use MetingJS and Aplayer
|
|
6
|
+
* https://github.com/MoePlayer/APlayer
|
|
7
|
+
* https://github.com/metowolf/MetingJS
|
|
8
|
+
*/
|
|
9
|
+
export function useAplayer() {
|
|
10
|
+
useHead({
|
|
11
|
+
link: [
|
|
12
|
+
{
|
|
13
|
+
rel: 'stylesheet',
|
|
14
|
+
href: 'https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.css',
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
// load meting after aplayer
|
|
20
|
+
useScriptTag('https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.js', () => {
|
|
21
|
+
useScriptTag('https://cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js')
|
|
22
|
+
})
|
|
23
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useWindowScroll } from '@vueuse/core'
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* You can use href="#" to back to top
|
|
6
|
+
* @description 你可以使用它来编写自己的 backToTop
|
|
7
|
+
*/
|
|
8
|
+
export function useBackToTop(options: {
|
|
9
|
+
/**
|
|
10
|
+
* show backToTop button, when height > offset
|
|
11
|
+
*/
|
|
12
|
+
offset: number
|
|
13
|
+
} = {
|
|
14
|
+
offset: 100,
|
|
15
|
+
}) {
|
|
16
|
+
const { y } = useWindowScroll()
|
|
17
|
+
|
|
18
|
+
const percentage = computed(() => {
|
|
19
|
+
return y.value / document.body.clientHeight
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const show = computed(() => y.value > options.offset)
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
percentage,
|
|
26
|
+
show,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
7
|
+
<link rel="apple-touch-icon" href="/pwa-192x192.png">
|
|
8
|
+
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#00aba9">
|
|
9
|
+
<meta name="msapplication-TileColor" content="#00aba9">
|
|
10
|
+
<meta name="theme-color" content="#ffffff">
|
|
11
|
+
<script>
|
|
12
|
+
(function() {
|
|
13
|
+
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
14
|
+
const setting = localStorage.getItem('vueuse-color-scheme') || 'auto'
|
|
15
|
+
if (setting === 'dark' || (prefersDark && setting !== 'light'))
|
|
16
|
+
document.documentElement.classList.toggle('dark', true)
|
|
17
|
+
})()
|
|
18
|
+
</script>
|
|
19
|
+
</head>
|
|
20
|
+
<body class="font-sans">
|
|
21
|
+
<div id="app"></div>
|
|
22
|
+
<script type="module" src="/main.ts"></script>
|
|
23
|
+
</body>
|
|
24
|
+
</html>
|