feat: add wishlist
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
export type WishlistSort = 'title' | 'author'
|
||||
|
||||
/** "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: WishlistBook): string {
|
||||
const authors = book.authors.flatMap(author => [author, wishlistAuthorName(author)])
|
||||
return [book.title, ...authors, book.isbn, book.isbn.replace(/-/g, ''), book.thalia ?? ''].join(' ').toLowerCase()
|
||||
}
|
||||
|
||||
const collator = new Intl.Collator('de', { sensitivity: 'base', numeric: true })
|
||||
|
||||
function compare(sort: WishlistSort) {
|
||||
const byTitle = (a: WishlistBook, b: WishlistBook) => collator.compare(a.title, b.title)
|
||||
const byAuthor = (a: WishlistBook, b: WishlistBook) => collator.compare(a.authors[0] ?? '', b.authors[0] ?? '')
|
||||
return sort === 'title'
|
||||
? (a: WishlistBook, b: WishlistBook) => byTitle(a, b) || byAuthor(a, b)
|
||||
: (a: WishlistBook, b: WishlistBook) => byAuthor(a, b) || byTitle(a, b)
|
||||
}
|
||||
|
||||
export function useWishlist(search: Ref<string>, sort: Ref<WishlistSort>) {
|
||||
const { data, error, status } = useFetch<WishlistBook[]>('/api/wishlist/books', { key: 'wishlist-books', default: () => [] })
|
||||
|
||||
const books = computed(() => {
|
||||
const terms = search.value.trim().toLowerCase().split(/\s+/).filter(Boolean)
|
||||
return data.value
|
||||
.filter((book) => {
|
||||
const text = searchableText(book)
|
||||
return terms.every(term => text.includes(term))
|
||||
})
|
||||
.toSorted(compare(sort.value))
|
||||
})
|
||||
|
||||
return { books, total: computed(() => data.value.length), error, status }
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
const lookups = new Map<string, Promise<string | null>>()
|
||||
|
||||
async function lookUpCover(isbn: string): Promise<string | null> {
|
||||
try {
|
||||
const edition = await $fetch<{ covers?: number[] }>(`https://openlibrary.org/isbn/${isbn}.json`)
|
||||
const id = edition.covers?.find(id => id > 0)
|
||||
return id ? `https://covers.openlibrary.org/b/id/${id}-M.jpg` : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The book's cover: the one saved in the table, or else looked up on Open Library by ISBN
|
||||
* and saved to the table so the next visit doesn't have to look it up again.
|
||||
*/
|
||||
export function useWishlistCover(book: MaybeRefOrGetter<WishlistBook>) {
|
||||
const url = ref<string | null>(toValue(book).cover)
|
||||
const loading = ref(!url.value)
|
||||
|
||||
onMounted(async () => {
|
||||
if (url.value) return
|
||||
const { id, isbn } = toValue(book)
|
||||
const digits = isbn.replace(/[^\dX]/gi, '')
|
||||
if (!digits) {
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
if (!lookups.has(digits)) lookups.set(digits, lookUpCover(digits))
|
||||
url.value = await lookups.get(digits)!
|
||||
loading.value = false
|
||||
|
||||
if (url.value) {
|
||||
$fetch(`/api/wishlist/books/${id}/cover`, { method: 'POST', body: { cover: url.value } }).catch(() => {})
|
||||
}
|
||||
})
|
||||
|
||||
return { url, loading }
|
||||
}
|
||||
Reference in New Issue
Block a user