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.
Files changed (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -1
  3. package/bin/valaxy.js +11 -0
  4. package/dist/build-LSWX3GTV.mjs +1 -0
  5. package/dist/build-QCHURQKL.js +1 -0
  6. package/dist/chunk-7NQGUR4O.mjs +1 -0
  7. package/dist/chunk-DEKAH6ZE.js +1 -0
  8. package/dist/chunk-TIPISWPV.mjs +10 -0
  9. package/dist/chunk-ZX4FV7OA.js +10 -0
  10. package/dist/cli.d.ts +3 -0
  11. package/dist/cli.js +2 -0
  12. package/dist/cli.mjs +2 -0
  13. package/dist/index.d.ts +203 -0
  14. package/dist/index.js +1 -0
  15. package/dist/index.mjs +1 -0
  16. package/package.json +75 -3
  17. package/src/client/App.vue +16 -0
  18. package/src/client/components/AppLink.vue +20 -0
  19. package/src/client/components/PostCard.vue +69 -0
  20. package/src/client/components/PostList.vue +50 -0
  21. package/src/client/components/README.md +7 -0
  22. package/src/client/components/ValaxyCopyright.vue +80 -0
  23. package/src/client/components/ValaxyFooter.vue +53 -0
  24. package/src/client/components/ValaxyHamburger.vue +21 -0
  25. package/src/client/components/ValaxyMd.vue +71 -0
  26. package/src/client/components/ValaxyOverlay.vue +44 -0
  27. package/src/client/components/ValaxyPagination.vue +122 -0
  28. package/src/client/components/ValaxyRightSidebar.vue +32 -0
  29. package/src/client/components/ValaxySidebar.vue +35 -0
  30. package/src/client/components/ValaxyToc.vue +70 -0
  31. package/src/client/composables/category.ts +101 -0
  32. package/src/client/composables/comments/index.ts +1 -0
  33. package/src/client/composables/comments/waline.ts +60 -0
  34. package/src/client/composables/common.ts +27 -0
  35. package/src/client/composables/dark.ts +4 -0
  36. package/src/client/composables/features/index.ts +1 -0
  37. package/src/client/composables/features/katex.ts +15 -0
  38. package/src/client/composables/helper.ts +26 -0
  39. package/src/client/composables/index.ts +17 -0
  40. package/src/client/composables/layout.ts +7 -0
  41. package/src/client/composables/post.ts +96 -0
  42. package/src/client/composables/search/algolia.ts +114 -0
  43. package/src/client/composables/search/index.ts +0 -0
  44. package/src/client/composables/sidebar.ts +128 -0
  45. package/src/client/composables/tag.ts +70 -0
  46. package/src/client/composables/widgets/aplayer.ts +23 -0
  47. package/src/client/composables/widgets/backToTop.ts +28 -0
  48. package/src/client/composables/widgets/codepen.ts +12 -0
  49. package/src/client/composables/widgets/index.ts +3 -0
  50. package/src/client/index.html +24 -0
  51. package/src/client/layouts/404.vue +25 -0
  52. package/src/client/layouts/README.md +14 -0
  53. package/src/client/locales/README.md +7 -0
  54. package/src/client/locales/en.yml +107 -0
  55. package/src/client/locales/zh-CN.yml +106 -0
  56. package/src/client/main.ts +30 -0
  57. package/src/client/modules/README.md +11 -0
  58. package/src/client/modules/nprogress.ts +14 -0
  59. package/src/client/modules/pinia.ts +17 -0
  60. package/src/client/modules/pwa.ts +12 -0
  61. package/src/client/modules/valaxy.ts +42 -0
  62. package/src/client/pages/README.md +20 -0
  63. package/src/client/pages/[...all].vue +15 -0
  64. package/src/client/pages/about/index.md +5 -0
  65. package/src/client/pages/hi/[name].vue +52 -0
  66. package/src/client/pages/index.vue +3 -0
  67. package/src/client/pages/page/[page].vue +12 -0
  68. package/src/client/pages/posts/index.md +5 -0
  69. package/src/client/public/_headers +3 -0
  70. package/src/client/public/favicon.svg +21 -0
  71. package/src/client/public/pwa-192x192.png +0 -0
  72. package/src/client/public/pwa-512x512.png +0 -0
  73. package/src/client/public/safari-pinned-tab.svg +41 -0
  74. package/src/client/shims.d.ts +36 -0
  75. package/src/client/stores/app.ts +14 -0
  76. package/src/client/stores/user.ts +35 -0
  77. package/src/client/styles/common/button.scss +29 -0
  78. package/src/client/styles/common/code.scss +35 -0
  79. package/src/client/styles/common/hamburger.scss +56 -0
  80. package/src/client/styles/common/markdown.scss +43 -0
  81. package/src/client/styles/common/scrollbar.scss +34 -0
  82. package/src/client/styles/common/sidebar.scss +30 -0
  83. package/src/client/styles/common/transition.scss +23 -0
  84. package/src/client/styles/css-vars/dark.scss +17 -0
  85. package/src/client/styles/css-vars/index.scss +18 -0
  86. package/src/client/styles/css-vars/light.scss +9 -0
  87. package/src/client/styles/global/helper.scss +3 -0
  88. package/src/client/styles/global/index.scss +38 -0
  89. package/src/client/styles/global/nprogress.scss +14 -0
  90. package/src/client/styles/global/reset.scss +20 -0
  91. package/src/client/styles/index.scss +18 -0
  92. package/src/client/styles/mixins/config.scss +1 -0
  93. package/src/client/styles/mixins/index.scss +2 -0
  94. package/src/client/styles/mixins/size.scss +49 -0
  95. package/src/client/styles/mixins/variable.scss +30 -0
  96. package/src/client/styles/palette.scss +61 -0
  97. package/src/client/styles/vars.scss +39 -0
  98. package/src/client/styles/widgets/banner.scss +116 -0
  99. package/src/client/types.ts +3 -0
  100. package/src/client/utils/helper.ts +30 -0
  101. package/src/client/utils/index.ts +2 -0
  102. package/src/client/utils/time.ts +23 -0
  103. package/src/core/config.ts +51 -0
  104. package/src/core/index.ts +5 -0
  105. package/src/core/utils.ts +1 -0
  106. package/src/index.ts +2 -0
  107. package/src/node/build.ts +12 -0
  108. package/src/node/cli.ts +177 -0
  109. package/src/node/config.ts +43 -0
  110. package/src/node/index.ts +1 -0
  111. package/src/node/markdown/headings.ts +24 -0
  112. package/src/node/markdown/index.ts +74 -0
  113. package/src/node/markdown/markdown-it-container.ts +53 -0
  114. package/src/node/markdown/markdown-it-katex.ts +200 -0
  115. package/src/node/markdown/parseHeader.ts +70 -0
  116. package/src/node/markdown/slugify.ts +24 -0
  117. package/src/node/options.ts +90 -0
  118. package/src/node/plugins/index.ts +91 -0
  119. package/src/node/plugins/markdown.ts +62 -0
  120. package/src/node/plugins/preset.ts +171 -0
  121. package/src/node/plugins/unocss.ts +106 -0
  122. package/src/node/plugins/valaxy.ts +1 -0
  123. package/src/node/server.ts +21 -0
  124. package/src/node/shims.d.ts +23 -0
  125. package/src/node/utils/cli.ts +105 -0
  126. package/src/node/utils/index.ts +26 -0
  127. package/src/node/vite.ts +83 -0
  128. package/src/types/config.ts +250 -0
  129. package/src/types/index.ts +2 -0
  130. package/src/types/posts.ts +107 -0
  131. 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,4 @@
1
+ import { useDark, useToggle } from '@vueuse/core'
2
+
3
+ export const isDark = useDark()
4
+ export const toggleDark = useToggle(isDark)
@@ -0,0 +1 @@
1
+ export * from './katex'
@@ -0,0 +1,15 @@
1
+ import { useHead } from '@vueuse/head'
2
+
3
+ /**
4
+ * use katex css cdn
5
+ */
6
+ export function useKatex() {
7
+ useHead({
8
+ link: [
9
+ {
10
+ rel: 'stylesheet',
11
+ href: 'https://cdn.jsdelivr.net/npm/katex@latest/dist/katex.min.css',
12
+ },
13
+ ],
14
+ })
15
+ }
@@ -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,7 @@
1
+ import { computed } from 'vue'
2
+ import { useRoute } from 'vue-router'
3
+
4
+ export function useLayout(layout: string) {
5
+ const route = useRoute()
6
+ return computed(() => route.meta.layout === layout)
7
+ }
@@ -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,12 @@
1
+ import { useHead } from '@vueuse/head'
2
+
3
+ export function useCodePen() {
4
+ useHead({
5
+ script: [
6
+ {
7
+ src: 'https://static.codepen.io/assets/embed/ei.js',
8
+ async: true,
9
+ },
10
+ ],
11
+ })
12
+ }
@@ -0,0 +1,3 @@
1
+ export * from './aplayer'
2
+ export * from './backToTop'
3
+ export * from './codepen'
@@ -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>