2024-05-27 22:43:55 +08:00

258 lines
7.0 KiB
TypeScript

import { usePageLang } from 'vuepress/client'
import { useExtraBlogData as _useExtraBlogData, useBlogPostData } from '@vuepress-plume/plugin-blog-data/client'
import { type Ref, computed } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import type { PlumeThemeBlogPostItem } from '../../shared/index.js'
import { useData, useLocaleLink, useRouteQuery } from '../composables/index.js'
import { toArray } from '../utils/index.js'
export const useExtraBlogData = _useExtraBlogData as () => Ref<{
tagsColorsPreset: (readonly [string, string, string])[]
tagsColors: Record<string, number>
}>
const DEFAULT_PER_PAGE = 10
export function useLocalePostList() {
const locale = usePageLang()
const list = useBlogPostData()
return computed(() => list.value.filter(item => item.lang === locale.value))
}
export function usePostListControl() {
const { theme } = useData()
const list = useLocalePostList()
const blog = computed(() => theme.value.blog || {})
const is960 = useMediaQuery('(max-width: 960px)')
const postList = computed(() => {
const stickyList = list.value.filter(item =>
typeof item.sticky === 'boolean' ? item.sticky : item.sticky >= 0,
)
const otherList = list.value.filter(
item => item.sticky === undefined || item.sticky === false,
)
return [
...stickyList.sort((prev, next) => {
if (next.sticky === true && prev.sticky === true)
return 0
return next.sticky > prev.sticky ? 1 : -1
}),
...otherList,
] as PlumeThemeBlogPostItem[]
})
const page = useRouteQuery('p', 1, {
mode: 'push',
transform(val) {
const page = Number(val)
if (!Number.isNaN(page) && page > 0)
return page
return 1
},
})
const totalPage = computed(() => {
if (blog.value.pagination === false)
return 0
const perPage = blog.value.pagination?.perPage || DEFAULT_PER_PAGE
return Math.ceil(postList.value.length / perPage)
})
const isLastPage = computed(() => page.value >= totalPage.value)
const isFirstPage = computed(() => page.value <= 1)
const isPaginationEnabled = computed(() => blog.value.pagination !== false && totalPage.value > 1)
const finalList = computed(() => {
if (blog.value.pagination === false)
return postList.value
const perPage = blog.value.pagination?.perPage || DEFAULT_PER_PAGE
if (postList.value.length <= perPage)
return postList.value
return postList.value.slice(
(page.value - 1) * perPage,
page.value * perPage,
)
})
const pageRange = computed(() => {
let range: { value: number | string, more?: true }[] = []
const total = totalPage.value
const _page = page.value
const per = is960.value ? 4 : 5
if (total <= 0)
return range
if (total <= 10) {
range = Array.from({ length: total }, (_, i) => ({ value: i + 1 }))
}
else {
let i = 1
let hasMore = false
while (i <= total) {
if ((_page <= per && i <= per) || (_page >= total - (per - 1) && i >= total - (per - 1))) {
hasMore = false
range.push({ value: i })
}
else if (i <= 2 || i >= total - 1) {
hasMore = false
range.push({ value: i })
}
else if (
(_page > per + 1 || _page < total - (per + 1))
&& _page - i < per - 2
&& i - _page < per - 2
) {
hasMore = false
range.push({ value: i })
}
else if (!hasMore) {
hasMore = true
range.push({ value: i, more: true })
}
i++
}
}
return range
})
const changePage = (current: number) => {
if (page.value === current)
return
page.value = current
window.scrollTo({ top: 0, left: 0, behavior: 'instant' })
}
return {
postList: finalList,
page,
totalPage,
pageRange,
isLastPage,
isFirstPage,
isPaginationEnabled,
changePage,
}
}
const extractLocales: Record<string, { tags: string, archives: string }> = {
'zh-CN': { tags: '标签', archives: '归档' },
'en': { tags: 'Tags', archives: 'Archives' },
'zh-TW': { tags: '標籤', archives: '歸檔' },
}
export function useBlogExtract() {
const { theme } = useData()
const locale = usePageLang()
const postList = useLocalePostList()
const { tags: tagsList } = useTags()
const blog = computed(() => theme.value.blog || {})
const hasBlogExtract = computed(() => blog.value.archives !== false || blog.value.tags !== false)
const tagsLink = useLocaleLink(blog.value.tagsLink || 'blog/tags/')
const archiveLink = useLocaleLink(blog.value.archivesLink || 'blog/archives/')
const tags = computed(() => ({
link: tagsLink.value,
text: extractLocales[locale.value]?.tags || extractLocales.en.tags,
total: tagsList.value.length,
}))
const archives = computed(() => ({
link: archiveLink.value,
text: extractLocales[locale.value]?.archives || extractLocales.en.archives,
total: postList.value.length,
}))
return {
hasBlogExtract,
tags,
archives,
}
}
export type ShortPostItem = Pick<PlumeThemeBlogPostItem, 'title' | 'path' | 'createTime'>
export function useTags() {
const list = useLocalePostList()
const extraData = useExtraBlogData()
const tags = computed(() => {
const tagMap: Record<string, number> = {}
list.value.forEach((item) => {
if (item.tags) {
toArray(item.tags).forEach((tag) => {
if (tagMap[tag])
tagMap[tag] += 1
else
tagMap[tag] = 1
})
}
})
return Object.keys(tagMap).map(tag => ({
name: tag,
count: tagMap[tag] > 99 ? '99+' : tagMap[tag],
colors: extraData.value.tagsColorsPreset[extraData.value.tagsColors[tag]] ?? [],
}))
})
const currentTag = useRouteQuery<string>('tag')
const postList = computed<ShortPostItem[]>(() => {
if (!currentTag.value)
return []
return list.value.filter((item) => {
if (item.tags)
return toArray(item.tags).includes(currentTag.value)
return false
}).map(item => ({
title: item.title,
path: item.path,
createTime: item.createTime.split(' ')[0].replace(/\//g, '-'),
}))
})
const handleTagClick = (tag: string) => {
currentTag.value = tag
}
return {
tags,
currentTag,
postList,
handleTagClick,
}
}
export function useArchives() {
const list = useLocalePostList()
const archives = computed(() => {
const archives: { label: string, list: ShortPostItem[] }[] = []
list.value.forEach((item) => {
const createTime = item.createTime.split(' ')[0]
const year = createTime.split('/')[0]
let current = archives.find(archive => archive.label === year)
if (!current) {
current = { label: year, list: [] }
archives.push(current)
}
current.list.push({
title: item.title,
path: item.path,
createTime: createTime.slice(year.length + 1).replace(/\//g, '-'),
})
})
return archives
})
return { archives }
}