52 lines
2.2 KiB
TypeScript
52 lines
2.2 KiB
TypeScript
export type WishlistSort = 'title' | 'author'
|
|
|
|
type ListedBook = Pick<WishlistBook, 'title' | 'authors'>
|
|
|
|
/** "Last, First" -> "First Last" */
|
|
export function wishlistAuthorName(author: string): string {
|
|
const [last, first] = author.split(/,\s*/, 2)
|
|
return first ? `${first} ${last}` : author
|
|
}
|
|
|
|
function searchableText(book: ListedBook): string {
|
|
const authors = book.authors.flatMap(author => [author, wishlistAuthorName(author)])
|
|
return [book.title, ...authors].join(' ').toLowerCase()
|
|
}
|
|
|
|
const collator = new Intl.Collator('de', { sensitivity: 'base', numeric: true })
|
|
|
|
function compare(sort: WishlistSort) {
|
|
const byTitle = (a: ListedBook, b: ListedBook) => collator.compare(a.title, b.title)
|
|
const byAuthor = (a: ListedBook, b: ListedBook) => collator.compare(a.authors[0] ?? '', b.authors[0] ?? '')
|
|
return sort === 'title'
|
|
? (a: ListedBook, b: ListedBook) => byTitle(a, b) || byAuthor(a, b)
|
|
: (a: ListedBook, b: ListedBook) => byAuthor(a, b) || byTitle(a, b)
|
|
}
|
|
|
|
function searchAndSort<Book extends ListedBook>(books: Book[], search: string, sort: WishlistSort): Book[] {
|
|
const terms = search.trim().toLowerCase().split(/\s+/).filter(Boolean)
|
|
return books
|
|
.filter((book) => {
|
|
const text = searchableText(book)
|
|
return terms.every(term => text.includes(term))
|
|
})
|
|
.toSorted(compare(sort))
|
|
}
|
|
|
|
export function useWishlist(search: Ref<string>, sort: Ref<WishlistSort>) {
|
|
const { data, error, status } = useFetch<WishlistBook[]>('/api/wishlist/books', { key: 'wishlist-books', default: () => [] })
|
|
const { data: reading } = useFetch<WishlistShelfBook[]>('/api/wishlist/reading', { key: 'wishlist-reading', default: () => [] })
|
|
const { data: read } = useFetch<WishlistShelfBook[]>('/api/wishlist/read', { key: 'wishlist-read', default: () => [] })
|
|
|
|
return {
|
|
books: computed(() => searchAndSort(data.value, search.value, sort.value)),
|
|
total: computed(() => data.value.length),
|
|
readingBooks: computed(() => searchAndSort(reading.value, search.value, sort.value)),
|
|
readingTotal: computed(() => reading.value.length),
|
|
readBooks: computed(() => searchAndSort(read.value, search.value, sort.value)),
|
|
readTotal: computed(() => read.value.length),
|
|
error,
|
|
status
|
|
}
|
|
}
|