diff --git a/build/debian/DEBIAN/preinst b/build/debian/DEBIAN/preinst index ad6657ef..f43f2683 100644 --- a/build/debian/DEBIAN/preinst +++ b/build/debian/DEBIAN/preinst @@ -60,13 +60,13 @@ install_ffmpeg() { fi $WGET - tar xvf ffmpeg-git-amd64-static.tar.xz --strip-components=1 + tar xvf ffmpeg-git-amd64-static.tar.xz --strip-components=1 --no-same-owner rm ffmpeg-git-amd64-static.tar.xz # Temp downloading tone library to the ffmpeg dir echo "Getting tone.." $WGET_TONE - tar xvf tone-0.1.5-linux-x64.tar.gz --strip-components=1 + tar xvf tone-0.1.5-linux-x64.tar.gz --strip-components=1 --no-same-owner rm tone-0.1.5-linux-x64.tar.gz echo "Good to go on Ffmpeg (& tone)... hopefully" diff --git a/client/components/app/BookShelfCategorized.vue b/client/components/app/BookShelfCategorized.vue index 40793610..fbed11be 100644 --- a/client/components/app/BookShelfCategorized.vue +++ b/client/components/app/BookShelfCategorized.vue @@ -171,7 +171,7 @@ export default { }, async fetchCategories() { const categories = await this.$axios - .$get(`/api/libraries/${this.currentLibraryId}/personalized2?include=rssfeed,numEpisodesIncomplete`) + .$get(`/api/libraries/${this.currentLibraryId}/personalized?include=rssfeed,numEpisodesIncomplete`) .then((data) => { return data }) diff --git a/client/components/app/ConfigSideNav.vue b/client/components/app/ConfigSideNav.vue index 0868bafb..50e440d7 100644 --- a/client/components/app/ConfigSideNav.vue +++ b/client/components/app/ConfigSideNav.vue @@ -99,6 +99,11 @@ export default { id: 'config-item-metadata-utils', title: this.$strings.HeaderItemMetadataUtils, path: '/config/item-metadata-utils' + }, + { + id: 'config-rss-feeds', + title: this.$strings.HeaderRSSFeeds, + path: '/config/rss-feeds' } ] diff --git a/client/components/app/LazyBookshelf.vue b/client/components/app/LazyBookshelf.vue index 3bc98fde..189f2c83 100644 --- a/client/components/app/LazyBookshelf.vue +++ b/client/components/app/LazyBookshelf.vue @@ -314,11 +314,6 @@ export default { } let entityPath = this.entityName === 'series-books' ? 'items' : this.entityName - // TODO: Temp use new library items API for everything except collapse sub-series - if (entityPath === 'items' && !this.collapseBookSeries && !(this.filterName === 'Series' && this.collapseSeries)) { - entityPath += '2' - } - const sfQueryString = this.currentSFQueryString ? this.currentSFQueryString + '&' : '' const fullQueryString = `?${sfQueryString}limit=${this.booksPerFetch}&page=${page}&minified=1&include=rssfeed,numEpisodesIncomplete` @@ -628,6 +623,11 @@ export default { return entitiesPerShelfBefore < this.entitiesPerShelf // Books per shelf has changed }, async init(bookshelf) { + if (this.entityName === 'series') { + this.booksPerFetch = 50 + } else { + this.booksPerFetch = 100 + } this.checkUpdateSearchParams() this.initSizeData(bookshelf) diff --git a/client/components/cards/LazyBookCard.vue b/client/components/cards/LazyBookCard.vue index 8045f504..d837bf90 100644 --- a/client/components/cards/LazyBookCard.vue +++ b/client/components/cards/LazyBookCard.vue @@ -219,7 +219,7 @@ export default { return this.mediaMetadata.series }, seriesSequence() { - return this.series ? this.series.sequence : null + return this.series?.sequence || null }, libraryId() { return this._libraryItem.libraryId @@ -318,6 +318,7 @@ export default { if (this.orderBy === 'media.duration') return 'Duration: ' + this.$elapsedPrettyExtended(this.media.duration, false) if (this.orderBy === 'size') return 'Size: ' + this.$bytesPretty(this._libraryItem.size) if (this.orderBy === 'media.numTracks') return `${this.numEpisodes} Episodes` + if (this.orderBy === 'media.metadata.publishedYear' && this.mediaMetadata.publishedYear) return 'Published ' + this.mediaMetadata.publishedYear return null }, episodeProgress() { diff --git a/client/components/cards/NarratorCard.vue b/client/components/cards/NarratorCard.vue index 7b8848cc..e1b4840f 100644 --- a/client/components/cards/NarratorCard.vue +++ b/client/components/cards/NarratorCard.vue @@ -36,7 +36,7 @@ export default { return this.narrator?.name || '' }, numBooks() { - return this.narrator?.books?.length || 0 + return this.narrator?.numBooks || this.narrator?.books?.length || 0 }, userCanUpdate() { return this.$store.getters['user/getUserCanUpdate'] diff --git a/client/components/controls/GlobalSearch.vue b/client/components/controls/GlobalSearch.vue index a731dbcf..0c5ba41b 100644 --- a/client/components/controls/GlobalSearch.vue +++ b/client/components/controls/GlobalSearch.vue @@ -103,7 +103,7 @@ export default { return this.$store.state.libraries.currentLibraryId }, totalResults() { - return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.tagResults.length + this.podcastResults.length + return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.tagResults.length + this.podcastResults.length + this.narratorResults.length } }, methods: { diff --git a/client/components/covers/PreviewCover.vue b/client/components/covers/PreviewCover.vue index f25d655d..daf579ac 100644 --- a/client/components/covers/PreviewCover.vue +++ b/client/components/covers/PreviewCover.vue @@ -13,8 +13,8 @@
- -

Invalid Cover

+ +

Invalid Cover

@@ -58,6 +58,9 @@ export default { sizeMultiplier() { return this.width / 120 }, + invalidCoverFontSize() { + return Math.max(this.sizeMultiplier * 0.8, 0.5) + }, placeholderCoverPadding() { return 0.8 * this.sizeMultiplier }, diff --git a/client/components/modals/item/tabs/Cover.vue b/client/components/modals/item/tabs/Cover.vue index 184d2036..09329482 100644 --- a/client/components/modals/item/tabs/Cover.vue +++ b/client/components/modals/item/tabs/Cover.vue @@ -283,9 +283,8 @@ export default { } if (success) { this.$toast.success('Update Successful') - // this.$emit('close') - } else { - this.imageUrl = this.media.coverPath || '' + } else if (this.media.coverPath) { + this.imageUrl = this.media.coverPath } this.isProcessing = false }, diff --git a/client/components/modals/libraries/LibrarySettings.vue b/client/components/modals/libraries/LibrarySettings.vue index 5d91daa6..53eb2650 100644 --- a/client/components/modals/libraries/LibrarySettings.vue +++ b/client/components/modals/libraries/LibrarySettings.vue @@ -11,9 +11,9 @@
- + -

{{ $strings.LabelSettingsDisableWatcherForLibrary }}

+

{{ $strings.LabelSettingsEnableWatcherForLibrary }}

*{{ $strings.MessageWatcherIsDisabledGlobally }}

@@ -65,7 +65,7 @@ export default { return { provider: null, useSquareBookCovers: false, - disableWatcher: false, + enableWatcher: false, skipMatchingMediaWithAsin: false, skipMatchingMediaWithIsbn: false, audiobooksOnly: false, @@ -95,7 +95,7 @@ export default { return { settings: { coverAspectRatio: this.useSquareBookCovers ? this.$constants.BookCoverAspectRatio.SQUARE : this.$constants.BookCoverAspectRatio.STANDARD, - disableWatcher: !!this.disableWatcher, + disableWatcher: !this.enableWatcher, skipMatchingMediaWithAsin: !!this.skipMatchingMediaWithAsin, skipMatchingMediaWithIsbn: !!this.skipMatchingMediaWithIsbn, audiobooksOnly: !!this.audiobooksOnly, @@ -108,7 +108,7 @@ export default { }, init() { this.useSquareBookCovers = this.librarySettings.coverAspectRatio === this.$constants.BookCoverAspectRatio.SQUARE - this.disableWatcher = !!this.librarySettings.disableWatcher + this.enableWatcher = !this.librarySettings.disableWatcher this.skipMatchingMediaWithAsin = !!this.librarySettings.skipMatchingMediaWithAsin this.skipMatchingMediaWithIsbn = !!this.librarySettings.skipMatchingMediaWithIsbn this.audiobooksOnly = !!this.librarySettings.audiobooksOnly diff --git a/client/components/modals/rssfeed/OpenCloseModal.vue b/client/components/modals/rssfeed/OpenCloseModal.vue index f5fe647c..c4ec909c 100644 --- a/client/components/modals/rssfeed/OpenCloseModal.vue +++ b/client/components/modals/rssfeed/OpenCloseModal.vue @@ -132,6 +132,8 @@ export default { return } + this.processing = true + const payload = { serverAddress: window.origin, slug: this.newFeedSlug, @@ -151,6 +153,9 @@ export default { const errorMsg = error.response ? error.response.data : null this.$toast.error(errorMsg || 'Failed to open RSS Feed') }) + .finally(() => { + this.processing = false + }) }, copyToClipboard(str) { this.$copyToClipboard(str, this) diff --git a/client/components/modals/rssfeed/ViewFeedModal.vue b/client/components/modals/rssfeed/ViewFeedModal.vue new file mode 100644 index 00000000..9e34b17f --- /dev/null +++ b/client/components/modals/rssfeed/ViewFeedModal.vue @@ -0,0 +1,124 @@ + + + + + + diff --git a/client/components/readers/ComicReader.vue b/client/components/readers/ComicReader.vue index f1d9a82f..40b28a74 100644 --- a/client/components/readers/ComicReader.vue +++ b/client/components/readers/ComicReader.vue @@ -303,8 +303,8 @@ export default { }, parseImageFilename(filename) { var basename = Path.basename(filename, Path.extname(filename)) - var numbersinpath = basename.match(/\d{1,5}/g) - if (!numbersinpath || !numbersinpath.length) { + var numbersinpath = basename.match(/\d+/g) + if (!numbersinpath?.length) { return { index: -1, filename diff --git a/client/components/stats/PreviewIcons.vue b/client/components/stats/PreviewIcons.vue index 488eae49..7bda889b 100644 --- a/client/components/stats/PreviewIcons.vue +++ b/client/components/stats/PreviewIcons.vue @@ -18,7 +18,7 @@ -
+
@@ -58,26 +58,32 @@ export default { return {} }, computed: { + currentLibraryMediaType() { + return this.$store.getters['libraries/getCurrentLibraryMediaType'] + }, + isBookLibrary() { + return this.currentLibraryMediaType === 'book' + }, user() { return this.$store.state.user.user }, totalItems() { - return this.libraryStats ? this.libraryStats.totalItems : 0 + return this.libraryStats?.totalItems || 0 }, totalAuthors() { - return this.libraryStats ? this.libraryStats.totalAuthors : 0 + return this.libraryStats?.totalAuthors || 0 }, numAudioTracks() { - return this.libraryStats ? this.libraryStats.numAudioTracks : 0 + return this.libraryStats?.numAudioTracks || 0 }, totalDuration() { - return this.libraryStats ? this.libraryStats.totalDuration : 0 + return this.libraryStats?.totalDuration || 0 }, totalHours() { return Math.round(this.totalDuration / (60 * 60)) }, totalSizePretty() { - var totalSize = this.libraryStats ? this.libraryStats.totalSize : 0 + var totalSize = this.libraryStats?.totalSize || 0 return this.$bytesPretty(totalSize, 1) }, totalSizeNum() { diff --git a/client/components/tables/library/LibrariesTable.vue b/client/components/tables/library/LibrariesTable.vue index a9255e2d..598b12b7 100644 --- a/client/components/tables/library/LibrariesTable.vue +++ b/client/components/tables/library/LibrariesTable.vue @@ -11,10 +11,6 @@ {{ $strings.ButtonAddYourFirstLibrary }}
-

- *{{ $strings.ButtonForceReScan }} {{ $strings.MessageForceReScanDescription }} -

-

**{{ $strings.ButtonMatchBooks }} {{ $strings.MessageMatchBooksDescription }}

diff --git a/client/components/tables/library/LibraryItem.vue b/client/components/tables/library/LibraryItem.vue index 6cec8867..b84dec44 100644 --- a/client/components/tables/library/LibraryItem.vue +++ b/client/components/tables/library/LibraryItem.vue @@ -71,11 +71,6 @@ export default { text: this.$strings.ButtonScan, action: 'scan', value: 'scan' - }, - { - text: this.$strings.ButtonForceReScan, - action: 'force-scan', - value: 'force-scan' } ] if (this.isBookLibrary) { @@ -137,26 +132,6 @@ export default { this.$toast.error(this.$strings.ToastLibraryScanFailedToStart) }) }, - forceScan() { - const payload = { - message: this.$strings.MessageConfirmForceReScan, - callback: (confirmed) => { - if (confirmed) { - this.$store - .dispatch('libraries/requestLibraryScan', { libraryId: this.library.id, force: 1 }) - .then(() => { - this.$toast.success(this.$strings.ToastLibraryScanStarted) - }) - .catch((error) => { - console.error('Failed to start scan', error) - this.$toast.error(this.$strings.ToastLibraryScanFailedToStart) - }) - } - }, - type: 'yesNo' - } - this.$store.commit('globals/setConfirmPrompt', payload) - }, deleteClick() { const payload = { message: this.$getString('MessageConfirmDeleteLibrary', [this.library.name]), diff --git a/client/layouts/default.vue b/client/layouts/default.vue index d27936e2..805fb8a1 100644 --- a/client/layouts/default.vue +++ b/client/layouts/default.vue @@ -343,6 +343,10 @@ export default { } this.$store.commit('libraries/removeCollection', collection) }, + seriesRemoved({ id, libraryId }) { + if (this.currentLibraryId !== libraryId) return + this.$store.commit('libraries/removeSeriesFromFilterData', id) + }, playlistAdded(playlist) { if (playlist.userId !== this.user.id || this.currentLibraryId !== playlist.libraryId) return this.$store.commit('libraries/addUpdateUserPlaylist', playlist) @@ -442,6 +446,9 @@ export default { this.socket.on('collection_updated', this.collectionUpdated) this.socket.on('collection_removed', this.collectionRemoved) + // Series Listeners + this.socket.on('series_removed', this.seriesRemoved) + // User Playlist Listeners this.socket.on('playlist_added', this.playlistAdded) this.socket.on('playlist_updated', this.playlistUpdated) diff --git a/client/package-lock.json b/client/package-lock.json index df642d28..6f24e887 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "audiobookshelf-client", - "version": "2.3.3", + "version": "2.4.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "audiobookshelf-client", - "version": "2.3.3", + "version": "2.4.1", "license": "ISC", "dependencies": { "@nuxtjs/axios": "^5.13.6", diff --git a/client/package.json b/client/package.json index 0c7dcaf3..0bb4bd60 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf-client", - "version": "2.3.3", + "version": "2.4.1", "description": "Self-hosted audiobook and podcast client", "main": "index.js", "scripts": { diff --git a/client/pages/config.vue b/client/pages/config.vue index 2b63d83c..542b7f2c 100644 --- a/client/pages/config.vue +++ b/client/pages/config.vue @@ -55,6 +55,7 @@ export default { else if (pageName === 'library-stats') return this.$strings.HeaderLibraryStats else if (pageName === 'users') return this.$strings.HeaderUsers else if (pageName === 'item-metadata-utils') return this.$strings.HeaderItemMetadataUtils + else if (pageName === 'rss-feeds') return this.$strings.HeaderRSSFeeds else if (pageName === 'email') return this.$strings.HeaderEmail } return this.$strings.HeaderSettings diff --git a/client/pages/config/index.vue b/client/pages/config/index.vue index af84317d..67391141 100644 --- a/client/pages/config/index.vue +++ b/client/pages/config/index.vue @@ -36,7 +36,10 @@
- + +
+ Save +
@@ -157,10 +160,10 @@
- - + +

- {{ $strings.LabelSettingsDisableWatcher }} + {{ $strings.LabelSettingsEnableWatcher }} info_outlined

@@ -259,9 +262,12 @@ export default { updatingServerSettings: false, homepageUseBookshelfView: false, useBookshelfView: false, + scannerEnableWatcher: false, isPurgingCache: false, + hasPrefixesChanged: false, newServerSettings: {}, showConfirmPurgeCache: false, + savingPrefixes: false, metadataFileFormats: [ { text: '.json', @@ -304,15 +310,36 @@ export default { } }, methods: { - updateSortingPrefixes(val) { - if (!val || !val.length) { + sortingPrefixesUpdated(val) { + const prefixes = [...new Set(val?.map((prefix) => prefix.trim().toLowerCase()) || [])] + this.newServerSettings.sortingPrefixes = prefixes + const serverPrefixes = this.serverSettings.sortingPrefixes || [] + this.hasPrefixesChanged = prefixes.some((p) => !serverPrefixes.includes(p)) || serverPrefixes.some((p) => !prefixes.includes(p)) + }, + updateSortingPrefixes() { + const prefixes = [...new Set(this.newServerSettings.sortingPrefixes.map((prefix) => prefix.trim().toLowerCase()) || [])] + if (!prefixes.length) { this.$toast.error('Must have at least 1 prefix') return } - var prefixes = val.map((prefix) => prefix.trim().toLowerCase()) - this.updateServerSettings({ - sortingPrefixes: prefixes - }) + + this.savingPrefixes = true + this.$axios + .$patch(`/api/sorting-prefixes`, { sortingPrefixes: prefixes }) + .then((data) => { + this.$toast.success(`Sorting prefixes updated. ${data.rowsUpdated} rows`) + if (data.serverSettings) { + this.$store.commit('setServerSettings', data.serverSettings) + } + this.hasPrefixesChanged = false + }) + .catch((error) => { + console.error('Failed to update prefixes', error) + this.$toast.error('Failed to update sorting prefixes') + }) + .finally(() => { + this.savingPrefixes = false + }) }, updateScannerCoverProvider(val) { this.updateServerSettings({ @@ -337,6 +364,9 @@ export default { this.updateSettingsKey('metadataFileFormat', val) }, updateSettingsKey(key, val) { + if (key === 'scannerDisableWatcher') { + this.newServerSettings.scannerDisableWatcher = val + } this.updateServerSettings({ [key]: val }) @@ -363,6 +393,7 @@ export default { initServerSettings() { this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {} this.newServerSettings.sortingPrefixes = [...(this.newServerSettings.sortingPrefixes || [])] + this.scannerEnableWatcher = !this.newServerSettings.scannerDisableWatcher this.homepageUseBookshelfView = this.newServerSettings.homeBookshelfView != this.$constants.BookshelfView.DETAIL this.useBookshelfView = this.newServerSettings.bookshelfView != this.$constants.BookshelfView.DETAIL diff --git a/client/pages/config/library-stats.vue b/client/pages/config/library-stats.vue index cc3dbf8e..1a95c630 100644 --- a/client/pages/config/library-stats.vue +++ b/client/pages/config/library-stats.vue @@ -22,7 +22,7 @@
-
+

{{ $strings.HeaderStatsTop10Authors }}

{{ $strings.MessageNoAuthors }}