1import { ap as parseBookTags, av as BOOK_STATUS_LABELS, cC as bookStatus, aG as readingProgress } from "./index-CPwKESzR.js"; 2const SORT_LABELS = { 3 title: "Title", 4 author: "Author", 5 recent: "Recent", 6 progress: "Progress", 7 rating: "Rating" 8}; 9const FILTER_LABELS = { all: "All", ...BOOK_STATUS_LABELS }; 10const FILTER_OPTIONS = ["all", "want", "reading", "finished", "dropped"]; 11const SORT_OPTIONS = ["recent", "title", "author", "progress", "rating"]; 12const FINISHED_TAIL_WORDS = 5; 13function isBookFinished(book) { 14 if (book.wordCount <= 0) return false; 15 return book.wordPosition >= book.wordCount - FINISHED_TAIL_WORDS; 16} 17function seriesProgress(activity) { 18 if (!activity || activity.total === 0) return 0; 19 return Math.min(100, Math.round(activity.finished / activity.total * 100)); 20} 21function seriesRecency(s, activity) { 22 return activity?.latestRead ?? s.createdAt; 23} 24function seriesStatus(activity) { 25 if (!activity || activity.total === 0) return "want"; 26 if (activity.finished >= activity.total) return "finished"; 27 return activity.started > 0 ? "reading" : "want"; 28} 29function bookSortKey(b) { 30 return { 31 title: b.title, 32 author: b.author ?? "", 33 recency: b.lastRead ?? b.addedAt, 34 progress: readingProgress(b), 35 status: bookStatus(b), 36 rating: b.rating, 37 tags: parseBookTags(b.tags) 38 }; 39} 40function seriesSortKey(s, activity) { 41 return { 42 title: s.title, 43 author: s.author ?? "", 44 recency: seriesRecency(s, activity), 45 progress: seriesProgress(activity), 46 status: seriesStatus(activity), 47 rating: null, 48 // Series carry no tags of their own; a tag filter therefore hides them, 49 // which is right: they cannot match it. 50 tags: [] 51 }; 52} 53function matchesFilter(item, filterBy) { 54 return filterBy === "all" || item.sortKey.status === filterBy; 55} 56function matchesSearch(item, needle) { 57 if (!needle) return true; 58 const { title, author } = item.sortKey; 59 return `${title} ${author}`.toLowerCase().includes(needle); 60} 61function tagsInUse(books) { 62 const seen = /* @__PURE__ */ new Set(); 63 for (const book of books) { 64 for (const tag of parseBookTags(book.tags)) seen.add(tag); 65 } 66 return [...seen].sort((a, b) => a.localeCompare(b)); 67} 68function compareItems(a, b, sortBy) { 69 switch (sortBy) { 70 case "title": 71 return a.sortKey.title.localeCompare(b.sortKey.title); 72 case "author": 73 return a.sortKey.author.localeCompare(b.sortKey.author); 74 case "recent": 75 return b.sortKey.recency - a.sortKey.recency; 76 case "progress": 77 return b.sortKey.progress - a.sortKey.progress; 78 // Unrated sorts below one star rather than above five. 79 case "rating": 80 return (b.sortKey.rating ?? -1) - (a.sortKey.rating ?? -1); 81 } 82} 83function filterAndSortLibrary(books, series, activity, view) { 84 const { filterBy, sortBy, search = "", tag = null } = view; 85 const needle = search.trim().toLowerCase(); 86 const items = [ 87 ...books.map((book) => ({ kind: "book", book, sortKey: bookSortKey(book) })), 88 ...series.map((s) => { 89 const a = activity.get(s.id); 90 return { kind: "series", series: s, activity: a, sortKey: seriesSortKey(s, a) }; 91 }) 92 ]; 93 return items.filter((it) => matchesFilter(it, filterBy)).filter((it) => matchesSearch(it, needle)).filter((it) => tag === null || it.sortKey.tags.includes(tag)).sort((a, b) => compareItems(a, b, sortBy)); 94} 95export { 96 FILTER_LABELS as F, 97 SORT_LABELS as S, 98 FILTER_OPTIONS as a, 99 SORT_OPTIONS as b, 100 filterAndSortLibrary as f, 101 isBookFinished as i, 102 tagsInUse as t 103};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.