mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-10 11:21:36 +00:00
Merge branch 'refs/heads/master' into mf/rssInboundManager
# Conflicts: # client/components/widgets/PodcastDetailsEdit.vue # client/pages/config/rss-feeds.vue # client/strings/en-us.json # server/controllers/PodcastController.js # server/models/Podcast.js # server/routers/ApiRouter.js # server/utils/queries/libraryItemsPodcastFilters.js
This commit is contained in:
commit
2d2c6242dd
462 changed files with 33361 additions and 21220 deletions
|
|
@ -13,11 +13,11 @@
|
|||
<div class="absolute top-0 left-0 w-full h-full z-10 opacity-0 group-hover:opacity-100 pointer-events-none">
|
||||
<div v-show="showPlayButton && !isStreaming" class="h-full flex items-center justify-center pointer-events-none">
|
||||
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto cursor-pointer" @click.stop.prevent="playItem">
|
||||
<span class="material-icons text-4xl">play_circle_filled</span>
|
||||
<span class="material-symbols fill text-4xl">play_arrow</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="absolute bottom-2.5 right-2.5 z-10 material-icons text-lg cursor-pointer text-white text-opacity-75 hover:text-opacity-100 hover:scale-110 transform duration-200 pointer-events-auto" @click="showEditCover">edit</span>
|
||||
<span class="absolute bottom-2.5 right-2.5 z-10 material-symbols text-lg cursor-pointer text-white text-opacity-75 hover:text-opacity-100 hover:scale-110 transform duration-200 pointer-events-auto" @click="showEditCover">edit</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -39,16 +39,11 @@
|
|||
><span :key="index" v-if="index < seriesList.length - 1">, </span>
|
||||
</template>
|
||||
|
||||
<template v-if="!isVideo">
|
||||
<p v-if="isPodcast" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl">{{ $getString('LabelByAuthor', [podcastAuthor]) }}</p>
|
||||
<p v-else-if="musicArtists.length" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl max-w-[calc(100vw-2rem)] overflow-hidden overflow-ellipsis">
|
||||
<nuxt-link v-for="(artist, index) in musicArtists" :key="index" :to="`/artist/${$encode(artist)}`" class="hover:underline">{{ artist }}<span v-if="index < musicArtists.length - 1">, </span></nuxt-link>
|
||||
</p>
|
||||
<p v-else-if="authors.length" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl max-w-[calc(100vw-2rem)] overflow-hidden overflow-ellipsis">
|
||||
by <nuxt-link v-for="(author, index) in authors" :key="index" :to="`/author/${author.id}`" class="hover:underline">{{ author.name }}<span v-if="index < authors.length - 1">, </span></nuxt-link>
|
||||
</p>
|
||||
<p v-else class="mb-2 mt-0.5 text-gray-200 text-xl">by Unknown</p>
|
||||
</template>
|
||||
<p v-if="isPodcast" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl">{{ $getString('LabelByAuthor', [podcastAuthor]) }}</p>
|
||||
<p v-else-if="authors.length" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl max-w-[calc(100vw-2rem)] overflow-hidden overflow-ellipsis">
|
||||
by <nuxt-link v-for="(author, index) in authors" :key="index" :to="`/author/${author.id}`" class="hover:underline">{{ author.name }}<span v-if="index < authors.length - 1">, </span></nuxt-link>
|
||||
</p>
|
||||
<p v-else class="mb-2 mt-0.5 text-gray-200 text-xl">by Unknown</p>
|
||||
|
||||
<content-library-item-details :library-item="libraryItem" />
|
||||
</div>
|
||||
|
|
@ -60,7 +55,7 @@
|
|||
<div class="flex items-center">
|
||||
<p class="text-sm py-1">{{ $getString('MessageEpisodesQueuedForDownload', [episodeDownloadsQueued.length]) }}</p>
|
||||
|
||||
<span v-if="userIsAdminOrUp" class="material-icons hover:text-error text-xl ml-3 cursor-pointer" @click="clearDownloadQueue">close</span>
|
||||
<span v-if="userIsAdminOrUp" class="material-symbols hover:text-error text-xl ml-3 cursor-pointer" @click="clearDownloadQueue">close</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -80,19 +75,19 @@
|
|||
<p class="text-gray-400 text-xs pt-1">{{ $strings.LabelStarted }} {{ $formatDate(userProgressStartedAt, dateFormat) }}</p>
|
||||
|
||||
<div v-if="!resettingProgress" class="absolute -top-1.5 -right-1.5 p-1 w-5 h-5 rounded-full bg-bg hover:bg-error border border-primary flex items-center justify-center cursor-pointer" @click.stop="clearProgressClick">
|
||||
<span class="material-icons text-sm">close</span>
|
||||
<span class="material-symbols text-sm"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Icon buttons -->
|
||||
<div class="flex items-center justify-center md:justify-start pt-4">
|
||||
<ui-btn v-if="showPlayButton" :disabled="isStreaming" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="playItem">
|
||||
<span v-show="!isStreaming" class="material-icons text-2xl -ml-2 pr-1 text-white">play_arrow</span>
|
||||
<span v-show="!isStreaming" class="material-symbols fill text-2xl -ml-2 pr-1 text-white"></span>
|
||||
{{ isStreaming ? $strings.ButtonPlaying : $strings.ButtonPlay }}
|
||||
</ui-btn>
|
||||
|
||||
<ui-btn v-else-if="isMissing || isInvalid" color="error" :padding-x="4" small class="flex items-center h-9 mr-2">
|
||||
<span v-show="!isStreaming" class="material-icons text-2xl -ml-2 pr-1 text-white">error</span>
|
||||
<span v-show="!isStreaming" class="material-symbols text-2xl -ml-2 pr-1 text-white">error</span>
|
||||
{{ isMissing ? $strings.LabelMissing : $strings.LabelIncomplete }}
|
||||
</ui-btn>
|
||||
|
||||
|
|
@ -101,15 +96,15 @@
|
|||
</ui-tooltip>
|
||||
|
||||
<ui-btn v-if="showReadButton" color="info" :padding-x="4" small class="flex items-center h-9 mr-2" @click="openEbook">
|
||||
<span class="material-icons text-2xl -ml-2 pr-2 text-white">auto_stories</span>
|
||||
<span class="material-symbols text-2xl -ml-2 pr-2 text-white">auto_stories</span>
|
||||
{{ $strings.ButtonRead }}
|
||||
</ui-btn>
|
||||
|
||||
<ui-tooltip v-if="userCanUpdate" :text="$strings.LabelEdit" direction="top">
|
||||
<ui-icon-btn icon="edit" class="mx-0.5" @click="editClick" />
|
||||
<ui-icon-btn icon="" outlined class="mx-0.5" @click="editClick" />
|
||||
</ui-tooltip>
|
||||
|
||||
<ui-tooltip v-if="!isPodcast && !isMusic" :text="userIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="top">
|
||||
<ui-tooltip v-if="!isPodcast" :text="userIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="top">
|
||||
<ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" class="mx-0.5" @click="toggleFinished" />
|
||||
</ui-tooltip>
|
||||
|
||||
|
|
@ -121,7 +116,7 @@
|
|||
<ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" :menu-width="148" @action="contextMenuAction">
|
||||
<template #default="{ showMenu, clickShowMenu, disabled }">
|
||||
<button type="button" :disabled="disabled" class="mx-0.5 icon-btn bg-primary border border-gray-600 w-9 h-9 rounded-md flex items-center justify-center relative" aria-haspopup="listbox" :aria-expanded="showMenu" @click.stop.prevent="clickShowMenu">
|
||||
<span class="material-icons">more_horiz</span>
|
||||
<span class="material-symbols text-2xl"></span>
|
||||
</button>
|
||||
</template>
|
||||
</ui-context-menu-dropdown>
|
||||
|
|
@ -129,9 +124,7 @@
|
|||
|
||||
<div class="my-4 w-full">
|
||||
<p ref="description" id="item-description" dir="auto" class="text-base text-gray-100 whitespace-pre-line mb-1" :class="{ 'show-full': showFullDescription }">{{ description }}</p>
|
||||
<button v-if="isDescriptionClamped" class="py-0.5 flex items-center text-slate-300 hover:text-white" @click="showFullDescription = !showFullDescription">
|
||||
{{ showFullDescription ? $strings.ButtonReadLess : $strings.ButtonReadMore }} <span class="material-icons text-xl pl-1">{{ showFullDescription ? 'expand_less' : 'expand_more' }}</span>
|
||||
</button>
|
||||
<button v-if="isDescriptionClamped" class="py-0.5 flex items-center text-slate-300 hover:text-white" @click="showFullDescription = !showFullDescription">{{ showFullDescription ? $strings.ButtonReadLess : $strings.ButtonReadMore }} <span class="material-symbols text-xl pl-1" v-html="showFullDescription ? 'expand_less' : ''" /></button>
|
||||
</div>
|
||||
|
||||
<tables-chapters-table v-if="chapters.length" :library-item="libraryItem" class="mt-6" />
|
||||
|
|
@ -160,7 +153,7 @@ export default {
|
|||
}
|
||||
|
||||
// Include episode downloads for podcasts
|
||||
var item = await app.$axios.$get(`/api/items/${params.id}?expanded=1&include=downloads,rssfeed`).catch((error) => {
|
||||
var item = await app.$axios.$get(`/api/items/${params.id}?expanded=1&include=downloads,rssfeed,share`).catch((error) => {
|
||||
console.error('Failed', error)
|
||||
return false
|
||||
})
|
||||
|
|
@ -168,9 +161,13 @@ export default {
|
|||
console.error('No item...', params.id)
|
||||
return redirect('/')
|
||||
}
|
||||
if (store.state.libraries.currentLibraryId !== item.libraryId || !store.state.libraries.filterData) {
|
||||
await store.dispatch('libraries/fetch', item.libraryId)
|
||||
}
|
||||
return {
|
||||
libraryItem: item,
|
||||
rssFeed: item.rssFeed || null
|
||||
rssFeed: item.rssFeed || null,
|
||||
mediaItemShare: item.mediaItemShare || null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
@ -218,12 +215,6 @@ export default {
|
|||
isPodcast() {
|
||||
return this.libraryItem.mediaType === 'podcast'
|
||||
},
|
||||
isVideo() {
|
||||
return this.libraryItem.mediaType === 'video'
|
||||
},
|
||||
isMusic() {
|
||||
return this.libraryItem.mediaType === 'music'
|
||||
},
|
||||
isMissing() {
|
||||
return this.libraryItem.isMissing
|
||||
},
|
||||
|
|
@ -238,8 +229,6 @@ export default {
|
|||
},
|
||||
showPlayButton() {
|
||||
if (this.isMissing || this.isInvalid) return false
|
||||
if (this.isMusic) return !!this.audioFile
|
||||
if (this.isVideo) return !!this.videoFile
|
||||
if (this.isPodcast) return this.podcastEpisodes.length
|
||||
return this.tracks.length
|
||||
},
|
||||
|
|
@ -290,9 +279,6 @@ export default {
|
|||
authors() {
|
||||
return this.mediaMetadata.authors || []
|
||||
},
|
||||
musicArtists() {
|
||||
return this.mediaMetadata.artists || []
|
||||
},
|
||||
series() {
|
||||
return this.mediaMetadata.series || []
|
||||
},
|
||||
|
|
@ -307,7 +293,7 @@ export default {
|
|||
})
|
||||
},
|
||||
duration() {
|
||||
if (!this.tracks.length && !this.audioFile) return 0
|
||||
if (!this.tracks.length) return 0
|
||||
return this.media.duration
|
||||
},
|
||||
libraryFiles() {
|
||||
|
|
@ -319,18 +305,10 @@ export default {
|
|||
ebookFile() {
|
||||
return this.media.ebookFile
|
||||
},
|
||||
videoFile() {
|
||||
return this.media.videoFile
|
||||
},
|
||||
audioFile() {
|
||||
// Music track
|
||||
return this.media.audioFile
|
||||
},
|
||||
description() {
|
||||
return this.mediaMetadata.description || ''
|
||||
},
|
||||
userMediaProgress() {
|
||||
if (this.isMusic) return null
|
||||
return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
||||
},
|
||||
userIsFinished() {
|
||||
|
|
@ -437,6 +415,13 @@ export default {
|
|||
})
|
||||
}
|
||||
|
||||
if (this.userIsAdminOrUp && !this.isPodcast && this.tracks.length) {
|
||||
items.push({
|
||||
text: this.$strings.LabelShare,
|
||||
action: 'share'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.userCanDelete) {
|
||||
items.push({
|
||||
text: this.$strings.ButtonDelete,
|
||||
|
|
@ -475,23 +460,23 @@ export default {
|
|||
this.$axios
|
||||
.$get(`/api/podcasts/${this.libraryItemId}/clear-queue`)
|
||||
.then(() => {
|
||||
this.$toast.success('Episode download queue cleared')
|
||||
this.$toast.success(this.$strings.ToastEpisodeDownloadQueueClearSuccess)
|
||||
this.episodeDownloadQueued = []
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to clear queue', error)
|
||||
this.$toast.error('Failed to clear queue')
|
||||
this.$toast.error(this.$strings.ToastEpisodeDownloadQueueClearFailed)
|
||||
})
|
||||
}
|
||||
},
|
||||
async findEpisodesClick() {
|
||||
if (!this.mediaMetadata.feedUrl) {
|
||||
return this.$toast.error('Podcast does not have an RSS Feed')
|
||||
return this.$toast.error(this.$strings.ToastNoRSSFeed)
|
||||
}
|
||||
this.fetchingRSSFeed = true
|
||||
var payload = await this.$axios.get(`/api/podcasts/${this.libraryItemId}/feed`).catch((error) => {
|
||||
console.error('Failed to get feed', error)
|
||||
this.$toast.error('Failed to get podcast feed')
|
||||
this.$toast.error(this.$strings.ToastPodcastGetFeedFailed)
|
||||
return null
|
||||
})
|
||||
this.fetchingRSSFeed = false
|
||||
|
|
@ -500,7 +485,7 @@ export default {
|
|||
console.log('Podcast feed', payload.data)
|
||||
const podcastfeed = payload.data.podcast
|
||||
if (!podcastfeed.episodes || !podcastfeed.episodes.length) {
|
||||
this.$toast.info('No episodes found in RSS feed')
|
||||
this.$toast.info(this.$strings.ToastPodcastNoEpisodesInFeed)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -569,7 +554,7 @@ export default {
|
|||
episodeId: episode.id,
|
||||
title: episode.title,
|
||||
subtitle: this.title,
|
||||
caption: episode.publishedAt ? `Published ${this.$formatDate(episode.publishedAt, this.dateFormat)}` : 'Unknown publish date',
|
||||
caption: episode.publishedAt ? this.$getString('LabelPublishedDate', [this.$formatDate(episode.publishedAt, this.dateFormat)]) : this.$strings.LabelUnknownPublishDate,
|
||||
duration: episode.audioFile.duration || null,
|
||||
coverPath: this.libraryItem.media.coverPath || null
|
||||
})
|
||||
|
|
@ -613,13 +598,12 @@ export default {
|
|||
},
|
||||
clearProgressClick() {
|
||||
if (!this.userMediaProgress) return
|
||||
if (confirm(`Are you sure you want to reset your progress?`)) {
|
||||
if (confirm(this.$strings.MessageConfirmResetProgress)) {
|
||||
this.resettingProgress = true
|
||||
this.$axios
|
||||
.$delete(`/api/me/progress/${this.userMediaProgress.id}`)
|
||||
.then(() => {
|
||||
console.log('Progress reset complete')
|
||||
this.$toast.success(`Your progress was reset`)
|
||||
this.resettingProgress = false
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
@ -666,6 +650,16 @@ export default {
|
|||
this.rssFeed = null
|
||||
}
|
||||
},
|
||||
shareOpen(mediaItemShare) {
|
||||
if (mediaItemShare.mediaItemId === this.media.id) {
|
||||
this.mediaItemShare = mediaItemShare
|
||||
}
|
||||
},
|
||||
shareClosed(mediaItemShare) {
|
||||
if (mediaItemShare.mediaItemId === this.media.id) {
|
||||
this.mediaItemShare = null
|
||||
}
|
||||
},
|
||||
queueBtnClick() {
|
||||
if (this.isQueued) {
|
||||
// Remove from queue
|
||||
|
|
@ -703,12 +697,12 @@ export default {
|
|||
this.$axios
|
||||
.$delete(`/api/items/${this.libraryItemId}?hard=${hardDelete ? 1 : 0}`)
|
||||
.then(() => {
|
||||
this.$toast.success('Item deleted')
|
||||
this.$toast.success(this.$strings.ToastItemDeletedSuccess)
|
||||
this.$router.replace(`/library/${this.libraryId}`)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to delete item', error)
|
||||
this.$toast.error('Failed to delete item')
|
||||
this.$toast.error(this.$strings.ToastItemDeleteFailed)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
|
@ -761,6 +755,9 @@ export default {
|
|||
this.deleteLibraryItem()
|
||||
} else if (action === 'sendToDevice') {
|
||||
this.sendToDevice(data)
|
||||
} else if (action === 'share') {
|
||||
this.$store.commit('setSelectedLibraryItem', this.libraryItem)
|
||||
this.$store.commit('globals/setShareModal', this.mediaItemShare)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -770,14 +767,12 @@ export default {
|
|||
this.episodeDownloadsQueued = this.libraryItem.episodeDownloadsQueued || []
|
||||
this.episodesDownloading = this.libraryItem.episodesDownloading || []
|
||||
|
||||
// use this items library id as the current
|
||||
if (this.libraryId) {
|
||||
this.$store.commit('libraries/setCurrentLibrary', this.libraryId)
|
||||
}
|
||||
this.$eventBus.$on(`${this.libraryItem.id}_updated`, this.libraryItemUpdated)
|
||||
this.$root.socket.on('item_updated', this.libraryItemUpdated)
|
||||
this.$root.socket.on('rss_feed_open', this.rssFeedOpen)
|
||||
this.$root.socket.on('rss_feed_closed', this.rssFeedClosed)
|
||||
this.$root.socket.on('share_open', this.shareOpen)
|
||||
this.$root.socket.on('share_closed', this.shareClosed)
|
||||
this.$root.socket.on('episode_download_queued', this.episodeDownloadQueued)
|
||||
this.$root.socket.on('episode_download_started', this.episodeDownloadStarted)
|
||||
this.$root.socket.on('episode_download_finished', this.episodeDownloadFinished)
|
||||
|
|
@ -787,6 +782,8 @@ export default {
|
|||
this.$root.socket.off('item_updated', this.libraryItemUpdated)
|
||||
this.$root.socket.off('rss_feed_open', this.rssFeedOpen)
|
||||
this.$root.socket.off('rss_feed_closed', this.rssFeedClosed)
|
||||
this.$root.socket.off('share_open', this.shareOpen)
|
||||
this.$root.socket.off('share_closed', this.shareClosed)
|
||||
this.$root.socket.off('episode_download_queued', this.episodeDownloadQueued)
|
||||
this.$root.socket.off('episode_download_started', this.episodeDownloadStarted)
|
||||
this.$root.socket.off('episode_download_finished', this.episodeDownloadFinished)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue