diff --git a/theme/src/client/composables/blog.ts b/theme/src/client/composables/blog.ts index ab6957bc..86c61aa5 100644 --- a/theme/src/client/composables/blog.ts +++ b/theme/src/client/composables/blog.ts @@ -1,8 +1,8 @@ import { usePageLang } from '@vuepress/client' import { useBlogPostData } from '@vuepress-plume/plugin-blog-data/client' -import { computed, ref } from 'vue' +import { computed } from 'vue' import type { PlumeThemeBlogPostItem } from '../../shared/index.js' -import { useLocaleLink, useThemeLocaleData } from '../composables/index.js' +import { useLocaleLink, useRouteQuery, useThemeLocaleData } from '../composables/index.js' import { getRandomColor, toArray } from '../utils/index.js' export function useLocalePostList() { @@ -33,10 +33,18 @@ export function usePostListControl() { return next.sticky > prev.sticky ? 1 : -1 }), ...otherList, - ] + ] as PlumeThemeBlogPostItem[] }) - const page = ref(1) + 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) @@ -133,14 +141,15 @@ export function useTags() { })) }) - const postList = ref([]) - const currentTag = ref() + const currentTag = useRouteQuery('tag') - const handleTagClick = (tag: string) => { - currentTag.value = tag - postList.value = list.value.filter((item) => { + const postList = computed(() => { + if (!currentTag.value) + return [] + + return list.value.filter((item) => { if (item.tags) - return toArray(item.tags).includes(tag) + return toArray(item.tags).includes(currentTag.value) return false }).map(item => ({ @@ -148,6 +157,10 @@ export function useTags() { path: item.path, createTime: item.createTime.split(' ')[0].replace(/\//g, '-'), })) + }) + + const handleTagClick = (tag: string) => { + currentTag.value = tag } return { diff --git a/theme/src/client/composables/useRouteQuery.ts b/theme/src/client/composables/useRouteQuery.ts new file mode 100644 index 00000000..b4eb7e63 --- /dev/null +++ b/theme/src/client/composables/useRouteQuery.ts @@ -0,0 +1,126 @@ +import { customRef, nextTick, toValue, watch } from 'vue' +import type { MaybeRef, MaybeRefOrGetter, Ref } from 'vue' +import { useRoute, useRouter } from 'vue-router' +import type { RouteParamValueRaw, Router } from 'vue-router' +import { tryOnScopeDispose } from '@vueuse/core' + +export type RouteQueryValueRaw = RouteParamValueRaw | string[] + +export interface ReactiveRouteOptions { + /** + * Mode to update the router query, ref is also acceptable + * + * @default 'replace' + */ + mode?: MaybeRef<'replace' | 'push'> + + /** + * Route instance, use `useRoute()` if not given + */ + route?: ReturnType + + /** + * Router instance, use `useRouter()` if not given + */ + router?: ReturnType +} + +export interface ReactiveRouteOptionsWithTransform extends ReactiveRouteOptions { + /** + * Function to transform data before return + */ + transform?: (val: V) => R +} + +const _queue = new WeakMap>() + +export function useRouteQuery( + name: string +): Ref + +export function useRouteQuery< + T extends RouteQueryValueRaw = RouteQueryValueRaw, + K = T, +>( + name: string, + defaultValue?: MaybeRefOrGetter, + options?: ReactiveRouteOptionsWithTransform +): Ref + +export function useRouteQuery< + T extends RouteQueryValueRaw = RouteQueryValueRaw, + K = T, +>( + name: string, + defaultValue?: MaybeRefOrGetter, + options: ReactiveRouteOptionsWithTransform = {}, +): Ref { + const { + mode = 'replace', + route = useRoute(), + router = useRouter(), + transform = value => value as any as K, + } = options + + if (!_queue.has(router)) + _queue.set(router, new Map()) + + const _queriesQueue = _queue.get(router)! + + let query = route.query[name] as any + + tryOnScopeDispose(() => { + query = undefined + }) + + let _trigger: () => void + + const proxy = customRef((track, trigger) => { + _trigger = trigger + + return { + get() { + track() + + return transform(query !== undefined ? query : toValue(defaultValue)) + }, + set(v) { + if (query === v) + return + + query = v + _queriesQueue.set(name, v) + + trigger() + + nextTick(() => { + if (_queriesQueue.size === 0) + return + + const newQueries = Object.fromEntries(_queriesQueue.entries()) + _queriesQueue.clear() + + const { params, query, hash } = route + + router[toValue(mode)]({ + params, + query: { ...query, ...newQueries }, + hash, + }) + }) + }, + } + }) + + watch( + () => route.query[name], + (v) => { + query = v + + _trigger() + }, + { flush: 'sync' }, + ) + + return proxy as any as Ref +}