diff --git a/client/components/cards/CollectionCard.vue b/client/components/cards/CollectionCard.vue
deleted file mode 100644
index a24ed47c..00000000
--- a/client/components/cards/CollectionCard.vue
+++ /dev/null
@@ -1,112 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/client/components/cards/GroupCard.vue b/client/components/cards/GroupCard.vue
index ec92669e..5b31ed0e 100644
--- a/client/components/cards/GroupCard.vue
+++ b/client/components/cards/GroupCard.vue
@@ -12,9 +12,6 @@
-
@@ -100,15 +97,6 @@ export default {
bookItems() {
return this._group.books || []
},
- userAudiobooks() {
- return Object.values(this.$store.state.user.user ? this.$store.state.user.user.audiobooks || {} : {})
- },
- userProgressItems() {
- return this.bookItems.map((item) => {
- var userAudiobook = this.userAudiobooks.find((ab) => ab.audiobookId === item.id)
- return userAudiobook || {}
- })
- },
groupName() {
return this._group.name || 'No Name'
},
diff --git a/client/components/cards/LazyBookCard.vue b/client/components/cards/LazyBookCard.vue
index c1c08a71..f0f62f29 100644
--- a/client/components/cards/LazyBookCard.vue
+++ b/client/components/cards/LazyBookCard.vue
@@ -197,28 +197,19 @@ export default {
playIconFontSize() {
return Math.max(2, 3 * this.sizeMultiplier)
},
- authors() {
- return this.mediaMetadata.authors || []
- },
author() {
if (this.isPodcast) return this.mediaMetadata.author
- return this.authors.map((au) => au.name).join(', ')
+ return this.mediaMetadata.authorName
},
authorLF() {
- return this.authors
- .map((au) => {
- var parts = au.name.split(' ')
- if (parts.length === 1) return parts[0]
- return `${parts[1]}, ${parts[0]}`
- })
- .join(', ')
+ return this.mediaMetadata.authorNameLF
},
volumeNumber() {
return this.mediaMetadata.volumeNumber || null
},
displayTitle() {
- if (this.orderBy === 'media.metadata.title' && this.sortingIgnorePrefix && this.title.toLowerCase().startsWith('the ')) {
- return this.title.substr(4) + ', The'
+ if (this.orderBy === 'media.metadata.title' && this.sortingIgnorePrefix) {
+ return this.mediaMetadata.titleIgnorePrefix
}
return this.title
},
diff --git a/client/pages/config/index.vue b/client/pages/config/index.vue
index c6d141cc..fd413136 100644
--- a/client/pages/config/index.vue
+++ b/client/pages/config/index.vue
@@ -47,9 +47,21 @@
+
updateSettingsKey('sortingIgnorePrefix', val)" />
- Ignore prefix "The" when sorting title and series
+
+
+ Ignore prefixes when sorting title and series
+ info_outlined
+
+
+
+
+
@@ -203,6 +215,7 @@ export default {
scannerPreferOpfMetadata: 'OPF file metadata will be used for book details over folder names',
scannerPreferAudioMetadata: 'Audio file ID3 meta tags will be used for book details over folder names',
scannerParseSubtitle: 'Extract subtitles from audiobook folder names.
Subtitle must be seperated by " - "
i.e. "Book Title - A Subtitle Here" has the subtitle "A Subtitle Here"',
+ sortingIgnorePrefix: 'i.e. for prefix "the" book title "The Book Title" would sort as "Book Title, The"',
scannerFindCovers: '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',
bookshelfView: 'Alternative bookshelf view that shows title & author under book covers',
storeCoverWithBook: 'By default covers are stored in /metadata/books, enabling this setting will store covers in the books folder. Only one file named "cover" will be kept',
@@ -215,7 +228,6 @@ export default {
watch: {
serverSettings(newVal, oldVal) {
if (newVal && !oldVal) {
- this.newServerSettings = { ...this.serverSettings }
this.initServerSettings()
}
}
@@ -243,6 +255,16 @@ export default {
updateEnableChromecast(val) {
this.updateServerSettings({ enableChromecast: val })
},
+ updateSortingPrefixes(val) {
+ if (!val || !val.length) {
+ this.$toast.error('Must have at least 1 prefix')
+ return
+ }
+ var prefixes = val.map((prefix) => prefix.trim().toLowerCase())
+ this.updateServerSettings({
+ sortingPrefixes: prefixes
+ })
+ },
updateScannerCoverProvider(val) {
this.updateServerSettings({
scannerCoverProvider: val
@@ -278,6 +300,7 @@ export default {
},
initServerSettings() {
this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {}
+ this.newServerSettings.sortingPrefixes = [...(this.newServerSettings.sortingPrefixes || [])]
this.useSquareBookCovers = this.newServerSettings.coverAspectRatio === this.$constants.BookCoverAspectRatio.SQUARE
diff --git a/server/objects/ServerSettings.js b/server/objects/ServerSettings.js
index d76344ef..1c7e0089 100644
--- a/server/objects/ServerSettings.js
+++ b/server/objects/ServerSettings.js
@@ -43,6 +43,8 @@ class ServerSettings {
this.podcastEpisodeSchedule = '0 * * * *' // Every hour
this.sortingIgnorePrefix = false
+ this.sortingPrefixes = ['the', 'a']
+
this.chromecastEnabled = false
this.logLevel = Logger.logLevel
this.version = null
@@ -82,6 +84,7 @@ class ServerSettings {
this.bookshelfView = settings.bookshelfView || BookshelfView.STANDARD
this.sortingIgnorePrefix = !!settings.sortingIgnorePrefix
+ this.sortingPrefixes = settings.sortingPrefixes || ['the', 'a']
this.chromecastEnabled = !!settings.chromecastEnabled
this.logLevel = settings.logLevel || Logger.logLevel
this.version = settings.version || null
@@ -114,6 +117,7 @@ class ServerSettings {
coverAspectRatio: this.coverAspectRatio,
bookshelfView: this.bookshelfView,
sortingIgnorePrefix: this.sortingIgnorePrefix,
+ sortingPrefixes: [...this.sortingPrefixes],
chromecastEnabled: this.chromecastEnabled,
logLevel: this.logLevel,
version: this.version
@@ -123,7 +127,13 @@ class ServerSettings {
update(payload) {
var hasUpdates = false
for (const key in payload) {
- if (this[key] !== payload[key]) {
+ if (key === 'sortingPrefixes' && payload[key] && payload[key].length) {
+ var prefixesCleaned = payload[key].filter(prefix => !!prefix).map(prefix => prefix.toLowerCase())
+ if (prefixesCleaned.join(',') !== this[key].join(',')) {
+ this[key] = [...prefixesCleaned]
+ hasUpdates = true
+ }
+ } else if (this[key] !== payload[key]) {
if (key === 'logLevel') {
Logger.setLogLevel(payload[key])
}
diff --git a/server/objects/mediaTypes/Book.js b/server/objects/mediaTypes/Book.js
index 00a5090d..890cc9b3 100644
--- a/server/objects/mediaTypes/Book.js
+++ b/server/objects/mediaTypes/Book.js
@@ -55,7 +55,7 @@ class Book {
toJSONMinified() {
return {
- metadata: this.metadata.toJSON(),
+ metadata: this.metadata.toJSONMinified(),
coverPath: this.coverPath,
tags: [...this.tags],
numTracks: this.tracks.length,
diff --git a/server/objects/metadata/BookMetadata.js b/server/objects/metadata/BookMetadata.js
index aea32206..7c1e1003 100644
--- a/server/objects/metadata/BookMetadata.js
+++ b/server/objects/metadata/BookMetadata.js
@@ -59,9 +59,31 @@ class BookMetadata {
}
}
+ toJSONMinified() {
+ return {
+ title: this.title,
+ titleIgnorePrefix: this.titleIgnorePrefix,
+ subtitle: this.subtitle,
+ authorName: this.authorName,
+ authorNameLF: this.authorNameLF,
+ narratorName: this.narratorName,
+ seriesName: this.seriesName,
+ genres: [...this.genres],
+ publishedYear: this.publishedYear,
+ publishedDate: this.publishedDate,
+ publisher: this.publisher,
+ description: this.description,
+ isbn: this.isbn,
+ asin: this.asin,
+ language: this.language,
+ explicit: this.explicit
+ }
+ }
+
toJSONExpanded() {
return {
title: this.title,
+ titleIgnorePrefix: this.titleIgnorePrefix,
subtitle: this.subtitle,
authors: this.authors.map(a => ({ ...a })), // Author JSONMinimal with name and id
narrators: [...this.narrators],
@@ -88,8 +110,12 @@ class BookMetadata {
get titleIgnorePrefix() {
if (!this.title) return ''
- if (this.title.toLowerCase().startsWith('the ')) {
- return this.title.substr(4) + ', The'
+ var prefixesToIgnore = global.ServerSettings.sortingPrefixes || []
+ for (const prefix of prefixesToIgnore) {
+ // e.g. for prefix "the". If title is "The Book Title" return "Book Title, The"
+ if (this.title.toLowerCase().startsWith(`${prefix} `)) {
+ return this.title.substr(prefix.length + 1) + `, ${prefix.substr(0, 1).toUpperCase() + prefix.substr(1)}`
+ }
}
return this.title
}