@@ -157,6 +157,12 @@ export default {
coverPath() {
return this.media.coverPath
},
+ coverUrl() {
+ if (!this.coverPath) {
+ return this.$store.getters['globals/getPlaceholderCoverSrc']
+ }
+ return this.$store.getters['globals/getLibraryItemCoverSrcById'](this.libraryItemId, this.libraryItemUpdatedAt, true)
+ },
mediaMetadata() {
return this.media.metadata || {}
},
diff --git a/client/components/modals/player/QueueItemRow.vue b/client/components/modals/player/QueueItemRow.vue
index 2eb1bc3b6..9ac01a167 100644
--- a/client/components/modals/player/QueueItemRow.vue
+++ b/client/components/modals/player/QueueItemRow.vue
@@ -55,7 +55,7 @@ export default {
return this.item.coverPath
},
coverUrl() {
- if (!this.coverPath) return `${this.$config.routerBasePath}/book_placeholder.jpg`
+ if (!this.coverPath) return this.$store.getters['globals/getPlaceholderCoverSrc']
return this.$store.getters['globals/getLibraryItemCoverSrcById'](this.libraryItemId)
},
bookCoverAspectRatio() {
diff --git a/client/components/modals/podcast/EpisodeFeed.vue b/client/components/modals/podcast/EpisodeFeed.vue
index 2648c5223..08f2f38c8 100644
--- a/client/components/modals/podcast/EpisodeFeed.vue
+++ b/client/components/modals/podcast/EpisodeFeed.vue
@@ -10,6 +10,12 @@
+
+ {{ $strings.LabelSortPubDate }}
+
+ {{ sortDescending ? 'expand_more' : 'expand_less' }}
+
+
@@ -73,7 +79,8 @@ export default {
searchTimeout: null,
searchText: null,
downloadedEpisodeGuidMap: {},
- downloadedEpisodeUrlMap: {}
+ downloadedEpisodeUrlMap: {},
+ sortDescending: true
}
},
watch: {
@@ -141,6 +148,17 @@ export default {
}
},
methods: {
+ toggleSort() {
+ this.sortDescending = !this.sortDescending
+ this.episodesCleaned = this.episodesCleaned.toSorted((a, b) => {
+ if (this.sortDescending) {
+ return a.publishedAt < b.publishedAt ? 1 : -1
+ }
+ return a.publishedAt > b.publishedAt ? 1 : -1
+ })
+ this.selectedEpisodes = {}
+ this.selectAll = false
+ },
getIsEpisodeDownloaded(episode) {
if (episode.guid && !!this.downloadedEpisodeGuidMap[episode.guid]) {
return true
diff --git a/client/components/player/PlayerTrackBar.vue b/client/components/player/PlayerTrackBar.vue
index cc581e254..6c96a6bfb 100644
--- a/client/components/player/PlayerTrackBar.vue
+++ b/client/components/player/PlayerTrackBar.vue
@@ -74,6 +74,9 @@ export default {
currentChapterStart() {
if (!this.currentChapter) return 0
return this.currentChapter.start
+ },
+ isMobile() {
+ return this.$store.state.globals.isMobile
}
},
methods: {
@@ -145,6 +148,9 @@ export default {
})
},
mousemoveTrack(e) {
+ if (this.isMobile) {
+ return
+ }
const offsetX = e.offsetX
const baseTime = this.useChapterTrack ? this.currentChapterStart : 0
@@ -198,6 +204,7 @@ export default {
setTrackWidth() {
if (this.$refs.track) {
this.trackWidth = this.$refs.track.clientWidth
+ this.trackOffsetLeft = this.$refs.track.getBoundingClientRect().left
} else {
console.error('Track not loaded', this.$refs)
}
diff --git a/client/components/stats/YearInReviewBanner.vue b/client/components/stats/YearInReviewBanner.vue
index e36775382..62776bc6b 100644
--- a/client/components/stats/YearInReviewBanner.vue
+++ b/client/components/stats/YearInReviewBanner.vue
@@ -164,14 +164,15 @@ export default {
beforeMount() {
this.yearInReviewYear = new Date().getFullYear()
- // When not December show previous year
- if (new Date().getMonth() < 11) {
+ this.availableYears = this.getAvailableYears()
+ const availableYearValues = this.availableYears.map((y) => y.value)
+
+ // When not December show previous year if data is available
+ if (new Date().getMonth() < 11 && availableYearValues.includes(this.yearInReviewYear - 1)) {
this.yearInReviewYear--
}
},
mounted() {
- this.availableYears = this.getAvailableYears()
-
if (typeof navigator.share !== 'undefined' && navigator.share) {
this.showShareButton = true
} else {
diff --git a/client/components/tables/BackupsTable.vue b/client/components/tables/BackupsTable.vue
index 69772732c..769c8d25c 100644
--- a/client/components/tables/BackupsTable.vue
+++ b/client/components/tables/BackupsTable.vue
@@ -26,9 +26,9 @@
error_outline
-
+
-
+
diff --git a/client/cypress/tests/components/cards/AuthorCard.cy.js b/client/cypress/tests/components/cards/AuthorCard.cy.js
index 21c638e18..4c4a1cb88 100644
--- a/client/cypress/tests/components/cards/AuthorCard.cy.js
+++ b/client/cypress/tests/components/cards/AuthorCard.cy.js
@@ -19,7 +19,9 @@ describe('AuthorCard', () => {
const mocks = {
$strings: {
LabelBooks: 'Books',
- ButtonQuickMatch: 'Quick Match'
+ ButtonQuickMatch: 'Quick Match',
+ ToastAuthorUpdateSuccess: 'Author updated',
+ ToastAuthorUpdateSuccessNoImageFound: 'Author updated (no image found)'
},
$store: {
getters: {
@@ -167,7 +169,7 @@ describe('AuthorCard', () => {
cy.get('&match').click()
cy.get('&spinner').should('be.hidden')
- cy.get('@success').should('have.been.calledOnceWithExactly', 'Author John Doe was updated (no image found)')
+ cy.get('@success').should('have.been.calledOnceWithExactly', 'Author updated (no image found)')
cy.get('@error').should('not.have.been.called')
cy.get('@info').should('not.have.been.called')
})
@@ -189,7 +191,7 @@ describe('AuthorCard', () => {
cy.get('&match').click()
cy.get('&spinner').should('be.hidden')
- cy.get('@success').should('have.been.calledOnceWithExactly', 'Author John Doe was updated')
+ cy.get('@success').should('have.been.calledOnceWithExactly', 'Author updated')
cy.get('@error').should('not.have.been.called')
cy.get('@info').should('not.have.been.called')
})
diff --git a/client/cypress/tests/components/cards/LazyBookCard.cy.js b/client/cypress/tests/components/cards/LazyBookCard.cy.js
index c39c03023..ab685b0d1 100644
--- a/client/cypress/tests/components/cards/LazyBookCard.cy.js
+++ b/client/cypress/tests/components/cards/LazyBookCard.cy.js
@@ -49,6 +49,7 @@ function createMountOptions() {
'libraries/getLibraryProvider': () => 'audible.us',
'libraries/getBookCoverAspectRatio': 1,
'globals/getLibraryItemCoverSrc': () => 'https://my.server.com/book_placeholder.jpg',
+ 'globals/getPlaceholderCoverSrc': 'https://my.server.com/book_placeholder.jpg',
getLibraryItemsStreaming: () => null,
getIsMediaQueued: () => false,
getIsStreamingFromDifferentLibrary: () => false
@@ -172,6 +173,7 @@ describe('LazyBookCard', () => {
})
it('shows titleImageNotReady and sets opacity 0 on coverImage when image not ready', () => {
+ mountOptions.mocks.$store.getters['globals/getLibraryItemCoverSrc'] = () => 'https://my.server.com/notfound.jpg'
cy.mount(LazyBookCard, mountOptions)
cy.get('&titleImageNotReady').should('be.visible')
@@ -257,7 +259,7 @@ describe('LazyBookCard', () => {
cy.get('#book-card-0').trigger('mouseover')
cy.get('&titleImageNotReady').should('be.hidden')
- cy.get('&seriesNameOverlay').should('be.visible').and('have.text', 'Middle Earth Chronicles')
+ cy.get('&seriesNameOverlay').should('be.visible').and('have.text', 'The Lord of the Rings')
})
it('shows the seriesSequenceList when collapsed series has a sequence list', () => {
diff --git a/client/cypress/tests/components/cards/LazySeriesCard.cy.js b/client/cypress/tests/components/cards/LazySeriesCard.cy.js
index c637c604e..346259d27 100644
--- a/client/cypress/tests/components/cards/LazySeriesCard.cy.js
+++ b/client/cypress/tests/components/cards/LazySeriesCard.cy.js
@@ -30,6 +30,14 @@ describe('LazySeriesCard', () => {
}
const mocks = {
+ $getString: (id, args) => {
+ switch (id) {
+ case 'LabelAddedDate':
+ return `Added ${args[0]}`
+ default:
+ return null
+ }
+ },
$store: {
getters: {
'user/getUserCanUpdate': true,
diff --git a/client/layouts/default.vue b/client/layouts/default.vue
index 9121561e4..33e7aa15b 100644
--- a/client/layouts/default.vue
+++ b/client/layouts/default.vue
@@ -183,7 +183,7 @@ export default {
this.$store.commit('libraries/updateFilterDataWithItem', libraryItem)
},
libraryItemUpdated(libraryItem) {
- if (this.$store.state.selectedLibraryItem && this.$store.state.selectedLibraryItem.id === libraryItem.id) {
+ if (this.$store.state.selectedLibraryItem?.id === libraryItem.id) {
this.$store.commit('setSelectedLibraryItem', libraryItem)
if (this.$store.state.globals.selectedEpisode && libraryItem.mediaType === 'podcast') {
const episode = libraryItem.media.episodes.find((ep) => ep.id === this.$store.state.globals.selectedEpisode.id)
@@ -192,6 +192,9 @@ export default {
}
}
}
+ if (this.$store.state.streamLibraryItem?.id === libraryItem.id) {
+ this.$store.commit('updateStreamLibraryItem', libraryItem)
+ }
this.$eventBus.$emit(`${libraryItem.id}_updated`, libraryItem)
this.$store.commit('libraries/updateFilterDataWithItem', libraryItem)
},
diff --git a/client/package-lock.json b/client/package-lock.json
index d0bf36b7d..733dc4aff 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "audiobookshelf-client",
- "version": "2.20.0",
+ "version": "2.21.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "audiobookshelf-client",
- "version": "2.20.0",
+ "version": "2.21.0",
"license": "ISC",
"dependencies": {
"@nuxtjs/axios": "^5.13.6",
diff --git a/client/package.json b/client/package.json
index baf8013d7..ac6edb8f0 100644
--- a/client/package.json
+++ b/client/package.json
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf-client",
- "version": "2.20.0",
+ "version": "2.21.0",
"buildNumber": 1,
"description": "Self-hosted audiobook and podcast client",
"main": "index.js",
diff --git a/client/pages/audiobook/_id/chapters.vue b/client/pages/audiobook/_id/chapters.vue
index 71abef33b..55f74b5c2 100644
--- a/client/pages/audiobook/_id/chapters.vue
+++ b/client/pages/audiobook/_id/chapters.vue
@@ -141,10 +141,21 @@