feat: articles pagination
This commit is contained in:
1 parent
a2d224240c
commit
73f4b539fe
45 files changed
+3528
-317
No files matched your search
@@ -0,0 +1,91 @@
|
||||
import type { LocationQuery } from 'vue-router'
|
||||
|
||||
export type ArticleStatus = 'online' | 'draft'
|
||||
|
||||
export const ARTICLE_PAGE_SIZES = [5, 25, 50, 100]
|
||||
export const DEFAULT_ARTICLE_PAGE_SIZE = 25
|
||||
export const ARTICLE_SEARCH_MAX_LENGTH = 200
|
||||
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]
|
||||
|
||||
const POSITIVE_INTEGER = /^[1-9]\d*$/
|
||||
|
||||
export interface ArticleListQuery {
|
||||
page: number
|
||||
size: number
|
||||
q: string
|
||||
status: ArticleStatus | ''
|
||||
sort: string
|
||||
}
|
||||
|
||||
function first(value: LocationQuery[string] | undefined) {
|
||||
const single = Array.isArray(value) ? value[0] : value
|
||||
return single ?? ''
|
||||
}
|
||||
|
||||
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 = first(query.page)
|
||||
const size = Number(first(query.size))
|
||||
const status = first(query.status)
|
||||
return {
|
||||
page: POSITIVE_INTEGER.test(page) ? Number(page) : 1,
|
||||
size: ARTICLE_PAGE_SIZES.includes(size) ? size : DEFAULT_ARTICLE_PAGE_SIZE,
|
||||
q: first(query.q).slice(0, ARTICLE_SEARCH_MAX_LENGTH),
|
||||
status: status === 'online' || status === 'draft' ? status : '',
|
||||
sort: validSort(first(query.sort)),
|
||||
}
|
||||
}
|
||||
|
||||
export function toRouteQuery(query: ArticleListQuery) {
|
||||
const route: Record<string, string> = {}
|
||||
if (query.page !== 1) {
|
||||
route.page = String(query.page)
|
||||
}
|
||||
if (query.size !== DEFAULT_ARTICLE_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
|
||||
}
|
||||
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 &&
|
||||
effectiveSort(left.sort) === effectiveSort(right.sort)
|
||||
)
|
||||
}
|
||||
|
||||
export function sameRouteQuery(left: LocationQuery, right: Record<string, string>) {
|
||||
const leftKeys = Object.keys(left)
|
||||
return (
|
||||
leftKeys.length === Object.keys(right).length &&
|
||||
leftKeys.every((key) => first(left[key]) === right[key])
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user