mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-03 00:06:46 +01:00
Add:Library setting to hide single book series #1433
This commit is contained in:
parent
bb9013541b
commit
bdbc5e3161
@ -38,6 +38,17 @@
|
|||||||
<p class="pl-4 text-base">{{ $strings.LabelSettingsSkipMatchingBooksWithISBN }}</p>
|
<p class="pl-4 text-base">{{ $strings.LabelSettingsSkipMatchingBooksWithISBN }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="isBookLibrary" class="py-3">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<ui-toggle-switch v-model="hideSingleBookSeries" @input="formUpdated" />
|
||||||
|
<ui-tooltip :text="$strings.LabelSettingsHideSingleBookSeriesHelp">
|
||||||
|
<p class="pl-4 text-base">
|
||||||
|
{{ $strings.LabelSettingsHideSingleBookSeries }}
|
||||||
|
<span class="material-icons icon-text text-sm">info_outlined</span>
|
||||||
|
</p>
|
||||||
|
</ui-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -57,7 +68,8 @@ export default {
|
|||||||
disableWatcher: false,
|
disableWatcher: false,
|
||||||
skipMatchingMediaWithAsin: false,
|
skipMatchingMediaWithAsin: false,
|
||||||
skipMatchingMediaWithIsbn: false,
|
skipMatchingMediaWithIsbn: false,
|
||||||
audiobooksOnly: false
|
audiobooksOnly: false,
|
||||||
|
hideSingleBookSeries: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -86,7 +98,8 @@ export default {
|
|||||||
disableWatcher: !!this.disableWatcher,
|
disableWatcher: !!this.disableWatcher,
|
||||||
skipMatchingMediaWithAsin: !!this.skipMatchingMediaWithAsin,
|
skipMatchingMediaWithAsin: !!this.skipMatchingMediaWithAsin,
|
||||||
skipMatchingMediaWithIsbn: !!this.skipMatchingMediaWithIsbn,
|
skipMatchingMediaWithIsbn: !!this.skipMatchingMediaWithIsbn,
|
||||||
audiobooksOnly: !!this.audiobooksOnly
|
audiobooksOnly: !!this.audiobooksOnly,
|
||||||
|
hideSingleBookSeries: !!this.hideSingleBookSeries
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -99,6 +112,7 @@ export default {
|
|||||||
this.skipMatchingMediaWithAsin = !!this.librarySettings.skipMatchingMediaWithAsin
|
this.skipMatchingMediaWithAsin = !!this.librarySettings.skipMatchingMediaWithAsin
|
||||||
this.skipMatchingMediaWithIsbn = !!this.librarySettings.skipMatchingMediaWithIsbn
|
this.skipMatchingMediaWithIsbn = !!this.librarySettings.skipMatchingMediaWithIsbn
|
||||||
this.audiobooksOnly = !!this.librarySettings.audiobooksOnly
|
this.audiobooksOnly = !!this.librarySettings.audiobooksOnly
|
||||||
|
this.hideSingleBookSeries = !!this.librarySettings.hideSingleBookSeries
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -395,6 +395,8 @@
|
|||||||
"LabelSettingsExperimentalFeaturesHelp": "Features in development that could use your feedback and help testing. Click to open github discussion.",
|
"LabelSettingsExperimentalFeaturesHelp": "Features in development that could use your feedback and help testing. Click to open github discussion.",
|
||||||
"LabelSettingsFindCovers": "Find covers",
|
"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.<br>Note: This will extend scan time",
|
"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.<br>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",
|
"LabelSettingsHomePageBookshelfView": "Home page use bookshelf view",
|
||||||
"LabelSettingsLibraryBookshelfView": "Library use bookshelf view",
|
"LabelSettingsLibraryBookshelfView": "Library use bookshelf view",
|
||||||
"LabelSettingsOverdriveMediaMarkers": "Use Overdrive Media Markers for chapters",
|
"LabelSettingsOverdriveMediaMarkers": "Use Overdrive Media Markers for chapters",
|
||||||
|
@ -209,7 +209,7 @@ class LibraryController {
|
|||||||
// If also filtering by series, will not collapse the filtered series as this would lead
|
// 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.
|
// to series having a collapsed series that is just that series.
|
||||||
if (payload.collapseseries) {
|
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)) {
|
if (!(collapsedItems.length == 1 && collapsedItems[0].collapsedSeries)) {
|
||||||
libraryItems = collapsedItems
|
libraryItems = collapsedItems
|
||||||
@ -405,7 +405,7 @@ class LibraryController {
|
|||||||
include: include.join(',')
|
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'
|
const direction = payload.sortDesc ? 'desc' : 'asc'
|
||||||
series = naturalSort(series).by([
|
series = naturalSort(series).by([
|
||||||
@ -544,12 +544,10 @@ class LibraryController {
|
|||||||
// api/libraries/:id/personalized
|
// api/libraries/:id/personalized
|
||||||
// New and improved personalized call only loops through library items once
|
// New and improved personalized call only loops through library items once
|
||||||
async getLibraryUserPersonalizedOptimal(req, res) {
|
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 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 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)
|
res.json(categories)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ class LibrarySettings {
|
|||||||
this.skipMatchingMediaWithIsbn = false
|
this.skipMatchingMediaWithIsbn = false
|
||||||
this.autoScanCronExpression = null
|
this.autoScanCronExpression = null
|
||||||
this.audiobooksOnly = false
|
this.audiobooksOnly = false
|
||||||
|
this.hideSingleBookSeries = false // Do not show series that only have 1 book
|
||||||
|
|
||||||
if (settings) {
|
if (settings) {
|
||||||
this.construct(settings)
|
this.construct(settings)
|
||||||
@ -21,6 +22,7 @@ class LibrarySettings {
|
|||||||
this.skipMatchingMediaWithIsbn = !!settings.skipMatchingMediaWithIsbn
|
this.skipMatchingMediaWithIsbn = !!settings.skipMatchingMediaWithIsbn
|
||||||
this.autoScanCronExpression = settings.autoScanCronExpression || null
|
this.autoScanCronExpression = settings.autoScanCronExpression || null
|
||||||
this.audiobooksOnly = !!settings.audiobooksOnly
|
this.audiobooksOnly = !!settings.audiobooksOnly
|
||||||
|
this.hideSingleBookSeries = !!settings.hideSingleBookSeries
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
@ -30,7 +32,8 @@ class LibrarySettings {
|
|||||||
skipMatchingMediaWithAsin: this.skipMatchingMediaWithAsin,
|
skipMatchingMediaWithAsin: this.skipMatchingMediaWithAsin,
|
||||||
skipMatchingMediaWithIsbn: this.skipMatchingMediaWithIsbn,
|
skipMatchingMediaWithIsbn: this.skipMatchingMediaWithIsbn,
|
||||||
autoScanCronExpression: this.autoScanCronExpression,
|
autoScanCronExpression: this.autoScanCronExpression,
|
||||||
audiobooksOnly: this.audiobooksOnly
|
audiobooksOnly: this.audiobooksOnly,
|
||||||
|
hideSingleBookSeries: this.hideSingleBookSeries
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +168,7 @@ module.exports = {
|
|||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
|
|
||||||
getSeriesFromBooks(books, allSeries, filterSeries, filterBy, user, minified = false) {
|
getSeriesFromBooks(books, allSeries, filterSeries, filterBy, user, minified, hideSingleBookSeries) {
|
||||||
const _series = {}
|
const _series = {}
|
||||||
const seriesToFilterOut = {}
|
const seriesToFilterOut = {}
|
||||||
books.forEach((libraryItem) => {
|
books.forEach((libraryItem) => {
|
||||||
@ -218,6 +218,11 @@ module.exports = {
|
|||||||
|
|
||||||
let seriesItems = Object.values(_series)
|
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
|
// check progress filter
|
||||||
if (filterBy && filterBy.startsWith('progress.') && user) {
|
if (filterBy && filterBy.startsWith('progress.') && user) {
|
||||||
seriesItems = seriesItems.filter(se => this.checkSeriesProgressFilter(se, filterBy, 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,
|
// 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.
|
// don't collapse that series, only books that are in other series.
|
||||||
const seriesObjects = this
|
const seriesObjects = this
|
||||||
.getSeriesFromBooks(libraryItems, series, filterSeries, null, null, true)
|
.getSeriesFromBooks(libraryItems, series, filterSeries, null, null, true, hideSingleBookSeries)
|
||||||
.filter(s => s.id != filterSeries)
|
.filter(s => s.id != filterSeries)
|
||||||
|
|
||||||
const filteredLibraryItems = []
|
const filteredLibraryItems = []
|
||||||
@ -341,9 +346,11 @@ module.exports = {
|
|||||||
return filteredLibraryItems
|
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 isPodcastLibrary = mediaType === 'podcast'
|
||||||
const includeRssFeed = include.includes('rssfeed')
|
const includeRssFeed = include.includes('rssfeed')
|
||||||
|
const hideSingleBookSeries = library.settings.hideSingleBookSeries
|
||||||
|
|
||||||
const shelves = [
|
const shelves = [
|
||||||
{
|
{
|
||||||
@ -580,21 +587,11 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
seriesMap[librarySeries.id] = series
|
seriesMap[librarySeries.id] = series
|
||||||
|
|
||||||
if (series.addedAt > categoryMap['recent-series'].smallest) {
|
const indexToPut = categoryMap['recent-series'].items.findIndex(i => series.addedAt > i.addedAt)
|
||||||
const indexToPut = categoryMap['recent-series'].items.findIndex(i => series.addedAt > i.addedAt)
|
if (indexToPut >= 0) {
|
||||||
if (indexToPut >= 0) {
|
categoryMap['recent-series'].items.splice(indexToPut, 0, series)
|
||||||
categoryMap['recent-series'].items.splice(indexToPut, 0, series)
|
} else {
|
||||||
} else {
|
categoryMap['recent-series'].items.push(series)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -819,6 +816,12 @@ module.exports = {
|
|||||||
|
|
||||||
// Sort series books by sequence
|
// Sort series books by sequence
|
||||||
if (categoryMap['recent-series'].items.length) {
|
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) {
|
for (const seriesItem of categoryMap['recent-series'].items) {
|
||||||
seriesItem.books = naturalSort(seriesItem.books).asc(li => li.seriesSequence)
|
seriesItem.books = naturalSort(seriesItem.books).asc(li => li.seriesSequence)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user