96 lines
2.6 KiB
TypeScript
96 lines
2.6 KiB
TypeScript
import type { LocationQuery } from 'vue-router'
|
|
import { SITE_KEY_MAX } from '@/lib/directory'
|
|
import {
|
|
DEFAULT_PAGE_SIZE,
|
|
PAGE_SIZES,
|
|
SEARCH_MAX_LENGTH,
|
|
firstQueryValue,
|
|
parsePositiveInteger,
|
|
} from '@/lib/pageQuery'
|
|
|
|
export type ArticleStatus = 'online' | 'draft'
|
|
|
|
export const DEFAULT_ARTICLE_SORT = 'updated_at:desc'
|
|
export const ARTICLE_SORT_FIELDS = ['title', 'status', 'created_at', 'updated_at'] as const
|
|
export type ArticleSortField = (typeof ARTICLE_SORT_FIELDS)[number]
|
|
|
|
export const SEO_FILTER = 'suggestions'
|
|
|
|
export interface ArticleListQuery {
|
|
page: number
|
|
size: number
|
|
q: string
|
|
status: ArticleStatus | ''
|
|
sort: string
|
|
site: string
|
|
seo: '' | typeof SEO_FILTER
|
|
}
|
|
|
|
const SORT_PATTERN = new RegExp(`^(${ARTICLE_SORT_FIELDS.join('|')}):(asc|desc)$`)
|
|
|
|
function validSort(value: string) {
|
|
return SORT_PATTERN.test(value) && value !== DEFAULT_ARTICLE_SORT ? value : ''
|
|
}
|
|
|
|
export function effectiveSort(sort: string) {
|
|
return sort || DEFAULT_ARTICLE_SORT
|
|
}
|
|
|
|
export function nextSort(current: string, field: ArticleSortField) {
|
|
const [activeField, direction] = effectiveSort(current).split(':')
|
|
return `${field}:${activeField === field && direction === 'asc' ? 'desc' : 'asc'}`
|
|
}
|
|
|
|
export function parseArticleQuery(query: LocationQuery): ArticleListQuery {
|
|
const page = firstQueryValue(query.page)
|
|
const size = Number(firstQueryValue(query.size))
|
|
const status = firstQueryValue(query.status)
|
|
return {
|
|
page: parsePositiveInteger(page) ?? 1,
|
|
size: PAGE_SIZES.includes(size) ? size : DEFAULT_PAGE_SIZE,
|
|
q: firstQueryValue(query.q).slice(0, SEARCH_MAX_LENGTH),
|
|
status: status === 'online' || status === 'draft' ? status : '',
|
|
sort: validSort(firstQueryValue(query.sort)),
|
|
site: firstQueryValue(query.site).slice(0, SITE_KEY_MAX),
|
|
seo: firstQueryValue(query.seo) === SEO_FILTER ? SEO_FILTER : '',
|
|
}
|
|
}
|
|
|
|
export function toRouteQuery(query: ArticleListQuery) {
|
|
const route: Record<string, string> = {}
|
|
if (query.page !== 1) {
|
|
route.page = String(query.page)
|
|
}
|
|
if (query.size !== DEFAULT_PAGE_SIZE) {
|
|
route.size = String(query.size)
|
|
}
|
|
if (query.q) {
|
|
route.q = query.q
|
|
}
|
|
if (query.status) {
|
|
route.status = query.status
|
|
}
|
|
if (effectiveSort(query.sort) !== DEFAULT_ARTICLE_SORT) {
|
|
route.sort = query.sort
|
|
}
|
|
if (query.site) {
|
|
route.site = query.site
|
|
}
|
|
if (query.seo) {
|
|
route.seo = query.seo
|
|
}
|
|
return route
|
|
}
|
|
|
|
export function sameArticleQuery(left: ArticleListQuery, right: ArticleListQuery) {
|
|
return (
|
|
left.page === right.page &&
|
|
left.size === right.size &&
|
|
left.q === right.q &&
|
|
left.status === right.status &&
|
|
left.site === right.site &&
|
|
left.seo === right.seo &&
|
|
effectiveSort(left.sort) === effectiveSort(right.sort)
|
|
)
|
|
}
|