diff --git a/client/components/modals/libraries/LibrarySettings.vue b/client/components/modals/libraries/LibrarySettings.vue
index bf2423bf..5d91daa6 100644
--- a/client/components/modals/libraries/LibrarySettings.vue
+++ b/client/components/modals/libraries/LibrarySettings.vue
@@ -38,6 +38,17 @@
{{ $strings.LabelSettingsSkipMatchingBooksWithISBN }}
+
+
+
+
+
+ {{ $strings.LabelSettingsHideSingleBookSeries }}
+ info_outlined
+
+
+
+
@@ -57,7 +68,8 @@ export default {
disableWatcher: false,
skipMatchingMediaWithAsin: false,
skipMatchingMediaWithIsbn: false,
- audiobooksOnly: false
+ audiobooksOnly: false,
+ hideSingleBookSeries: false
}
},
computed: {
@@ -86,7 +98,8 @@ export default {
disableWatcher: !!this.disableWatcher,
skipMatchingMediaWithAsin: !!this.skipMatchingMediaWithAsin,
skipMatchingMediaWithIsbn: !!this.skipMatchingMediaWithIsbn,
- audiobooksOnly: !!this.audiobooksOnly
+ audiobooksOnly: !!this.audiobooksOnly,
+ hideSingleBookSeries: !!this.hideSingleBookSeries
}
}
},
@@ -99,6 +112,7 @@ export default {
this.skipMatchingMediaWithAsin = !!this.librarySettings.skipMatchingMediaWithAsin
this.skipMatchingMediaWithIsbn = !!this.librarySettings.skipMatchingMediaWithIsbn
this.audiobooksOnly = !!this.librarySettings.audiobooksOnly
+ this.hideSingleBookSeries = !!this.librarySettings.hideSingleBookSeries
}
},
mounted() {
diff --git a/client/strings/en-us.json b/client/strings/en-us.json
index be8ac1ff..36fdec68 100644
--- a/client/strings/en-us.json
+++ b/client/strings/en-us.json
@@ -395,6 +395,8 @@
"LabelSettingsExperimentalFeaturesHelp": "Features in development that could use your feedback and help testing. Click to open github discussion.",
"LabelSettingsFindCovers": "Find covers",
"LabelSettingsFindCoversHelp": "If your audiobook does not have an embedded cover or a cover image inside the folder, the scanner will attempt to find a cover.
Note: This will extend scan time",
+ "LabelSettingsHideSingleBookSeries": "Hide single book series",
+ "LabelSettingsHideSingleBookSeriesHelp": "Series that have a single book will be hidden from the series page and home page shelves.",
"LabelSettingsHomePageBookshelfView": "Home page use bookshelf view",
"LabelSettingsLibraryBookshelfView": "Library use bookshelf view",
"LabelSettingsOverdriveMediaMarkers": "Use Overdrive Media Markers for chapters",
diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js
index e52c6652..55f2c79f 100644
--- a/server/controllers/LibraryController.js
+++ b/server/controllers/LibraryController.js
@@ -209,7 +209,7 @@ class LibraryController {
// If also filtering by series, will not collapse the filtered series as this would lead
// to series having a collapsed series that is just that series.
if (payload.collapseseries) {
- let collapsedItems = libraryHelpers.collapseBookSeries(libraryItems, this.db.series, filterSeries)
+ let collapsedItems = libraryHelpers.collapseBookSeries(libraryItems, this.db.series, filterSeries, req.library.settings.hideSingleBookSeries)
if (!(collapsedItems.length == 1 && collapsedItems[0].collapsedSeries)) {
libraryItems = collapsedItems
@@ -405,7 +405,7 @@ class LibraryController {
include: include.join(',')
}
- let series = libraryHelpers.getSeriesFromBooks(libraryItems, this.db.series, null, payload.filterBy, req.user, payload.minified)
+ let series = libraryHelpers.getSeriesFromBooks(libraryItems, this.db.series, null, payload.filterBy, req.user, payload.minified, req.library.settings.hideSingleBookSeries)
const direction = payload.sortDesc ? 'desc' : 'asc'
series = naturalSort(series).by([
@@ -544,12 +544,10 @@ class LibraryController {
// api/libraries/:id/personalized
// New and improved personalized call only loops through library items once
async getLibraryUserPersonalizedOptimal(req, res) {
- const mediaType = req.library.mediaType
- const libraryItems = req.libraryItems
const limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) || 10 : 10
const include = (req.query.include || '').split(',').map(v => v.trim().toLowerCase()).filter(v => !!v)
- const categories = libraryHelpers.buildPersonalizedShelves(this, req.user, libraryItems, mediaType, limitPerShelf, include)
+ const categories = libraryHelpers.buildPersonalizedShelves(this, req.user, req.libraryItems, req.library, limitPerShelf, include)
res.json(categories)
}
diff --git a/server/objects/settings/LibrarySettings.js b/server/objects/settings/LibrarySettings.js
index 4b3df50d..79c729ef 100644
--- a/server/objects/settings/LibrarySettings.js
+++ b/server/objects/settings/LibrarySettings.js
@@ -8,6 +8,7 @@ class LibrarySettings {
this.skipMatchingMediaWithIsbn = false
this.autoScanCronExpression = null
this.audiobooksOnly = false
+ this.hideSingleBookSeries = false // Do not show series that only have 1 book
if (settings) {
this.construct(settings)
@@ -21,6 +22,7 @@ class LibrarySettings {
this.skipMatchingMediaWithIsbn = !!settings.skipMatchingMediaWithIsbn
this.autoScanCronExpression = settings.autoScanCronExpression || null
this.audiobooksOnly = !!settings.audiobooksOnly
+ this.hideSingleBookSeries = !!settings.hideSingleBookSeries
}
toJSON() {
@@ -30,7 +32,8 @@ class LibrarySettings {
skipMatchingMediaWithAsin: this.skipMatchingMediaWithAsin,
skipMatchingMediaWithIsbn: this.skipMatchingMediaWithIsbn,
autoScanCronExpression: this.autoScanCronExpression,
- audiobooksOnly: this.audiobooksOnly
+ audiobooksOnly: this.audiobooksOnly,
+ hideSingleBookSeries: this.hideSingleBookSeries
}
}
diff --git a/server/utils/libraryHelpers.js b/server/utils/libraryHelpers.js
index 2b9a56c0..899e43bb 100644
--- a/server/utils/libraryHelpers.js
+++ b/server/utils/libraryHelpers.js
@@ -168,7 +168,7 @@ module.exports = {
return data
},
- getSeriesFromBooks(books, allSeries, filterSeries, filterBy, user, minified = false) {
+ getSeriesFromBooks(books, allSeries, filterSeries, filterBy, user, minified, hideSingleBookSeries) {
const _series = {}
const seriesToFilterOut = {}
books.forEach((libraryItem) => {
@@ -218,6 +218,11 @@ module.exports = {
let seriesItems = Object.values(_series)
+ // Library setting to hide series with only 1 book
+ if (hideSingleBookSeries) {
+ seriesItems = seriesItems.filter(se => se.books.length > 1)
+ }
+
// check progress filter
if (filterBy && filterBy.startsWith('progress.') && user) {
seriesItems = seriesItems.filter(se => this.checkSeriesProgressFilter(se, filterBy, user))
@@ -312,11 +317,11 @@ module.exports = {
},
- collapseBookSeries(libraryItems, series, filterSeries) {
+ collapseBookSeries(libraryItems, series, filterSeries, hideSingleBookSeries) {
// 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.
const seriesObjects = this
- .getSeriesFromBooks(libraryItems, series, filterSeries, null, null, true)
+ .getSeriesFromBooks(libraryItems, series, filterSeries, null, null, true, hideSingleBookSeries)
.filter(s => s.id != filterSeries)
const filteredLibraryItems = []
@@ -341,9 +346,11 @@ module.exports = {
return filteredLibraryItems
},
- buildPersonalizedShelves(ctx, user, libraryItems, mediaType, maxEntitiesPerShelf, include) {
+ buildPersonalizedShelves(ctx, user, libraryItems, library, maxEntitiesPerShelf, include) {
+ const mediaType = library.mediaType
const isPodcastLibrary = mediaType === 'podcast'
const includeRssFeed = include.includes('rssfeed')
+ const hideSingleBookSeries = library.settings.hideSingleBookSeries
const shelves = [
{
@@ -580,21 +587,11 @@ module.exports = {
}
seriesMap[librarySeries.id] = series
- if (series.addedAt > categoryMap['recent-series'].smallest) {
- const indexToPut = categoryMap['recent-series'].items.findIndex(i => series.addedAt > i.addedAt)
- if (indexToPut >= 0) {
- categoryMap['recent-series'].items.splice(indexToPut, 0, series)
- } else {
- categoryMap['recent-series'].items.push(series)
- }
-
- // Max series is 5
- if (categoryMap['recent-series'].items.length > 5) {
- categoryMap['recent-series'].items.pop()
- categoryMap['recent-series'].smallest = categoryMap['recent-series'].items[categoryMap['recent-series'].items.length - 1].addedAt
- }
-
- categoryMap['recent-series'].biggest = categoryMap['recent-series'].items[0].addedAt
+ const indexToPut = categoryMap['recent-series'].items.findIndex(i => series.addedAt > i.addedAt)
+ if (indexToPut >= 0) {
+ categoryMap['recent-series'].items.splice(indexToPut, 0, series)
+ } else {
+ categoryMap['recent-series'].items.push(series)
}
}
} else {
@@ -819,6 +816,12 @@ module.exports = {
// Sort series books by sequence
if (categoryMap['recent-series'].items.length) {
+ if (hideSingleBookSeries) {
+ categoryMap['recent-series'].items = categoryMap['recent-series'].items.filter(seriesItem => seriesItem.books.length > 1)
+ }
+ // Limit series shown to 5
+ categoryMap['recent-series'].items = categoryMap['recent-series'].items.slice(0, 5)
+
for (const seriesItem of categoryMap['recent-series'].items) {
seriesItem.books = naturalSort(seriesItem.books).asc(li => li.seriesSequence)
}