mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-03-06 07:59:43 +00:00
Merge remote-tracking branch 'remotes/upstream/master'
# Conflicts: # client/pages/item/_id/index.vue
This commit is contained in:
commit
6643b371cc
212 changed files with 5454 additions and 8175 deletions
|
|
@ -117,10 +117,10 @@ export default {
|
|||
},
|
||||
submitChangePassword() {
|
||||
if (this.newPassword !== this.confirmPassword) {
|
||||
return this.$toast.error('New password and confirm password do not match')
|
||||
return this.$toast.error(this.$strings.ToastUserPasswordMismatch)
|
||||
}
|
||||
if (this.password === this.newPassword) {
|
||||
return this.$toast.error('Password and New Password cannot be the same')
|
||||
return this.$toast.error(this.$strings.ToastUserPasswordMustChange)
|
||||
}
|
||||
this.changingPassword = true
|
||||
this.$axios
|
||||
|
|
@ -130,16 +130,16 @@ export default {
|
|||
})
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.$toast.success('Password Changed Successfully')
|
||||
this.$toast.success(this.$strings.ToastUserPasswordChangeSuccess)
|
||||
this.resetForm()
|
||||
} else {
|
||||
this.$toast.error(res.error || 'Unknown Error')
|
||||
this.$toast.error(res.error || this.$strings.ToastUnknownError)
|
||||
}
|
||||
this.changingPassword = false
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
this.$toast.error('Api call failed')
|
||||
this.$toast.error(this.$strings.ToastUnknownError)
|
||||
this.changingPassword = false
|
||||
})
|
||||
}
|
||||
|
|
@ -148,4 +148,4 @@ export default {
|
|||
this.selectedLanguage = this.$languageCodes.current
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@
|
|||
<div class="flex items-center">
|
||||
<ui-tooltip :text="$strings.MessageRemoveChapter" direction="bottom">
|
||||
<button v-if="newChapters.length > 1" class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-error transform hover:scale-110 duration-150" @click="removeChapter(chapter)">
|
||||
<span class="material-symbols-outlined text-base">remove</span>
|
||||
<span class="material-symbols text-base">remove</span>
|
||||
</button>
|
||||
</ui-tooltip>
|
||||
|
||||
|
|
@ -84,14 +84,14 @@
|
|||
<ui-tooltip :text="selectedChapterId === chapter.id && isPlayingChapter ? $strings.MessagePauseChapter : $strings.MessagePlayChapter" direction="bottom">
|
||||
<button class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-white transform hover:scale-110 duration-150" @click="playChapter(chapter)">
|
||||
<widgets-loading-spinner v-if="selectedChapterId === chapter.id && isLoadingChapter" />
|
||||
<span v-else-if="selectedChapterId === chapter.id && isPlayingChapter" class="material-symbols-outlined text-base">pause</span>
|
||||
<span v-else class="material-symbols-outlined text-base">play_arrow</span>
|
||||
<span v-else-if="selectedChapterId === chapter.id && isPlayingChapter" class="material-symbols text-base">pause</span>
|
||||
<span v-else class="material-symbols text-base">play_arrow</span>
|
||||
</button>
|
||||
</ui-tooltip>
|
||||
|
||||
<ui-tooltip v-if="chapter.error" :text="chapter.error" direction="left">
|
||||
<button class="w-7 h-7 rounded-full flex items-center justify-center text-error">
|
||||
<span class="material-symbols-outlined text-lg">error_outline</span>
|
||||
<span class="material-symbols text-lg">error_outline</span>
|
||||
</button>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
|
|
@ -106,7 +106,7 @@
|
|||
<div class="flex-grow" />
|
||||
<ui-btn small @click="setChaptersFromTracks">{{ $strings.ButtonSetChaptersFromTracks }}</ui-btn>
|
||||
<ui-tooltip :text="$strings.MessageSetChaptersFromTracksDescription" direction="top" class="flex items-center mx-1 cursor-default">
|
||||
<span class="material-symbols-outlined text-xl text-gray-200">info</span>
|
||||
<span class="material-symbols text-xl text-gray-200">info</span>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
<div class="flex text-xs uppercase text-gray-300 font-semibold mb-2">
|
||||
|
|
@ -189,7 +189,7 @@
|
|||
<div class="flex items-center pt-2">
|
||||
<ui-btn small color="primary" class="mr-1" @click="applyChapterNamesOnly">{{ $strings.ButtonMapChapterTitles }}</ui-btn>
|
||||
<ui-tooltip :text="$strings.MessageMapChapterTitles" direction="top" class="flex items-center">
|
||||
<span class="material-symbols-outlined text-xl text-gray-200">info</span>
|
||||
<span class="material-symbols text-xl text-gray-200">info</span>
|
||||
</ui-tooltip>
|
||||
<div class="flex-grow" />
|
||||
<ui-btn small color="success" @click="applyChapterData">{{ $strings.ButtonApplyChapters }}</ui-btn>
|
||||
|
|
@ -560,7 +560,7 @@ export default {
|
|||
.catch((error) => {
|
||||
this.findingChapters = false
|
||||
console.error('Failed to get chapter data', error)
|
||||
this.$toast.error('Failed to find chapters')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
this.showFindChaptersModal = false
|
||||
})
|
||||
},
|
||||
|
|
@ -611,7 +611,7 @@ export default {
|
|||
.$post(`/api/items/${this.libraryItem.id}/chapters`, payload)
|
||||
.then((data) => {
|
||||
if (data.updated) {
|
||||
this.$toast.success('Chapters removed')
|
||||
this.$toast.success(this.$strings.ToastChaptersRemoved)
|
||||
if (this.previousRoute) {
|
||||
this.$router.push(this.previousRoute)
|
||||
} else {
|
||||
|
|
@ -623,7 +623,7 @@ export default {
|
|||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to remove chapters', error)
|
||||
this.$toast.error('Failed to remove chapters')
|
||||
this.$toast.error(this.$strings.ToastRemoveFailed)
|
||||
})
|
||||
.finally(() => {
|
||||
this.saving = false
|
||||
|
|
|
|||
|
|
@ -331,11 +331,11 @@ export default {
|
|||
this.$axios
|
||||
.$delete(`/api/tools/item/${this.libraryItemId}/encode-m4b`)
|
||||
.then(() => {
|
||||
this.$toast.success('Encode canceled')
|
||||
this.$toast.success(this.$strings.ToastEncodeCancelSucces)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to cancel encode', error)
|
||||
this.$toast.error('Failed to cancel encode')
|
||||
this.$toast.error(this.$strings.ToastEncodeCancelFailed)
|
||||
})
|
||||
.finally(() => {
|
||||
this.isCancelingEncode = false
|
||||
|
|
|
|||
|
|
@ -97,8 +97,8 @@
|
|||
<div class="flex justify-center flex-wrap">
|
||||
<template v-for="libraryItem in libraryItemCopies">
|
||||
<div :key="libraryItem.id" class="w-full max-w-3xl border border-black-300 p-6 -ml-px -mt-px">
|
||||
<widgets-book-details-edit v-if="libraryItem.mediaType === 'book'" :ref="`itemForm-${libraryItem.id}`" :library-item="libraryItem" />
|
||||
<widgets-podcast-details-edit v-else :ref="`itemForm-${libraryItem.id}`" :library-item="libraryItem" />
|
||||
<widgets-book-details-edit v-if="libraryItem.mediaType === 'book'" :ref="`itemForm-${libraryItem.id}`" :library-item="libraryItem" @change="handleItemChange" />
|
||||
<widgets-podcast-details-edit v-else :ref="`itemForm-${libraryItem.id}`" :library-item="libraryItem" @change="handleItemChange" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
|
@ -108,7 +108,7 @@
|
|||
|
||||
<div :class="isScrollable ? 'fixed left-0 box-shadow-lg-up bg-primary' : ''" class="w-full h-20 px-4 flex items-center border-t border-bg z-40" :style="{ bottom: streamLibraryItem ? '165px' : '0px' }">
|
||||
<div class="flex-grow" />
|
||||
<ui-btn color="success" :padding-x="8" class="text-lg" :loading="isProcessing" @click.prevent="saveClick">{{ $strings.ButtonSave }}</ui-btn>
|
||||
<ui-btn color="success" :padding-x="8" class="text-lg" :loading="isProcessing" :disabled="!hasChanges" @click.prevent="saveClick">{{ $strings.ButtonSave }}</ui-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -170,7 +170,8 @@ export default {
|
|||
abridged: false
|
||||
},
|
||||
appendableKeys: ['authors', 'genres', 'tags', 'narrators', 'series'],
|
||||
openMapOptions: false
|
||||
openMapOptions: false,
|
||||
itemsWithChanges: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -221,9 +222,19 @@ export default {
|
|||
},
|
||||
hasSelectedBatchUsage() {
|
||||
return Object.values(this.selectedBatchUsage).some((b) => !!b)
|
||||
},
|
||||
hasChanges() {
|
||||
return this.itemsWithChanges.length > 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleItemChange(itemChange) {
|
||||
if (!itemChange.hasChanges) {
|
||||
this.itemsWithChanges = this.itemsWithChanges.filter((id) => id !== itemChange.libraryItemId)
|
||||
} else if (!this.itemsWithChanges.includes(itemChange.libraryItemId)) {
|
||||
this.itemsWithChanges.push(itemChange.libraryItemId)
|
||||
}
|
||||
},
|
||||
blurBatchForm() {
|
||||
if (this.$refs.seriesSelect && this.$refs.seriesSelect.isFocused) {
|
||||
this.$refs.seriesSelect.forceBlur()
|
||||
|
|
@ -283,38 +294,10 @@ export default {
|
|||
removedSeriesItem(item) {},
|
||||
newNarratorItem(item) {},
|
||||
removedNarratorItem(item) {},
|
||||
newTagItem(item) {
|
||||
// if (item && !this.newTagItems.includes(item)) {
|
||||
// this.newTagItems.push(item)
|
||||
// }
|
||||
},
|
||||
removedTagItem(item) {
|
||||
// If newly added, remove if not used on any other items
|
||||
// if (item && this.newTagItems.includes(item)) {
|
||||
// var usedByOtherAb = this.libraryItemCopies.find((ab) => {
|
||||
// return ab.tags && ab.tags.includes(item)
|
||||
// })
|
||||
// if (!usedByOtherAb) {
|
||||
// this.newTagItems = this.newTagItems.filter((t) => t !== item)
|
||||
// }
|
||||
// }
|
||||
},
|
||||
newGenreItem(item) {
|
||||
// if (item && !this.newGenreItems.includes(item)) {
|
||||
// this.newGenreItems.push(item)
|
||||
// }
|
||||
},
|
||||
removedGenreItem(item) {
|
||||
// If newly added, remove if not used on any other items
|
||||
// if (item && this.newGenreItems.includes(item)) {
|
||||
// var usedByOtherAb = this.libraryItemCopies.find((ab) => {
|
||||
// return ab.book.genres && ab.book.genres.includes(item)
|
||||
// })
|
||||
// if (!usedByOtherAb) {
|
||||
// this.newGenreItems = this.newGenreItems.filter((t) => t !== item)
|
||||
// }
|
||||
// }
|
||||
},
|
||||
newTagItem(item) {},
|
||||
removedTagItem(item) {},
|
||||
newGenreItem(item) {},
|
||||
removedGenreItem(item) {},
|
||||
init() {
|
||||
// TODO: Better deep cloning of library items
|
||||
this.libraryItemCopies = this.libraryItems.map((li) => {
|
||||
|
|
@ -366,7 +349,7 @@ export default {
|
|||
}
|
||||
}
|
||||
if (!updates.length) {
|
||||
return this.$toast.warning('No updates were made')
|
||||
return this.$toast.warning(this.$strings.ToastNoUpdatesNecessary)
|
||||
}
|
||||
|
||||
console.log('Pushing updates', updates)
|
||||
|
|
@ -376,6 +359,7 @@ export default {
|
|||
.then((data) => {
|
||||
this.isProcessing = false
|
||||
if (data.updates) {
|
||||
this.itemsWithChanges = []
|
||||
this.$toast.success(`Successfully updated ${data.updates} items`)
|
||||
this.$router.replace(`/library/${this.currentLibraryId}/bookshelf`)
|
||||
} else {
|
||||
|
|
@ -387,10 +371,28 @@ export default {
|
|||
this.$toast.error('Failed to batch update')
|
||||
this.isProcessing = false
|
||||
})
|
||||
},
|
||||
beforeUnload(e) {
|
||||
if (!e || !this.hasChanges) return
|
||||
e.preventDefault()
|
||||
e.returnValue = ''
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
if (this.hasChanges) {
|
||||
next(false)
|
||||
window.location = to.path
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
|
||||
window.addEventListener('beforeunload', this.beforeUnload)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('beforeunload', this.beforeUnload)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -406,4 +408,4 @@ export default {
|
|||
transform: translateY(-100%);
|
||||
transition: all 150ms ease-in 0s;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<app-settings-content :header-text="$strings.HeaderBackups" :description="$strings.MessageBackupsDescription">
|
||||
<div v-if="backupLocation" class="mb-4 max-w-full overflow-hidden">
|
||||
<div class="flex items-center mb-0.5">
|
||||
<span class="material-symbols-outlined text-2xl text-black-50 mr-2">folder</span>
|
||||
<span class="material-symbols text-2xl text-black-50 mr-2">folder</span>
|
||||
<span class="text-white text-opacity-60 uppercase text-sm whitespace-nowrap">{{ $strings.LabelBackupLocation }}:</span>
|
||||
</div>
|
||||
<div v-if="!showEditBackupPath" class="inline-flex items-center w-full overflow-hidden">
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
|
||||
<div v-if="enableBackups" class="mb-6">
|
||||
<div class="flex items-center pl-0 sm:pl-6 mb-2">
|
||||
<span class="material-symbols-outlined text-xl sm:text-2xl text-black-50 mr-2">schedule</span>
|
||||
<span class="material-symbols text-xl sm:text-2xl text-black-50 mr-2">schedule</span>
|
||||
<div class="w-32 min-w-32 sm:w-40 sm:min-w-40">
|
||||
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.HeaderSchedule }}:</span>
|
||||
</div>
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
</div>
|
||||
|
||||
<div v-if="nextBackupDate" class="flex items-center pl-0 sm:pl-6 py-0.5">
|
||||
<span class="material-symbols-outlined text-xl sm:text-2xl text-black-50 mr-2">event</span>
|
||||
<span class="material-symbols text-xl sm:text-2xl text-black-50 mr-2">event</span>
|
||||
<div class="w-32 min-w-32 sm:w-40 sm:min-w-40">
|
||||
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelNextBackupDate }}:</span>
|
||||
</div>
|
||||
|
|
@ -162,7 +162,7 @@ export default {
|
|||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to save backup path', error)
|
||||
const errorMsg = error.response?.data || 'Failed to save backup path'
|
||||
const errorMsg = error.response?.data || this.$strings.ToastBackupPathUpdateFailed
|
||||
this.$toast.error(errorMsg)
|
||||
})
|
||||
.finally(() => {
|
||||
|
|
@ -171,11 +171,11 @@ export default {
|
|||
},
|
||||
updateBackupsSettings() {
|
||||
if (isNaN(this.maxBackupSize) || this.maxBackupSize < 0) {
|
||||
this.$toast.error('Invalid maximum backup size')
|
||||
this.$toast.error(this.$strings.ToastBackupInvalidMaxSize)
|
||||
return
|
||||
}
|
||||
if (isNaN(this.backupsToKeep) || this.backupsToKeep <= 0 || this.backupsToKeep > 99) {
|
||||
this.$toast.error('Invalid number of backups to keep')
|
||||
this.$toast.error(this.$strings.ToastBackupInvalidMaxKeep)
|
||||
return
|
||||
}
|
||||
const updatePayload = {
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@
|
|||
</tr>
|
||||
</table>
|
||||
<div v-else-if="!loading" class="text-center py-4">
|
||||
<p class="text-lg text-gray-100">No Devices</p>
|
||||
<p class="text-lg text-gray-100">{{ $strings.MessageNoDevices }}</p>
|
||||
</div>
|
||||
</app-settings-content>
|
||||
|
||||
|
|
@ -199,7 +199,7 @@ export default {
|
|||
},
|
||||
deleteDeviceClick(device) {
|
||||
const payload = {
|
||||
message: `Are you sure you want to delete e-reader device "${device.name}"?`,
|
||||
message: this.$getString('MessageConfirmDeleteDevice', [device.name]),
|
||||
callback: (confirmed) => {
|
||||
if (confirmed) {
|
||||
this.deleteDevice(device)
|
||||
|
|
@ -218,11 +218,10 @@ export default {
|
|||
.$post(`/api/emails/ereader-devices`, payload)
|
||||
.then((data) => {
|
||||
this.ereaderDevicesUpdated(data.ereaderDevices)
|
||||
this.$toast.success('Device deleted')
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to delete device', error)
|
||||
this.$toast.error('Failed to delete device')
|
||||
this.$toast.error(this.$strings.ToastRemoveFailed)
|
||||
})
|
||||
.finally(() => {
|
||||
this.deletingDeviceName = null
|
||||
|
|
@ -246,11 +245,11 @@ export default {
|
|||
this.$axios
|
||||
.$post('/api/emails/test')
|
||||
.then(() => {
|
||||
this.$toast.success('Test Email Sent')
|
||||
this.$toast.success(this.$strings.ToastDeviceTestEmailSuccess)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to send test email', error)
|
||||
const errorMsg = error.response.data || 'Failed to send test email'
|
||||
const errorMsg = error.response.data || this.$strings.ToastDeviceTestEmailFailed
|
||||
this.$toast.error(errorMsg)
|
||||
})
|
||||
.finally(() => {
|
||||
|
|
@ -289,11 +288,11 @@ export default {
|
|||
this.newSettings = {
|
||||
...data.settings
|
||||
}
|
||||
this.$toast.success('Email settings updated')
|
||||
this.$toast.success(this.$strings.ToastEmailSettingsUpdateSuccess)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to update email settings', error)
|
||||
this.$toast.error('Failed to update email settings')
|
||||
this.$toast.error(this.$strings.ToastEmailSettingsUpdateFailed)
|
||||
})
|
||||
.finally(() => {
|
||||
this.savingSettings = false
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ export default {
|
|||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to rename genre', error)
|
||||
this.$toast.error('Failed to rename genre')
|
||||
this.$toast.error(this.$strings.ToastRenameFailed)
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
|
|
@ -147,7 +147,7 @@ export default {
|
|||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to remove genre', error)
|
||||
this.$toast.error('Failed to remove genre')
|
||||
this.$toast.error(this.$strings.ToastRemoveFailed)
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ export default {
|
|||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to rename tag', error)
|
||||
this.$toast.error('Failed to rename tag')
|
||||
this.$toast.error(this.$strings.ToastRenameFailed)
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
|
|
@ -143,7 +143,7 @@ export default {
|
|||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to remove tag', error)
|
||||
this.$toast.error('Failed to remove tag')
|
||||
this.$toast.error(this.$strings.ToastRemoveFailed)
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
|
|
|
|||
|
|
@ -105,12 +105,12 @@ export default {
|
|||
}
|
||||
|
||||
if (isNaN(this.maxNotificationQueue) || this.maxNotificationQueue <= 0) {
|
||||
this.$toast.error('Max notification queue must be >= 0')
|
||||
this.$toast.error(this.$strings.ToastNotificationQueueMaximum)
|
||||
return false
|
||||
}
|
||||
|
||||
if (isNaN(this.maxFailedAttempts) || this.maxFailedAttempts <= 0) {
|
||||
this.$toast.error('Max failed attempts must be >= 0')
|
||||
this.$toast.error(this.$strings.ToastNotificationFailedMaximum)
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -128,11 +128,11 @@ export default {
|
|||
this.$axios
|
||||
.$patch('/api/notifications', updatePayload)
|
||||
.then(() => {
|
||||
this.$toast.success('Notification settings updated')
|
||||
this.$toast.success(this.$strings.ToastNotificationSettingsUpdateSuccess)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to update notification settings', error)
|
||||
this.$toast.error('Failed to update notification settings')
|
||||
this.$toast.error(this.$strings.ToastNotificationSettingsUpdateFailed)
|
||||
})
|
||||
.finally(() => {
|
||||
this.savingSettings = false
|
||||
|
|
|
|||
|
|
@ -290,7 +290,6 @@ export default {
|
|||
this.$axios
|
||||
.$post(`/api/sessions/batch/delete`, payload)
|
||||
.then(() => {
|
||||
this.$toast.success('Sessions removed')
|
||||
if (isAllSessions) {
|
||||
// If all sessions were removed from the current page then go to the previous page
|
||||
if (this.currentPage > 0) {
|
||||
|
|
@ -303,7 +302,7 @@ export default {
|
|||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
const errorMsg = error.response?.data || 'Failed to remove sessions'
|
||||
const errorMsg = error.response?.data || this.$strings.ToastRemoveFailed
|
||||
this.$toast.error(errorMsg)
|
||||
})
|
||||
.finally(() => {
|
||||
|
|
@ -358,12 +357,13 @@ export default {
|
|||
})
|
||||
|
||||
if (!libraryItem) {
|
||||
this.$toast.error('Failed to get library item')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
this.processingGoToTimestamp = false
|
||||
return
|
||||
}
|
||||
if (session.episodeId && !libraryItem.media.episodes.some((ep) => ep.id === session.episodeId)) {
|
||||
this.$toast.error('Failed to get podcast episode')
|
||||
console.error('Episode not found in library item', session.episodeId, libraryItem.media.episodes)
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
this.processingGoToTimestamp = false
|
||||
return
|
||||
}
|
||||
|
|
@ -377,7 +377,7 @@ export default {
|
|||
episodeId: episode.id,
|
||||
title: episode.title,
|
||||
subtitle: libraryItem.media.metadata.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: libraryItem.media.coverPath || null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
<div class="flex p-2">
|
||||
<div class="hidden sm:block">
|
||||
<span class="hidden sm:block material-symbols-outlined text-5xl lg:text-6xl">event</span>
|
||||
<span class="hidden sm:block material-symbols text-5xl lg:text-6xl">event</span>
|
||||
</div>
|
||||
<div class="px-1">
|
||||
<p class="text-4xl md:text-5xl font-bold">{{ $formatNumber(totalDaysListened) }}</p>
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
<div class="flex p-2">
|
||||
<div class="hidden sm:block">
|
||||
<span class="material-symbols-outlined text-5xl lg:text-6xl">watch_later</span>
|
||||
<span class="material-symbols text-5xl lg:text-6xl">watch_later</span>
|
||||
</div>
|
||||
<div class="px-1">
|
||||
<p class="text-4xl md:text-5xl font-bold">{{ $formatNumber(totalMinutesListening) }}</p>
|
||||
|
|
|
|||
|
|
@ -127,12 +127,13 @@ export default {
|
|||
})
|
||||
|
||||
if (!libraryItem) {
|
||||
this.$toast.error('Failed to get library item')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
this.processingGoToTimestamp = false
|
||||
return
|
||||
}
|
||||
if (session.episodeId && !libraryItem.media.episodes.some((ep) => ep.id === session.episodeId)) {
|
||||
this.$toast.error('Failed to get podcast episode')
|
||||
console.error('Episode not found in library item', session.episodeId, libraryItem.media.episodes)
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
this.processingGoToTimestamp = false
|
||||
return
|
||||
}
|
||||
|
|
@ -146,7 +147,7 @@ export default {
|
|||
episodeId: episode.id,
|
||||
title: episode.title,
|
||||
subtitle: libraryItem.media.metadata.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: libraryItem.media.coverPath || null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -80,14 +75,14 @@
|
|||
<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-symbols 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 aria-hidden="true" v-show="!isStreaming" class="material-symbols fill text-2xl -ml-2 pr-1 text-white">play_arrow</span>
|
||||
<span aria-hidden="true" v-show="!isStreaming" class="material-symbols fill text-2xl -ml-2 pr-1 text-white"></span>
|
||||
{{ isStreaming ? $strings.ButtonPlaying : $strings.ButtonPlay }}
|
||||
</ui-btn>
|
||||
|
||||
|
|
@ -106,10 +101,10 @@
|
|||
</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-symbols text-2xl">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-symbols 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" />
|
||||
|
|
@ -222,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
|
||||
},
|
||||
|
|
@ -242,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
|
||||
},
|
||||
|
|
@ -294,9 +279,6 @@ export default {
|
|||
authors() {
|
||||
return this.mediaMetadata.authors || []
|
||||
},
|
||||
musicArtists() {
|
||||
return this.mediaMetadata.artists || []
|
||||
},
|
||||
series() {
|
||||
return this.mediaMetadata.series || []
|
||||
},
|
||||
|
|
@ -311,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() {
|
||||
|
|
@ -323,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() {
|
||||
|
|
@ -486,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.$post(`/api/podcasts/feed`, { rssFeed: this.mediaMetadata.feedUrl }).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
|
||||
|
|
@ -511,7 +485,7 @@ export default {
|
|||
console.log('Podcast feed', payload)
|
||||
const podcastfeed = payload.podcast
|
||||
if (!podcastfeed.episodes || !podcastfeed.episodes.length) {
|
||||
this.$toast.info('No episodes found in RSS feed')
|
||||
this.$toast.info(this.$strings.ToastPodcastNoEpisodesInFeed)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -580,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
|
||||
})
|
||||
|
|
@ -624,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) => {
|
||||
|
|
@ -724,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)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ export default {
|
|||
const bDesc = this.authorSortDesc ? -1 : 1
|
||||
return this.authors.sort((a, b) => {
|
||||
if (typeof a[sortProp] === 'number' && typeof b[sortProp] === 'number') {
|
||||
// Fallback to name sort if equal
|
||||
if (a[sortProp] === b[sortProp]) return a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }) * bDesc
|
||||
return a[sortProp] > b[sortProp] ? bDesc : -bDesc
|
||||
}
|
||||
return a[sortProp]?.localeCompare(b[sortProp], undefined, { sensitivity: 'base' }) * bDesc
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ export default {
|
|||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to remove narrator', error)
|
||||
this.$toast.error('Failed to remove narrator')
|
||||
this.$toast.error(this.$strings.ToastRemoveFailed)
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
|
@ -158,4 +158,4 @@ export default {
|
|||
},
|
||||
beforeDestroy() {}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ export default {
|
|||
this.processing = true
|
||||
const queuePayload = await this.$axios.$get(`/api/libraries/${this.libraryId}/episode-downloads`).catch((error) => {
|
||||
console.error('Failed to get download queue', error)
|
||||
this.$toast.error('Failed to get download queue')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
return null
|
||||
})
|
||||
this.processing = false
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
<p dir="auto" class="text-sm text-gray-200 mb-4 line-clamp-4" v-html="episode.subtitle || episode.description" />
|
||||
|
||||
<div class="flex items-center">
|
||||
<button class="h-8 px-4 border border-white border-opacity-20 hover:bg-white hover:bg-opacity-10 rounded-full flex items-center justify-center cursor-pointer focus:outline-none" :class="episode.progress && episode.progress.isFinished ? 'text-white text-opacity-40' : ''" @click.stop="playClick(episode)">
|
||||
<button class="h-8 px-4 border border-white border-opacity-20 hover:bg-white hover:bg-opacity-10 rounded-full flex items-center justify-center cursor-pointer focus:outline-none" :class="episode.progress?.isFinished ? 'text-white text-opacity-40' : ''" @click.stop="playClick(episode)">
|
||||
<span v-if="episodeIdStreaming === episode.id" class="material-symbols text-2xl" :class="streamIsPlaying ? '' : 'text-success'">{{ streamIsPlaying ? 'pause' : 'play_arrow' }}</span>
|
||||
<span v-else class="material-symbols fill text-2xl text-success">play_arrow</span>
|
||||
<p class="pl-2 pr-1 text-sm font-semibold">{{ getButtonText(episode) }}</p>
|
||||
|
|
@ -56,9 +56,10 @@
|
|||
|
||||
<ui-tooltip v-if="libraryItemIdStreaming && !isStreamingFromDifferentLibrary" :text="playerQueueEpisodeIdMap[episode.id] ? $strings.MessageRemoveFromPlayerQueue : $strings.MessageAddToPlayerQueue" :class="playerQueueEpisodeIdMap[episode.id] ? 'text-success' : ''" direction="top">
|
||||
<ui-icon-btn :icon="playerQueueEpisodeIdMap[episode.id] ? 'playlist_add_check' : 'playlist_play'" borderless @click="queueBtnClick(episode)" />
|
||||
<!-- <button class="h-8 w-8 flex justify-center items-center mx-2" :class="playerQueueEpisodeIdMap[episode.id] ? 'text-success' : ''" @click.stop="queueBtnClick(episode)">
|
||||
<span class="material-symbols-outlined text-2xl">{{ playerQueueEpisodeIdMap[episode.id] ? 'playlist_add_check' : 'playlist_add' }}</span>
|
||||
</button> -->
|
||||
</ui-tooltip>
|
||||
|
||||
<ui-tooltip :text="!!episode.progress?.isFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="top">
|
||||
<ui-read-icon-btn :disabled="episodesProcessingMap[episode.id]" :is-read="!!episode.progress?.isFinished" borderless class="mx-1 mt-0.5" @click="toggleEpisodeFinished(episode)" />
|
||||
</ui-tooltip>
|
||||
|
||||
<ui-tooltip :text="$strings.LabelYourPlaylists" direction="top">
|
||||
|
|
@ -98,6 +99,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
recentEpisodes: [],
|
||||
episodesProcessingMap: {},
|
||||
totalEpisodes: 0,
|
||||
currentPage: 0,
|
||||
processing: false,
|
||||
|
|
@ -143,6 +145,44 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
async toggleEpisodeFinished(episode, confirmed = false) {
|
||||
if (this.episodesProcessingMap[episode.id]) {
|
||||
console.warn('Episode is already processing')
|
||||
return
|
||||
}
|
||||
|
||||
const isFinished = !!episode.progress?.isFinished
|
||||
const itemProgressPercent = episode.progress?.progress || 0
|
||||
if (!isFinished && itemProgressPercent > 0 && !confirmed) {
|
||||
const payload = {
|
||||
message: `Are you sure you want to mark "${episode.title}" as finished?`,
|
||||
callback: (confirmed) => {
|
||||
if (confirmed) {
|
||||
this.toggleEpisodeFinished(episode, true)
|
||||
}
|
||||
},
|
||||
type: 'yesNo'
|
||||
}
|
||||
this.$store.commit('globals/setConfirmPrompt', payload)
|
||||
return
|
||||
}
|
||||
|
||||
const updatePayload = {
|
||||
isFinished: !isFinished
|
||||
}
|
||||
|
||||
this.$set(this.episodesProcessingMap, episode.id, true)
|
||||
|
||||
this.$axios
|
||||
.$patch(`/api/me/progress/${episode.libraryItemId}/${episode.id}`, updatePayload)
|
||||
.catch((error) => {
|
||||
console.error('Failed to update progress', error)
|
||||
this.$toast.error(updatePayload.isFinished ? this.$strings.ToastItemMarkedAsFinishedFailed : this.$strings.ToastItemMarkedAsNotFinishedFailed)
|
||||
})
|
||||
.finally(() => {
|
||||
this.$set(this.episodesProcessingMap, episode.id, false)
|
||||
})
|
||||
},
|
||||
clickAddToPlaylist(episode) {
|
||||
// Makeshift libraryItem
|
||||
const libraryItem = {
|
||||
|
|
@ -194,7 +234,7 @@ export default {
|
|||
episodeId: episode.id,
|
||||
title: episode.title,
|
||||
subtitle: episode.podcast.metadata.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.duration || null,
|
||||
coverPath: episode.podcast.coverPath || null
|
||||
})
|
||||
|
|
@ -211,11 +251,10 @@ export default {
|
|||
this.processing = true
|
||||
const episodePayload = await this.$axios.$get(`/api/libraries/${this.libraryId}/recent-episodes?limit=25&page=${page}`).catch((error) => {
|
||||
console.error('Failed to get recent episodes', error)
|
||||
this.$toast.error('Failed to get recent episodes')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
return null
|
||||
})
|
||||
this.processing = false
|
||||
console.log('Episodes', episodePayload)
|
||||
this.recentEpisodes = episodePayload.episodes || []
|
||||
this.totalEpisodes = episodePayload.total
|
||||
this.currentPage = page
|
||||
|
|
@ -232,7 +271,7 @@ export default {
|
|||
episodeId: episode.id,
|
||||
title: episode.title,
|
||||
subtitle: episode.podcast.metadata.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.duration || null,
|
||||
coverPath: episode.podcast.coverPath || null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ export default {
|
|||
this.processing = true
|
||||
var payload = await this.$axios.$post(`/api/podcasts/feed`, { rssFeed }).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.processing = false
|
||||
|
|
@ -197,7 +197,7 @@ export default {
|
|||
this.processing = true
|
||||
const payload = await this.$axios.$post(`/api/podcasts/feed`, { rssFeed: podcast.feedUrl }).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.processing = false
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
<div :key="author.id" class="w-full py-2">
|
||||
<div class="flex items-center mb-1">
|
||||
<p class="text-sm text-white text-opacity-70 w-36 pr-2 truncate">
|
||||
{{ index + 1 }}. <nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=authors.${$encode(author.id)}`" class="hover:underline">{{ author.name }}</nuxt-link>
|
||||
{{ index + 1 }}. <nuxt-link :to="`/author/${author.id}`" class="hover:underline">{{ author.name }}</nuxt-link>
|
||||
</p>
|
||||
<div class="flex-grow rounded-full h-2.5 bg-primary bg-opacity-0 overflow-hidden">
|
||||
<div class="bg-yellow-400 h-full rounded-full" :style="{ width: Math.round((100 * author.count) / mostUsedAuthorCount) + '%' }" />
|
||||
|
|
@ -75,7 +75,7 @@
|
|||
<div class="bg-yellow-400 h-full rounded-full" :style="{ width: Math.round((100 * ab.size) / largestItemSize) + '%' }" />
|
||||
</div>
|
||||
<div class="w-4 ml-3">
|
||||
<p class="text-sm font-bold">{{ $bytesPretty(ab.size) }}</p>
|
||||
<p class="text-sm font-bold whitespace-nowrap">{{ $bytesPretty(ab.size) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -132,11 +132,11 @@ export default {
|
|||
methods: {
|
||||
async submitServerSetup() {
|
||||
if (!this.newRoot.username || !this.newRoot.username.trim()) {
|
||||
this.$toast.error('Must enter a root username')
|
||||
this.$toast.error(this.$strings.ToastUserRootRequireName)
|
||||
return
|
||||
}
|
||||
if (this.newRoot.password !== this.confirmPassword) {
|
||||
this.$toast.error('Password mismatch')
|
||||
this.$toast.error(this.$strings.ToastUserPasswordMismatch)
|
||||
return
|
||||
}
|
||||
if (!this.newRoot.password) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue