diff --git a/client/components/modals/podcast/EditEpisode.vue b/client/components/modals/podcast/EditEpisode.vue
index 236a0d87..e2126be2 100644
--- a/client/components/modals/podcast/EditEpisode.vue
+++ b/client/components/modals/podcast/EditEpisode.vue
@@ -11,8 +11,15 @@
+
+
+
-
+
@@ -21,8 +28,8 @@
export default {
data() {
return {
+ episodeItem: null,
processing: false,
- selectedTab: 'details',
tabs: [
{
id: 'details',
@@ -37,6 +44,29 @@ export default {
]
}
},
+ watch: {
+ show: {
+ handler(newVal) {
+ if (newVal) {
+ const availableTabIds = this.tabs.map((tab) => tab.id);
+ if (!availableTabIds.length) {
+ this.show = false
+ return
+ }
+
+ if (!availableTabIds.includes(this.selectedTab)) {
+ this.selectedTab = availableTabIds[0]
+ }
+
+ this.episodeItem = null
+ this.init()
+ this.registerListeners()
+ } else {
+ this.unregisterListeners()
+ }
+ }
+ }
+ },
computed: {
show: {
get() {
@@ -46,27 +76,128 @@ export default {
this.$store.commit('globals/setShowEditPodcastEpisodeModal', val)
}
},
+ selectedTab: {
+ get() {
+ return this.$store.state.editPodcastModalTab
+ },
+ set(val) {
+ this.$store.commit('setEditPodcastModalTab', val)
+ }
+ },
libraryItem() {
return this.$store.state.selectedLibraryItem
},
episode() {
return this.$store.state.globals.selectedEpisode
},
+ selectedEpisodeId() {
+ return this.episode.id
+ },
title() {
if (!this.libraryItem) return ''
return this.libraryItem.media.metadata.title || 'Unknown'
},
tabComponentName() {
- var _tab = this.tabs.find((t) => t.id === this.selectedTab)
+ const _tab = this.tabs.find((t) => t.id === this.selectedTab);
return _tab ? _tab.component : ''
+ },
+ episodeTableEpisodeIds() {
+ return this.$store.state.episodeTableEpisodeIds || []
+ },
+ currentEpisodeIndex() {
+ if (!this.episodeTableEpisodeIds.length) return 0
+ return this.episodeTableEpisodeIds.findIndex((bid) => bid === this.selectedEpisodeId)
+ },
+ canGoPrev() {
+ return this.episodeTableEpisodeIds.length && this.currentEpisodeIndex > 0
+ },
+ canGoNext() {
+ return this.episodeTableEpisodeIds.length && this.currentEpisodeIndex < this.episodeTableEpisodeIds.length - 1
}
},
methods: {
+ async goPrevEpisode() {
+ if (this.currentEpisodeIndex - 1 < 0) return
+ const prevEpisodeId = this.episodeTableEpisodeIds[this.currentEpisodeIndex - 1];
+ this.processing = true
+ const prevEpisode = await this.$axios.$get(`/api/podcasts/${this.libraryItem.id}/episode/${prevEpisodeId}`).catch((error) => {
+ const errorMsg = error.response && error.response.data ? error.response.data : 'Failed to fetch episode';
+ this.$toast.error(errorMsg)
+ return null
+ });
+ this.processing = false
+ if (prevEpisode) {
+ this.unregisterListeners()
+ this.episodeItem = prevEpisode
+ this.$store.commit('globals/setSelectedEpisode', prevEpisode)
+ this.$nextTick(this.registerListeners)
+ } else {
+ console.error('Episode not found', prevEpisodeId)
+ }
+ },
+ async goNextEpisode() {
+ if (this.currentEpisodeIndex >= this.episodeTableEpisodeIds.length - 1) return
+ this.processing = true
+ const nextEpisodeId = this.episodeTableEpisodeIds[this.currentEpisodeIndex + 1];
+ const nextEpisode = await this.$axios.$get(`/api/podcasts/${this.libraryItem.id}/episode/${nextEpisodeId}`).catch((error) => {
+ const errorMsg = error.response && error.response.data ? error.response.data : 'Failed to fetch book';
+ this.$toast.error(errorMsg)
+ return null
+ });
+ this.processing = false
+ if (nextEpisode) {
+ this.unregisterListeners()
+ this.episodeItem = nextEpisode
+ this.$store.commit('globals/setSelectedEpisode', nextEpisode)
+ this.$nextTick(this.registerListeners)
+ } else {
+ console.error('Episode not found', nextEpisodeId)
+ }
+ },
selectTab(tab) {
- this.selectedTab = tab
+ if (this.selectedTab === tab) return
+ if (this.tabs.find((t) => t.id === tab)) {
+ this.selectedTab = tab
+ this.processing = false
+ }
+ },
+ libraryItemUpdated(expandedLibraryItem) {
+ this.libraryItem = expandedLibraryItem
+ },
+ init() {
+ this.fetchFull()
+ },
+ async fetchFull() {
+ try {
+ this.processing = true
+ this.episodeItem = await this.$axios.$get(`/api/podcasts/${this.libraryItem.id}/episode/${this.selectedEpisodeId}`)
+ this.processing = false
+ } catch (error) {
+ console.error('Failed to fetch episode', this.selectedEpisodeId, error)
+ this.processing = false
+ this.show = false
+ }
+ },
+ hotkey(action) {
+ if (action === this.$hotkeys.Modal.NEXT_PAGE) {
+ this.goNextEpisode()
+ } else if (action === this.$hotkeys.Modal.PREV_PAGE) {
+ this.goPrevEpisode()
+ }
+ },
+ registerListeners() {
+ this.$eventBus.$on('modal-hotkey', this.hotkey)
+ this.$eventBus.$on(`${this.selectedLibraryItemId}_updated`, this.libraryItemUpdated)
+ },
+ unregisterListeners() {
+ this.$eventBus.$off('modal-hotkey', this.hotkey)
+ this.$eventBus.$off(`${this.selectedLibraryItemId}_updated`, this.libraryItemUpdated)
}
},
- mounted() {}
+ mounted() {},
+ beforeDestroy() {
+ this.unregisterListeners()
+ }
}
@@ -77,4 +208,4 @@ export default {
.tab.tab-selected {
height: 41px;
}
-
\ No newline at end of file
+
diff --git a/client/components/tables/podcast/EpisodesTable.vue b/client/components/tables/podcast/EpisodesTable.vue
index 39228a39..03883bc1 100644
--- a/client/components/tables/podcast/EpisodesTable.vue
+++ b/client/components/tables/podcast/EpisodesTable.vue
@@ -281,6 +281,8 @@ export default {
this.showPodcastRemoveModal = true
},
editEpisode(episode) {
+ const episodeIds = this.episodesSorted.map((e) => e.id)
+ this.$store.commit('setEpisodeTableEpisodeIds', episodeIds)
this.$store.commit('setSelectedLibraryItem', this.libraryItem)
this.$store.commit('globals/setSelectedEpisode', episode)
this.$store.commit('globals/setShowEditPodcastEpisodeModal', true)
@@ -314,4 +316,4 @@ export default {
.episode-leave-active {
position: absolute;
}
-
\ No newline at end of file
+
diff --git a/client/store/index.js b/client/store/index.js
index 1529d864..57710da2 100644
--- a/client/store/index.js
+++ b/client/store/index.js
@@ -13,6 +13,7 @@ export const state = () => ({
playerQueueAutoPlay: true,
playerIsFullscreen: false,
editModalTab: 'details',
+ editPodcastModalTab: 'details',
showEditModal: false,
showEReader: false,
selectedLibraryItem: null,
@@ -21,6 +22,7 @@ export const state = () => ({
previousPath: '/',
showExperimentalFeatures: false,
bookshelfBookIds: [],
+ episodeTableEpisodeIds: [],
openModal: null,
innerModalOpen: false,
lastBookshelfScrollData: {},
@@ -135,6 +137,9 @@ export const mutations = {
setBookshelfBookIds(state, val) {
state.bookshelfBookIds = val || []
},
+ setEpisodeTableEpisodeIds(state, val) {
+ state.episodeTableEpisodeIds = val || []
+ },
setPreviousPath(state, val) {
state.previousPath = val
},
@@ -198,6 +203,9 @@ export const mutations = {
setShowEditModal(state, val) {
state.showEditModal = val
},
+ setEditPodcastModalTab(state, tab) {
+ state.editPodcastModalTab = tab
+ },
showEReader(state, libraryItem) {
state.selectedLibraryItem = libraryItem
@@ -225,4 +233,4 @@ export const mutations = {
setInnerModalOpen(state, val) {
state.innerModalOpen = val
}
-}
\ No newline at end of file
+}
diff --git a/server/controllers/PodcastController.js b/server/controllers/PodcastController.js
index 23cec882..60f1e35d 100644
--- a/server/controllers/PodcastController.js
+++ b/server/controllers/PodcastController.js
@@ -225,6 +225,20 @@ class PodcastController {
res.json(libraryItem.toJSONExpanded())
}
+ // GET: api/podcasts/:id/episode/:episodeId
+ async getEpisode(req, res) {
+ const episodeId = req.params.episodeId;
+ const libraryItem = req.libraryItem;
+
+ const episode = libraryItem.media.episodes.find(ep => ep.id === episodeId);
+ if (!episode) {
+ Logger.error(`[PodcastController] getEpisode episode ${episodeId} not found for item ${libraryItem.id}`)
+ return res.sendStatus(404)
+ }
+
+ res.json(episode)
+ }
+
// DELETE: api/podcasts/:id/episode/:episodeId
async removeEpisode(req, res) {
var episodeId = req.params.episodeId
@@ -283,4 +297,4 @@ class PodcastController {
next()
}
}
-module.exports = new PodcastController()
\ No newline at end of file
+module.exports = new PodcastController()
diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js
index 41c26769..53721706 100644
--- a/server/routers/ApiRouter.js
+++ b/server/routers/ApiRouter.js
@@ -235,6 +235,7 @@ class ApiRouter {
this.router.get('/podcasts/:id/search-episode', PodcastController.middleware.bind(this), PodcastController.findEpisode.bind(this))
this.router.post('/podcasts/:id/download-episodes', PodcastController.middleware.bind(this), PodcastController.downloadEpisodes.bind(this))
this.router.post('/podcasts/:id/match-episodes', PodcastController.middleware.bind(this), PodcastController.quickMatchEpisodes.bind(this))
+ this.router.get('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.getEpisode.bind(this))
this.router.patch('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.updateEpisode.bind(this))
this.router.delete('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.removeEpisode.bind(this))
@@ -553,4 +554,4 @@ class ApiRouter {
}
}
}
-module.exports = ApiRouter
\ No newline at end of file
+module.exports = ApiRouter