From d64932dad70c0be31375b99c92fd2c994007bb58 Mon Sep 17 00:00:00 2001 From: Scott Ruoti Date: Sat, 29 Oct 2022 22:54:31 -0400 Subject: [PATCH 1/5] Fixes bug when titles are in multiple series being collapsed --- server/utils/libraryHelpers.js | 43 +++++++++++++++++----------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/server/utils/libraryHelpers.js b/server/utils/libraryHelpers.js index cb5fb0d4..19e0cd6f 100644 --- a/server/utils/libraryHelpers.js +++ b/server/utils/libraryHelpers.js @@ -283,32 +283,31 @@ module.exports = { collapseBookSeries(libraryItems, series) { var seriesObjects = this.getSeriesFromBooks(libraryItems, series, null, null, true) var seriesToUse = {} - var libraryItemIdsToHide = [] - seriesObjects.forEach((series) => { - series.firstBook = series.books.find(b => !seriesToUse[b.id]) // Find first book not already used - if (series.firstBook) { - seriesToUse[series.firstBook.id] = series - libraryItemIdsToHide = libraryItemIdsToHide.concat(series.books.filter(b => !seriesToUse[b.id]).map(b => b.id)) - } - }) + var collapsedLibraryItems = [] - return libraryItems.map((li) => { + libraryItems.forEach((li) => { if (li.mediaType != 'book') return - var libraryItemJson = li.toJSONMinified() - if (libraryItemIdsToHide.includes(li.id)) { - return null - } - if (seriesToUse[li.id]) { + + // Handle when this is the first book in a series + seriesObjects.filter(s => s.books[0].id == li.id).forEach(series => { + let libraryItemJson = li.toJSONMinified() libraryItemJson.collapsedSeries = { - id: seriesToUse[li.id].id, - name: seriesToUse[li.id].name, - nameIgnorePrefix: seriesToUse[li.id].nameIgnorePrefix, - libraryItemIds: seriesToUse[li.id].books.map(b => b.id), - numBooks: seriesToUse[li.id].books.length + id: series.id, + name: series.name, + nameIgnorePrefix: series.nameIgnorePrefix, + libraryItemIds: series.books.map(b => b.id), + numBooks: series.books.length } - } - return libraryItemJson - }).filter(li => li) + collapsedLibraryItems.push(libraryItemJson); + }); + + // Ignore books contained in series + if (li.media.metadata.series.length) return + + collapsedLibraryItems.push(li.toJSONMinified()) + }); + + return collapsedLibraryItems }, buildPersonalizedShelves(user, libraryItems, mediaType, allSeries, allAuthors, maxEntitiesPerShelf = 10) { From b322d0207bdea39007122b70e7d239638ac95986 Mon Sep 17 00:00:00 2001 From: Scott Ruoti Date: Sun, 30 Oct 2022 10:21:12 -0400 Subject: [PATCH 2/5] Fixed sorting to be more consistent for multiple series (and generally) --- server/controllers/LibraryController.js | 94 +++++++++++++++---------- server/utils/libraryHelpers.js | 36 +++++----- 2 files changed, 76 insertions(+), 54 deletions(-) diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index e06fbe92..a5bcfe73 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -160,17 +160,30 @@ class LibraryController { minified: req.query.minified === '1', collapseseries: req.query.collapseseries === '1' } + var mediaIsBook = payload.mediaType === 'book' var filterSeries = null if (payload.filterBy) { // If filtering by series, will include seriesName and seriesSequence on media metadata - filterSeries = (payload.mediaType == 'book' && payload.filterBy.startsWith('series.')) ? libraryHelpers.decode(payload.filterBy.replace('series.', '')) : null + filterSeries = (mediaIsBook && payload.filterBy.startsWith('series.')) ? libraryHelpers.decode(payload.filterBy.replace('series.', '')) : null if (filterSeries === 'No Series') filterSeries = null libraryItems = libraryHelpers.getFilteredLibraryItems(libraryItems, payload.filterBy, req.user, this.rssFeedManager.feedsArray) payload.total = libraryItems.length } + if (payload.collapseseries) { + libraryItems = libraryHelpers.collapseBookSeries(libraryItems, this.db.series, filterSeries) + payload.total = libraryItems.length + } + + + var sortArray = [] + if (filterSeries) { + // Book media when filtering series will sort by the series sequence + sortArray.push({ asc: (li) => li.media.metadata.getSeries(filterSeries).sequence }) + } + if (payload.sortBy) { var sortKey = payload.sortBy @@ -186,29 +199,37 @@ class LibraryController { sortKey += 'IgnorePrefix' } - // Start sort - var direction = payload.sortDesc ? 'desc' : 'asc' - var sortArray = [ - { - [direction]: (li) => { - // When collapsing by series and sorting by title use the series name instead of the book title - if (payload.mediaType === 'book' && payload.collapseseries && li.media.metadata.seriesName) { - if (sortByTitle) { - return this.db.serverSettings.sortingIgnorePrefix ? li.media.metadata.seriesNameIgnorePrefix : li.media.metadata.seriesName - } else { - // When not sorting by title always show the collapsed series at the end - return direction === 'desc' ? -1 : 'zzzz' - } + // If series are collapsed and not sorting by title, sort all collapsed series to the end in alphabetical order + if (payload.collapseseries && !sortByTitle) { + sortArray.push({ + asc: (li) => { + if (li.collapsedSeries) { + return this.db.serverSettings.sortingIgnorePrefix ? + li.collapsedSeries.nameIgnorePrefix : + li.collapsedSeries.name + } else { + return '' } + } + }) + } + var direction = payload.sortDesc ? 'desc' : 'asc' + sortArray.push({ + [direction]: (li) => { + if (mediaIsBook && sortByTitle && li.collapsedSeries) { + return this.db.serverSettings.sortingIgnorePrefix ? + li.collapsedSeries.nameIgnorePrefix : + li.collapsedSeries.name + } else { // Supports dot notation strings i.e. "media.metadata.title" return sortKey.split('.').reduce((a, b) => a[b], li) } } - ] + }) // Secondary sort when sorting by book author use series sort title - if (payload.mediaType === 'book' && payload.sortBy.includes('author')) { + if (mediaIsBook && payload.sortBy.includes('author')) { sortArray.push({ asc: (li) => { if (li.media.metadata.series && li.media.metadata.series.length) { @@ -218,30 +239,31 @@ class LibraryController { } }) } + } + + // Sort the items + if (sortArray.length) { libraryItems = naturalSort(libraryItems).by(sortArray) } - if (payload.collapseseries) { - libraryItems = libraryHelpers.collapseBookSeries(libraryItems, this.db.series) - payload.total = libraryItems.length - } else if (filterSeries) { - // Book media when filtering series will include series object on media metadata - libraryItems = libraryItems.map(li => { - var series = li.media.metadata.getSeries(filterSeries) - var liJson = payload.minified ? li.toJSONMinified() : li.toJSON() - liJson.media.metadata.series = series - return liJson - }) - libraryItems = naturalSort(libraryItems).asc(li => li.media.metadata.series.sequence) - } else { - libraryItems = libraryItems.map(li => payload.minified ? li.toJSONMinified() : li.toJSON()) - } + payload.results = libraryItems.map(li => { + let json = payload.minified ? li.toJSONMinified() : li.toJSON() + + if (li.collapsedSeries) { + json.collapsedSeries = { + id: li.collapsedSeries.id, + name: li.collapsedSeries.name, + nameIgnorePrefix: li.collapsedSeries.nameIgnorePrefix, + libraryItemIds: li.collapsedSeries.books.map(b => b.id), + numBooks: li.collapsedSeries.books.length + } + } else if (filterSeries) { + json.media.metadata.series = li.media.metadata.getSeries(filterSeries) + } + + return json + }) - if (payload.limit) { - var startIndex = payload.page * payload.limit - libraryItems = libraryItems.slice(startIndex, startIndex + payload.limit) - } - payload.results = libraryItems res.json(payload) } diff --git a/server/utils/libraryHelpers.js b/server/utils/libraryHelpers.js index 19e0cd6f..f3d28acf 100644 --- a/server/utils/libraryHelpers.js +++ b/server/utils/libraryHelpers.js @@ -280,34 +280,34 @@ module.exports = { return totalSize }, - collapseBookSeries(libraryItems, series) { - var seriesObjects = this.getSeriesFromBooks(libraryItems, series, null, null, true) - var seriesToUse = {} - var collapsedLibraryItems = [] + + collapseBookSeries(libraryItems, series, filterSeries) { + // Get series from the library items. If this list is being collapsed after filtering for a series, + // don't collapse that series, only books that are in other series. + var seriesObjects = this + .getSeriesFromBooks(libraryItems, series, null, null, true) + .filter(s => s.id != filterSeries) + + var filteredLibraryItems = [] libraryItems.forEach((li) => { if (li.mediaType != 'book') return // Handle when this is the first book in a series seriesObjects.filter(s => s.books[0].id == li.id).forEach(series => { - let libraryItemJson = li.toJSONMinified() - libraryItemJson.collapsedSeries = { - id: series.id, - name: series.name, - nameIgnorePrefix: series.nameIgnorePrefix, - libraryItemIds: series.books.map(b => b.id), - numBooks: series.books.length - } - collapsedLibraryItems.push(libraryItemJson); + // Clone the library item as we need to attach data to it, but don't + // want to change the global copy of the library item + filteredLibraryItems.push(Object.assign( + Object.create(Object.getPrototypeOf(li)), + li, { collapsedSeries: series })) }); - // Ignore books contained in series - if (li.media.metadata.series.length) return - - collapsedLibraryItems.push(li.toJSONMinified()) + // Only included books not contained in series + if (!seriesObjects.some(s => s.books.some(b => b.id == li.id))) + filteredLibraryItems.push(li) }); - return collapsedLibraryItems + return filteredLibraryItems }, buildPersonalizedShelves(user, libraryItems, mediaType, allSeries, allAuthors, maxEntitiesPerShelf = 10) { From c1035d97e8d988269b896bc78a112e7ca1232cff Mon Sep 17 00:00:00 2001 From: Scott Ruoti Date: Sun, 30 Oct 2022 11:38:00 -0400 Subject: [PATCH 3/5] Show book sequences for collapsed series when filtering by series --- client/components/app/LazyBookshelf.vue | 28 ++++-- client/components/cards/LazyBookCard.vue | 117 +++++++++++++++++------ server/controllers/LibraryController.js | 58 ++++++++--- server/utils/libraryHelpers.js | 7 +- 4 files changed, 159 insertions(+), 51 deletions(-) diff --git a/client/components/app/LazyBookshelf.vue b/client/components/app/LazyBookshelf.vue index 08ed1496..c0798d19 100644 --- a/client/components/app/LazyBookshelf.vue +++ b/client/components/app/LazyBookshelf.vue @@ -1,12 +1,15 @@