mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-06-04 01:40:40 +02:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2054accdc9 | |||
| 7d8b857c77 | |||
| 0107cb4782 | |||
| f273eee807 | |||
| 4af21b079a | |||
| c9eaf2db2d | |||
| a5fb0d9cdb | |||
| 53c80d9798 | |||
| 832165716b | |||
| d9f2d8bf1d | |||
| a7a3a56509 | |||
| 4082fadf90 | |||
| 93160b83bf | |||
| 472240f994 | |||
| c3f0fb8e5e | |||
| b156ebeb9f | |||
| e4c775c847 | |||
| 45e8e72759 | |||
| 0ae7340889 | |||
| 8c38987d92 |
@@ -116,7 +116,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full h-12 px-1 py-2 border-t border-black/20 bg-bg absolute left-0" :style="{ bottom: streamLibraryItem ? '224px' : '65px' }">
|
<div class="w-full h-12 px-1 py-2 border-t border-black/20 bg-bg absolute left-0" :style="{ bottom: streamLibraryItem ? '224px' : '65px' }">
|
||||||
<p class="underline font-mono text-xs text-center text-gray-300 leading-3 mb-1" @click="clickChangelog">v{{ $config.version }}</p>
|
<p class="underline font-mono text-xs text-center text-gray-300 leading-3 mb-1 cursor-pointer" @click="clickChangelog">v{{ $config.version }}</p>
|
||||||
<a v-if="hasUpdate" :href="githubTagUrl" target="_blank" class="text-warning text-xxs text-center block leading-3">Update</a>
|
<a v-if="hasUpdate" :href="githubTagUrl" target="_blank" class="text-warning text-xxs text-center block leading-3">Update</a>
|
||||||
<p v-else class="text-xxs text-gray-400 leading-3 text-center italic">{{ Source }}</p>
|
<p v-else class="text-xxs text-gray-400 leading-3 text-center italic">{{ Source }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -101,7 +101,8 @@
|
|||||||
<!-- Podcast Episode # -->
|
<!-- Podcast Episode # -->
|
||||||
<div cy-id="podcastEpisodeNumber" v-if="recentEpisodeNumber !== null && !isHovering && !isSelectionMode && !processing" class="absolute rounded-lg bg-black/90 box-shadow-md z-10" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', padding: `${0.1}em ${0.25}em` }">
|
<div cy-id="podcastEpisodeNumber" v-if="recentEpisodeNumber !== null && !isHovering && !isSelectionMode && !processing" class="absolute rounded-lg bg-black/90 box-shadow-md z-10" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', padding: `${0.1}em ${0.25}em` }">
|
||||||
<p :style="{ fontSize: 0.8 + 'em' }">
|
<p :style="{ fontSize: 0.8 + 'em' }">
|
||||||
Episode<span v-if="recentEpisodeNumber"> #{{ recentEpisodeNumber }}</span>
|
Episode
|
||||||
|
<span v-if="recentEpisodeNumber">#{{ recentEpisodeNumber }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -200,6 +201,9 @@ export default {
|
|||||||
dateFormat() {
|
dateFormat() {
|
||||||
return this.store.getters['getServerSetting']('dateFormat')
|
return this.store.getters['getServerSetting']('dateFormat')
|
||||||
},
|
},
|
||||||
|
timeFormat() {
|
||||||
|
return this.store.getters['getServerSetting']('timeFormat')
|
||||||
|
},
|
||||||
_libraryItem() {
|
_libraryItem() {
|
||||||
return this.libraryItem || {}
|
return this.libraryItem || {}
|
||||||
},
|
},
|
||||||
@@ -345,6 +349,10 @@ export default {
|
|||||||
if (this.mediaMetadata.publishedYear) return this.$getString('LabelPublishedDate', [this.mediaMetadata.publishedYear])
|
if (this.mediaMetadata.publishedYear) return this.$getString('LabelPublishedDate', [this.mediaMetadata.publishedYear])
|
||||||
return '\u00A0'
|
return '\u00A0'
|
||||||
}
|
}
|
||||||
|
if (this.orderBy === 'progress') {
|
||||||
|
if (!this.userProgressLastUpdated) return '\u00A0'
|
||||||
|
return this.$getString('LabelLastProgressDate', [this.$formatDatetime(this.userProgressLastUpdated, this.dateFormat, this.timeFormat)])
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
episodeProgress() {
|
episodeProgress() {
|
||||||
@@ -377,6 +385,10 @@ export default {
|
|||||||
let progressPercent = this.itemIsFinished ? 1 : this.booksInSeries ? this.seriesProgressPercent : this.useEBookProgress ? this.userProgress?.ebookProgress || 0 : this.userProgress?.progress || 0
|
let progressPercent = this.itemIsFinished ? 1 : this.booksInSeries ? this.seriesProgressPercent : this.useEBookProgress ? this.userProgress?.ebookProgress || 0 : this.userProgress?.progress || 0
|
||||||
return Math.max(Math.min(1, progressPercent), 0)
|
return Math.max(Math.min(1, progressPercent), 0)
|
||||||
},
|
},
|
||||||
|
userProgressLastUpdated() {
|
||||||
|
if (!this.userProgress) return null
|
||||||
|
return this.userProgress.lastUpdate
|
||||||
|
},
|
||||||
itemIsFinished() {
|
itemIsFinished() {
|
||||||
if (this.booksInSeries) return this.seriesIsFinished
|
if (this.booksInSeries) return this.seriesIsFinished
|
||||||
return this.userProgress ? !!this.userProgress.isFinished : false
|
return this.userProgress ? !!this.userProgress.isFinished : false
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul v-show="showMenu" class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-96 rounded-md py-1 ring-1 ring-black/5 overflow-auto focus:outline-hidden text-sm" role="menu">
|
<ul v-show="showMenu" class="librarySortMenu absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-96 rounded-md py-1 ring-1 ring-black/5 overflow-auto focus:outline-hidden text-sm" role="menu">
|
||||||
<template v-for="item in selectItems">
|
<template v-for="item in selectItems">
|
||||||
<li :key="item.value" class="select-none relative py-2 pr-9 cursor-pointer hover:bg-white/5" :class="item.value === selected ? 'bg-white/5 text-yellow-400' : 'text-gray-200 hover:text-white'" role="menuitem" @click="clickedOption(item.value)">
|
<li :key="item.value" class="select-none relative py-2 pr-9 cursor-pointer hover:bg-white/5" :class="item.value === selected ? 'bg-white/5 text-yellow-400' : 'text-gray-200 hover:text-white'" role="menuitem" @click="clickedOption(item.value)">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@@ -130,6 +130,10 @@ export default {
|
|||||||
text: this.$strings.LabelFileModified,
|
text: this.$strings.LabelFileModified,
|
||||||
value: 'mtimeMs'
|
value: 'mtimeMs'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelLibrarySortByProgress,
|
||||||
|
value: 'progress'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: this.$strings.LabelRandomly,
|
text: this.$strings.LabelRandomly,
|
||||||
value: 'random'
|
value: 'random'
|
||||||
@@ -191,3 +195,9 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.librarySortMenu {
|
||||||
|
max-height: calc(100vh - 125px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -99,22 +99,32 @@ export default {
|
|||||||
return `/api/items/${this.libraryItemId}/ebook`
|
return `/api/items/${this.libraryItemId}/ebook`
|
||||||
},
|
},
|
||||||
themeRules() {
|
themeRules() {
|
||||||
const isDark = this.ereaderSettings.theme === 'dark'
|
const theme = this.ereaderSettings.theme
|
||||||
const fontColor = isDark ? '#fff' : '#000'
|
const isDark = theme === 'dark'
|
||||||
const backgroundColor = isDark ? 'rgb(35 35 35)' : 'rgb(255, 255, 255)'
|
const isSepia = theme === 'sepia'
|
||||||
|
|
||||||
|
const fontColor = isDark
|
||||||
|
? '#fff'
|
||||||
|
: isSepia
|
||||||
|
? '#5b4636'
|
||||||
|
: '#000'
|
||||||
|
|
||||||
|
const backgroundColor = isDark
|
||||||
|
? 'rgb(35 35 35)'
|
||||||
|
: isSepia
|
||||||
|
? 'rgb(244, 236, 216)'
|
||||||
|
: 'rgb(255, 255, 255)'
|
||||||
|
|
||||||
const lineSpacing = this.ereaderSettings.lineSpacing / 100
|
const lineSpacing = this.ereaderSettings.lineSpacing / 100
|
||||||
|
|
||||||
const fontScale = this.ereaderSettings.fontScale / 100
|
const fontScale = this.ereaderSettings.fontScale / 100
|
||||||
|
|
||||||
const textStroke = this.ereaderSettings.textStroke / 100
|
const textStroke = this.ereaderSettings.textStroke / 100
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'*': {
|
'*': {
|
||||||
color: `${fontColor}!important`,
|
color: `${fontColor}!important`,
|
||||||
'background-color': `${backgroundColor}!important`,
|
'background-color': `${backgroundColor}!important`,
|
||||||
'line-height': lineSpacing * fontScale + 'rem!important',
|
'line-height': `${lineSpacing * fontScale}rem!important`,
|
||||||
'-webkit-text-stroke': textStroke + 'px ' + fontColor + '!important'
|
'-webkit-text-stroke': `${textStroke}px ${fontColor}!important`
|
||||||
},
|
},
|
||||||
a: {
|
a: {
|
||||||
color: `${fontColor}!important`
|
color: `${fontColor}!important`
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="show" id="reader" :data-theme="ereaderTheme" class="group absolute top-0 left-0 w-full z-60 data-[theme=dark]:bg-primary data-[theme=dark]:text-white data-[theme=light]:bg-white data-[theme=light]:text-black" :class="{ 'reader-player-open': !!streamLibraryItem }">
|
<div v-if="show" id="reader" :data-theme="ereaderTheme" class="group absolute top-0 left-0 w-full z-60 data-[theme=dark]:bg-primary data-[theme=dark]:text-white data-[theme=light]:bg-white data-[theme=light]:text-black data-[theme=sepia]:bg-[rgb(244,236,216)] data-[theme=sepia]:text-[#5b4636]" :class="{ 'reader-player-open': !!streamLibraryItem }">
|
||||||
<div class="absolute top-4 left-4 z-20 flex items-center">
|
<div class="absolute top-4 left-4 z-20 flex items-center">
|
||||||
<button v-if="isEpub" @click="toggleToC" type="button" aria-label="Table of contents menu" class="inline-flex opacity-80 hover:opacity-100">
|
<button v-if="isEpub" @click="toggleToC" type="button" aria-label="Table of contents menu" class="inline-flex opacity-80 hover:opacity-100">
|
||||||
<span class="material-symbols text-2xl">menu</span>
|
<span class="material-symbols text-2xl">menu</span>
|
||||||
@@ -27,7 +27,12 @@
|
|||||||
|
|
||||||
<!-- TOC side nav -->
|
<!-- TOC side nav -->
|
||||||
<div v-if="tocOpen" class="w-full h-full overflow-y-scroll absolute inset-0 bg-black/20 z-20" @click.stop.prevent="toggleToC"></div>
|
<div v-if="tocOpen" class="w-full h-full overflow-y-scroll absolute inset-0 bg-black/20 z-20" @click.stop.prevent="toggleToC"></div>
|
||||||
<div v-if="isEpub" class="w-96 h-full max-h-full absolute top-0 left-0 shadow-xl transition-transform z-30 group-data-[theme=dark]:bg-primary group-data-[theme=dark]:text-white group-data-[theme=light]:bg-white group-data-[theme=light]:text-black" :class="tocOpen ? 'translate-x-0' : '-translate-x-96'" @click.stop.prevent>
|
<div
|
||||||
|
v-if="isEpub"
|
||||||
|
class="w-96 h-full max-h-full absolute top-0 left-0 shadow-xl transition-transform z-30 group-data-[theme=dark]:bg-primary group-data-[theme=dark]:text-white group-data-[theme=light]:bg-white group-data-[theme=light]:text-black group-data-[theme=sepia]:bg-[rgb(244,236,216)] group-data-[theme=sepia]:text-[#5b4636]"
|
||||||
|
:class="tocOpen ? 'translate-x-0' : '-translate-x-96'"
|
||||||
|
@click.stop.prevent
|
||||||
|
>
|
||||||
<div class="flex flex-col p-4 h-full">
|
<div class="flex flex-col p-4 h-full">
|
||||||
<div class="flex items-center mb-2">
|
<div class="flex items-center mb-2">
|
||||||
<button @click.stop.prevent="toggleToC" type="button" aria-label="Close table of contents" class="inline-flex opacity-80 hover:opacity-100">
|
<button @click.stop.prevent="toggleToC" type="button" aria-label="Close table of contents" class="inline-flex opacity-80 hover:opacity-100">
|
||||||
@@ -37,7 +42,7 @@
|
|||||||
<p class="text-lg font-semibold ml-2">{{ $strings.HeaderTableOfContents }}</p>
|
<p class="text-lg font-semibold ml-2">{{ $strings.HeaderTableOfContents }}</p>
|
||||||
</div>
|
</div>
|
||||||
<form @submit.prevent="searchBook" @click.stop.prevent>
|
<form @submit.prevent="searchBook" @click.stop.prevent>
|
||||||
<ui-text-input clearable ref="input" @clear="searchBook" v-model="searchQuery" :placeholder="$strings.PlaceholderSearch" class="h-8 w-full text-sm flex mb-2" />
|
<ui-text-input clearable ref="input" @clear="searchBook" v-model="searchQuery" :placeholder="$strings.PlaceholderSearch" custom-input-class="text-inherit !bg-inherit" class="h-8 w-full text-sm flex mb-2" />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="overflow-y-auto">
|
<div class="overflow-y-auto">
|
||||||
@@ -181,6 +186,10 @@ export default {
|
|||||||
text: this.$strings.LabelThemeDark,
|
text: this.$strings.LabelThemeDark,
|
||||||
value: 'dark'
|
value: 'dark'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelThemeSepia,
|
||||||
|
value: 'sepia'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: this.$strings.LabelThemeLight,
|
text: this.$strings.LabelThemeLight,
|
||||||
value: 'light'
|
value: 'light'
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.26.2",
|
"version": "2.26.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.26.2",
|
"version": "2.26.3",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/axios": "^5.13.6",
|
"@nuxtjs/axios": "^5.13.6",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.26.2",
|
"version": "2.26.3",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast client",
|
"description": "Self-hosted audiobook and podcast client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="listeningSessions.length" class="block max-w-full relative">
|
<div v-if="listeningSessions.length" class="block max-w-full relative">
|
||||||
|
<div class="overflow-x-auto">
|
||||||
<table class="userSessionsTable">
|
<table class="userSessionsTable">
|
||||||
<tr class="bg-primary/40">
|
<tr class="bg-primary/40">
|
||||||
<th class="w-6 min-w-6 text-left hidden md:table-cell h-11">
|
<th class="w-6 min-w-6 text-left hidden md:table-cell h-11">
|
||||||
@@ -80,6 +81,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
<!-- table bottom options -->
|
<!-- table bottom options -->
|
||||||
<div class="flex items-center my-2">
|
<div class="flex items-center my-2">
|
||||||
<div class="grow" />
|
<div class="grow" />
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
<div class="py-2">
|
<div class="py-2">
|
||||||
<h1 class="text-lg mb-2 text-white/90 px-2 sm:px-0">{{ $strings.HeaderListeningSessions }}</h1>
|
<h1 class="text-lg mb-2 text-white/90 px-2 sm:px-0">{{ $strings.HeaderListeningSessions }}</h1>
|
||||||
<div v-if="listeningSessions.length">
|
<div v-if="listeningSessions.length">
|
||||||
|
<div class="overflow-x-auto">
|
||||||
<table class="userSessionsTable">
|
<table class="userSessionsTable">
|
||||||
<tr class="bg-primary/40">
|
<tr class="bg-primary/40">
|
||||||
<th class="w-48 min-w-48 text-left">{{ $strings.LabelItem }}</th>
|
<th class="w-48 min-w-48 text-left">{{ $strings.LabelItem }}</th>
|
||||||
@@ -52,6 +53,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
<div class="flex items-center justify-end py-1">
|
<div class="flex items-center justify-end py-1">
|
||||||
<ui-icon-btn icon="arrow_back_ios_new" :size="7" icon-font-size="1rem" class="mx-1" :disabled="currentPage === 0" @click="prevPage" />
|
<ui-icon-btn icon="arrow_back_ios_new" :size="7" icon-font-size="1rem" class="mx-1" :disabled="currentPage === 0" @click="prevPage" />
|
||||||
<p class="text-sm mx-1">{{ $getString('LabelPaginationPageXOfY', [currentPage + 1, numPages]) }}</p>
|
<p class="text-sm mx-1">{{ $getString('LabelPaginationPageXOfY', [currentPage + 1, numPages]) }}</p>
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export const actions = {
|
|||||||
if (state.settings.orderBy == 'media.duration') {
|
if (state.settings.orderBy == 'media.duration') {
|
||||||
settingsUpdate.orderBy = 'media.numTracks'
|
settingsUpdate.orderBy = 'media.numTracks'
|
||||||
}
|
}
|
||||||
if (state.settings.orderBy == 'media.metadata.publishedYear') {
|
if (state.settings.orderBy == 'media.metadata.publishedYear' || state.settings.orderBy == 'progress') {
|
||||||
settingsUpdate.orderBy = 'media.metadata.title'
|
settingsUpdate.orderBy = 'media.metadata.title'
|
||||||
}
|
}
|
||||||
const invalidFilters = ['series', 'authors', 'narrators', 'publishers', 'publishedDecades', 'languages', 'progress', 'issues', 'ebooks', 'abridged']
|
const invalidFilters = ['series', 'authors', 'narrators', 'publishers', 'publishedDecades', 'languages', 'progress', 'issues', 'ebooks', 'abridged']
|
||||||
|
|||||||
@@ -438,6 +438,7 @@
|
|||||||
"LabelLogLevelWarn": "Warnungen",
|
"LabelLogLevelWarn": "Warnungen",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Suche nach neuen Episoden nach diesem Datum",
|
"LabelLookForNewEpisodesAfterDate": "Suche nach neuen Episoden nach diesem Datum",
|
||||||
"LabelLowestPriority": "Niedrigste Priorität",
|
"LabelLowestPriority": "Niedrigste Priorität",
|
||||||
|
"LabelMatchConfidence": "Zuversicht",
|
||||||
"LabelMatchExistingUsersBy": "Zuordnen existierender Benutzer mit",
|
"LabelMatchExistingUsersBy": "Zuordnen existierender Benutzer mit",
|
||||||
"LabelMatchExistingUsersByDescription": "Wird zum Verbinden vorhandener Benutzer verwendet. Sobald die Verbindung hergestellt ist, wird den Benutzern eine eindeutige ID vom SSO-Anbieter zugeordnet",
|
"LabelMatchExistingUsersByDescription": "Wird zum Verbinden vorhandener Benutzer verwendet. Sobald die Verbindung hergestellt ist, wird den Benutzern eine eindeutige ID vom SSO-Anbieter zugeordnet",
|
||||||
"LabelMaxEpisodesToDownload": "Max. Anzahl an Episoden zum Herunterladen, 0 für unbegrenzte Episoden.",
|
"LabelMaxEpisodesToDownload": "Max. Anzahl an Episoden zum Herunterladen, 0 für unbegrenzte Episoden.",
|
||||||
@@ -723,6 +724,7 @@
|
|||||||
"MessageAddToPlayerQueue": "Zur Abspielwarteliste hinzufügen",
|
"MessageAddToPlayerQueue": "Zur Abspielwarteliste hinzufügen",
|
||||||
"MessageAppriseDescription": "Um diese Funktion nutzen zu können, musst du eine Instanz von <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> laufen haben oder eine API verwenden welche dieselbe Anfragen bearbeiten kann. <br />Die Apprise API Url muss der vollständige URL-Pfad sein, an den die Benachrichtigung gesendet werden soll, z.B. wenn Ihre API-Instanz unter <code>http://192.168.1.1:8337</code> läuft, würdest du <code>http://192.168.1.1:8337/notify</code> eingeben.",
|
"MessageAppriseDescription": "Um diese Funktion nutzen zu können, musst du eine Instanz von <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> laufen haben oder eine API verwenden welche dieselbe Anfragen bearbeiten kann. <br />Die Apprise API Url muss der vollständige URL-Pfad sein, an den die Benachrichtigung gesendet werden soll, z.B. wenn Ihre API-Instanz unter <code>http://192.168.1.1:8337</code> läuft, würdest du <code>http://192.168.1.1:8337/notify</code> eingeben.",
|
||||||
"MessageAsinCheck": "Stellen Sie sicher, dass Sie die ASIN aus der richtigen Audible Region verwenden, nicht Amazon.",
|
"MessageAsinCheck": "Stellen Sie sicher, dass Sie die ASIN aus der richtigen Audible Region verwenden, nicht Amazon.",
|
||||||
|
"MessageAuthenticationLegacyTokenWarning": "Alte API tokens werden in Zukunft entfernt. Benutze stattdessen <a href=\"/config/api-keys\">API Keys</a>.",
|
||||||
"MessageAuthenticationOIDCChangesRestart": "Nach dem Speichern muss der Server neugestartet werden um die OIDC Änderungen zu übernehmen.",
|
"MessageAuthenticationOIDCChangesRestart": "Nach dem Speichern muss der Server neugestartet werden um die OIDC Änderungen zu übernehmen.",
|
||||||
"MessageAuthenticationSecurityMessage": "Die Anmeldung wurde abgesichert. Benutzersitzungen werden getrennt, alle Benutzer müssen sich erneut anmelden.",
|
"MessageAuthenticationSecurityMessage": "Die Anmeldung wurde abgesichert. Benutzersitzungen werden getrennt, alle Benutzer müssen sich erneut anmelden.",
|
||||||
"MessageBackupsDescription": "In einer Sicherung werden Benutzer, Benutzerfortschritte, Details zu den Bibliotheksobjekten, Servereinstellungen und Bilder welche in <code>/metadata/items</code> & <code>/metadata/authors</code> gespeichert sind gespeichert. Sicherungen enthalten keine Dateien welche in den einzelnen Bibliotheksordnern (Medien-Ordnern) gespeichert sind.",
|
"MessageBackupsDescription": "In einer Sicherung werden Benutzer, Benutzerfortschritte, Details zu den Bibliotheksobjekten, Servereinstellungen und Bilder welche in <code>/metadata/items</code> & <code>/metadata/authors</code> gespeichert sind gespeichert. Sicherungen enthalten keine Dateien welche in den einzelnen Bibliotheksordnern (Medien-Ordnern) gespeichert sind.",
|
||||||
|
|||||||
@@ -418,6 +418,7 @@
|
|||||||
"LabelLanguages": "Languages",
|
"LabelLanguages": "Languages",
|
||||||
"LabelLastBookAdded": "Last Book Added",
|
"LabelLastBookAdded": "Last Book Added",
|
||||||
"LabelLastBookUpdated": "Last Book Updated",
|
"LabelLastBookUpdated": "Last Book Updated",
|
||||||
|
"LabelLastProgressDate": "Last progress: {0}",
|
||||||
"LabelLastSeen": "Last Seen",
|
"LabelLastSeen": "Last Seen",
|
||||||
"LabelLastTime": "Last Time",
|
"LabelLastTime": "Last Time",
|
||||||
"LabelLastUpdate": "Last Update",
|
"LabelLastUpdate": "Last Update",
|
||||||
@@ -430,6 +431,7 @@
|
|||||||
"LabelLibraryFilterSublistEmpty": "No {0}",
|
"LabelLibraryFilterSublistEmpty": "No {0}",
|
||||||
"LabelLibraryItem": "Library Item",
|
"LabelLibraryItem": "Library Item",
|
||||||
"LabelLibraryName": "Library Name",
|
"LabelLibraryName": "Library Name",
|
||||||
|
"LabelLibrarySortByProgress": "Progress Updated",
|
||||||
"LabelLimit": "Limit",
|
"LabelLimit": "Limit",
|
||||||
"LabelLineSpacing": "Line spacing",
|
"LabelLineSpacing": "Line spacing",
|
||||||
"LabelListenAgain": "Listen Again",
|
"LabelListenAgain": "Listen Again",
|
||||||
@@ -656,6 +658,7 @@
|
|||||||
"LabelTheme": "Theme",
|
"LabelTheme": "Theme",
|
||||||
"LabelThemeDark": "Dark",
|
"LabelThemeDark": "Dark",
|
||||||
"LabelThemeLight": "Light",
|
"LabelThemeLight": "Light",
|
||||||
|
"LabelThemeSepia": "Sepia",
|
||||||
"LabelTimeBase": "Time Base",
|
"LabelTimeBase": "Time Base",
|
||||||
"LabelTimeDurationXHours": "{0} hours",
|
"LabelTimeDurationXHours": "{0} hours",
|
||||||
"LabelTimeDurationXMinutes": "{0} minutes",
|
"LabelTimeDurationXMinutes": "{0} minutes",
|
||||||
|
|||||||
@@ -438,6 +438,7 @@
|
|||||||
"LabelLogLevelWarn": "Предупреждение",
|
"LabelLogLevelWarn": "Предупреждение",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Искать новые эпизоды после этой даты",
|
"LabelLookForNewEpisodesAfterDate": "Искать новые эпизоды после этой даты",
|
||||||
"LabelLowestPriority": "Самый низкий приоритет",
|
"LabelLowestPriority": "Самый низкий приоритет",
|
||||||
|
"LabelMatchConfidence": "Уверенность",
|
||||||
"LabelMatchExistingUsersBy": "Сопоставление существующих пользователей по",
|
"LabelMatchExistingUsersBy": "Сопоставление существующих пользователей по",
|
||||||
"LabelMatchExistingUsersByDescription": "Используется для подключения существующих пользователей. После подключения пользователям будет присвоен уникальный идентификатор от поставщика единого входа",
|
"LabelMatchExistingUsersByDescription": "Используется для подключения существующих пользователей. После подключения пользователям будет присвоен уникальный идентификатор от поставщика единого входа",
|
||||||
"LabelMaxEpisodesToDownload": "Максимальное количество эпизодов для загрузки. Используйте 0 для неограниченного количества.",
|
"LabelMaxEpisodesToDownload": "Максимальное количество эпизодов для загрузки. Используйте 0 для неограниченного количества.",
|
||||||
@@ -723,6 +724,7 @@
|
|||||||
"MessageAddToPlayerQueue": "Добавить в очередь проигрывателя",
|
"MessageAddToPlayerQueue": "Добавить в очередь проигрывателя",
|
||||||
"MessageAppriseDescription": "Для использования этой функции необходимо иметь запущенный экземпляр <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> или api которое обрабатывает те же самые запросы. <br />URL-адрес API Apprise должен быть полным URL-адресом для отправки уведомления, т.е., если API запущено по адресу <code>http://192.168.1.1:8337</code> тогда нужно указать <code>http://192.168.1.1:8337/notify</code>.",
|
"MessageAppriseDescription": "Для использования этой функции необходимо иметь запущенный экземпляр <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> или api которое обрабатывает те же самые запросы. <br />URL-адрес API Apprise должен быть полным URL-адресом для отправки уведомления, т.е., если API запущено по адресу <code>http://192.168.1.1:8337</code> тогда нужно указать <code>http://192.168.1.1:8337/notify</code>.",
|
||||||
"MessageAsinCheck": "Убедитесь, что вы используете ASIN из правильной региональной зоны Audible, а не из Amazon.",
|
"MessageAsinCheck": "Убедитесь, что вы используете ASIN из правильной региональной зоны Audible, а не из Amazon.",
|
||||||
|
"MessageAuthenticationLegacyTokenWarning": "Устаревшие токены API в будущем будут удалены. Вместо них используйте <a href=\"/config/api-keys\">API-ключи</a>.",
|
||||||
"MessageAuthenticationOIDCChangesRestart": "Перезапустите ваш сервер после сохранения для применения изменений в OIDC.",
|
"MessageAuthenticationOIDCChangesRestart": "Перезапустите ваш сервер после сохранения для применения изменений в OIDC.",
|
||||||
"MessageAuthenticationSecurityMessage": "В целях безопасности была улучшена аутентификация. Всем пользователям необходимо повторно войти в систему.",
|
"MessageAuthenticationSecurityMessage": "В целях безопасности была улучшена аутентификация. Всем пользователям необходимо повторно войти в систему.",
|
||||||
"MessageBackupsDescription": "Бэкап включает пользователей, прогресс пользователей, данные элементов библиотеки, настройки сервера и изображения хранящиеся в <code>/metadata/items</code> и <code>/metadata/authors</code>. Бэкапы <strong>НЕ</strong> сохраняют файлы из папок библиотек.",
|
"MessageBackupsDescription": "Бэкап включает пользователей, прогресс пользователей, данные элементов библиотеки, настройки сервера и изображения хранящиеся в <code>/metadata/items</code> и <code>/metadata/authors</code>. Бэкапы <strong>НЕ</strong> сохраняют файлы из папок библиотек.",
|
||||||
|
|||||||
@@ -438,6 +438,7 @@
|
|||||||
"LabelLogLevelWarn": "Увага",
|
"LabelLogLevelWarn": "Увага",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Шукати нові епізоди після вказаної дати",
|
"LabelLookForNewEpisodesAfterDate": "Шукати нові епізоди після вказаної дати",
|
||||||
"LabelLowestPriority": "Найнижчий пріоритет",
|
"LabelLowestPriority": "Найнижчий пріоритет",
|
||||||
|
"LabelMatchConfidence": "Впевненість",
|
||||||
"LabelMatchExistingUsersBy": "Шукати наявних користувачів за",
|
"LabelMatchExistingUsersBy": "Шукати наявних користувачів за",
|
||||||
"LabelMatchExistingUsersByDescription": "Використовується для підключення наявних користувачів. Після підключення користувач отримає унікальний id від вашого сервісу SSO",
|
"LabelMatchExistingUsersByDescription": "Використовується для підключення наявних користувачів. Після підключення користувач отримає унікальний id від вашого сервісу SSO",
|
||||||
"LabelMaxEpisodesToDownload": "Максимальна кількість епізодів для скачування. Використовуйте 0 для необмеженої кількості.",
|
"LabelMaxEpisodesToDownload": "Максимальна кількість епізодів для скачування. Використовуйте 0 для необмеженої кількості.",
|
||||||
@@ -837,7 +838,7 @@
|
|||||||
"MessageNoItems": "Елементи відсутні",
|
"MessageNoItems": "Елементи відсутні",
|
||||||
"MessageNoItemsFound": "Елементів не знайдено",
|
"MessageNoItemsFound": "Елементів не знайдено",
|
||||||
"MessageNoListeningSessions": "Сеанси прослуховування відсутні",
|
"MessageNoListeningSessions": "Сеанси прослуховування відсутні",
|
||||||
"MessageNoLogs": "Немає журнали",
|
"MessageNoLogs": "Немає журналів'",
|
||||||
"MessageNoMediaProgress": "Прогрес відсутній",
|
"MessageNoMediaProgress": "Прогрес відсутній",
|
||||||
"MessageNoNotifications": "Сповіщення відсутні",
|
"MessageNoNotifications": "Сповіщення відсутні",
|
||||||
"MessageNoPodcastFeed": "Некоректний подкаст: немає каналу",
|
"MessageNoPodcastFeed": "Некоректний подкаст: немає каналу",
|
||||||
|
|||||||
@@ -438,6 +438,7 @@
|
|||||||
"LabelLogLevelWarn": "警告",
|
"LabelLogLevelWarn": "警告",
|
||||||
"LabelLookForNewEpisodesAfterDate": "在此日期后查找新剧集",
|
"LabelLookForNewEpisodesAfterDate": "在此日期后查找新剧集",
|
||||||
"LabelLowestPriority": "最低优先级",
|
"LabelLowestPriority": "最低优先级",
|
||||||
|
"LabelMatchConfidence": "置信度",
|
||||||
"LabelMatchExistingUsersBy": "匹配现有用户",
|
"LabelMatchExistingUsersBy": "匹配现有用户",
|
||||||
"LabelMatchExistingUsersByDescription": "用于连接现有用户. 连接后, 用户将通过 SSO 提供商提供的唯一 id 进行匹配",
|
"LabelMatchExistingUsersByDescription": "用于连接现有用户. 连接后, 用户将通过 SSO 提供商提供的唯一 id 进行匹配",
|
||||||
"LabelMaxEpisodesToDownload": "可下载的最大集数. 输入 0 表示无限制.",
|
"LabelMaxEpisodesToDownload": "可下载的最大集数. 输入 0 表示无限制.",
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.26.2",
|
"version": "2.26.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.26.2",
|
"version": "2.26.3",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.26.2",
|
"version": "2.26.3",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast server",
|
"description": "Self-hosted audiobook and podcast server",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ class MeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { password, newPassword } = req.body
|
const { password, newPassword } = req.body
|
||||||
if (!password || !newPassword || typeof password !== 'string' || typeof newPassword !== 'string') {
|
if ((typeof password !== 'string' && password !== null) || (typeof newPassword !== 'string' && newPassword !== null)) {
|
||||||
return res.status(400).send('Missing or invalid password or new password')
|
return res.status(400).send('Missing or invalid password or new password')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,7 +450,7 @@ class MeController {
|
|||||||
if (updated) {
|
if (updated) {
|
||||||
await Database.updateSetting(Database.emailSettings)
|
await Database.updateSetting(Database.emailSettings)
|
||||||
SocketAuthority.clientEmitter(req.user.id, 'ereader-devices-updated', {
|
SocketAuthority.clientEmitter(req.user.id, 'ereader-devices-updated', {
|
||||||
ereaderDevices: Database.emailSettings.ereaderDevices
|
ereaderDevices: Database.emailSettings.getEReaderDevices(req.user)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
res.json({
|
res.json({
|
||||||
|
|||||||
@@ -288,7 +288,12 @@ class SessionController {
|
|||||||
return res.sendStatus(404)
|
return res.sendStatus(404)
|
||||||
}
|
}
|
||||||
|
|
||||||
const audioTrack = playbackSession.audioTracks.find((t) => t.index === audioTrackIndex)
|
let audioTrack = playbackSession.audioTracks.find((t) => toNumber(t.index, 1) === audioTrackIndex)
|
||||||
|
|
||||||
|
// Support clients passing 0 or 1 for podcast episode audio track index (handles old episodes pre-v2.21.0 having null index)
|
||||||
|
if (!audioTrack && playbackSession.mediaType === 'podcast' && audioTrackIndex === 0) {
|
||||||
|
audioTrack = playbackSession.audioTracks[0]
|
||||||
|
}
|
||||||
if (!audioTrack) {
|
if (!audioTrack) {
|
||||||
Logger.error(`[SessionController] Unable to find audio track with index=${audioTrackIndex}`)
|
Logger.error(`[SessionController] Unable to find audio track with index=${audioTrackIndex}`)
|
||||||
return res.sendStatus(404)
|
return res.sendStatus(404)
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ class PodcastEpisode extends Model {
|
|||||||
const track = structuredClone(this.audioFile)
|
const track = structuredClone(this.audioFile)
|
||||||
track.startOffset = 0
|
track.startOffset = 0
|
||||||
track.title = this.audioFile.metadata.filename
|
track.title = this.audioFile.metadata.filename
|
||||||
|
track.index = 1 // Podcast episodes only have one track
|
||||||
track.contentUrl = `/api/items/${libraryItemId}/file/${track.ino}`
|
track.contentUrl = `/api/items/${libraryItemId}/file/${track.ino}`
|
||||||
return track
|
return track
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -399,9 +399,6 @@ module.exports = {
|
|||||||
if (filterGroup !== 'series' && sortBy === 'sequence') {
|
if (filterGroup !== 'series' && sortBy === 'sequence') {
|
||||||
sortBy = 'media.metadata.title'
|
sortBy = 'media.metadata.title'
|
||||||
}
|
}
|
||||||
if (filterGroup !== 'progress' && sortBy === 'progress') {
|
|
||||||
sortBy = 'media.metadata.title'
|
|
||||||
}
|
|
||||||
const includeRSSFeed = include.includes('rssfeed')
|
const includeRSSFeed = include.includes('rssfeed')
|
||||||
const includeMediaItemShare = !!user?.isAdminOrUp && include.includes('share')
|
const includeMediaItemShare = !!user?.isAdminOrUp && include.includes('share')
|
||||||
|
|
||||||
@@ -532,6 +529,18 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When sorting by progress but not filtering by progress, include media progresses
|
||||||
|
if (filterGroup !== 'progress' && sortBy === 'progress') {
|
||||||
|
bookIncludes.push({
|
||||||
|
model: Database.mediaProgressModel,
|
||||||
|
attributes: ['id', 'isFinished', 'currentTime', 'ebookProgress', 'updatedAt'],
|
||||||
|
where: {
|
||||||
|
userId: user.id
|
||||||
|
},
|
||||||
|
required: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let { mediaWhere, replacements } = this.getMediaGroupQuery(filterGroup, filterValue)
|
let { mediaWhere, replacements } = this.getMediaGroupQuery(filterGroup, filterValue)
|
||||||
let bookWhere = Array.isArray(mediaWhere) ? mediaWhere : [mediaWhere]
|
let bookWhere = Array.isArray(mediaWhere) ? mediaWhere : [mediaWhere]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user