mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-31 11:38:47 -05:00
Compare commits
48 Commits
fix_logger
...
fix_next_p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36ef675556 | ||
|
|
0dd57a8912 | ||
|
|
25ae6dd59a | ||
|
|
a37fe3c3d2 | ||
|
|
59bcbe0dfa | ||
|
|
b5e69630de | ||
|
|
0bba709124 | ||
|
|
e93bb5cb07 | ||
|
|
3f7af8acfb | ||
|
|
5e5a604d03 | ||
|
|
201e12ecc3 | ||
|
|
24d6e390f0 | ||
|
|
0cf7a6abec | ||
|
|
76ac0d001b | ||
|
|
00343a953b | ||
|
|
82ab95ab02 | ||
|
|
a1d8ebc01b | ||
|
|
eeaae5f934 | ||
|
|
4464276a6e | ||
|
|
3465790fe9 | ||
|
|
5fa4c5a2c3 | ||
|
|
13f353596b | ||
|
|
3d9100e5b8 | ||
|
|
b62309ead2 | ||
|
|
1fce94ad4a | ||
|
|
9abd6698ae | ||
|
|
88c10ad619 | ||
|
|
c62a6fbffd | ||
|
|
989388d3ed | ||
|
|
4cc97a22f6 | ||
|
|
8bd336a4ba | ||
|
|
437c8dd09c | ||
|
|
f82697cbbf | ||
|
|
74c87a0bbd | ||
|
|
35eb5bcfc0 | ||
|
|
0a29b549df | ||
|
|
a38a92b948 | ||
|
|
d245c93da4 | ||
|
|
bcf8f6b732 | ||
|
|
40e11db5e5 | ||
|
|
aebb3ff413 | ||
|
|
a58d486c44 | ||
|
|
4a76ba0226 | ||
|
|
7afff57b5e | ||
|
|
2e13c5bd50 | ||
|
|
344de941ff | ||
|
|
5c0cd98cb3 | ||
|
|
8974c582fc |
@@ -99,6 +99,7 @@ export default {
|
||||
this.$store.commit('showEditModal', libraryItem)
|
||||
},
|
||||
editEpisode({ libraryItem, episode }) {
|
||||
this.$store.commit('setEpisodeTableEpisodeIds', [episode.id])
|
||||
this.$store.commit('setSelectedLibraryItem', libraryItem)
|
||||
this.$store.commit('globals/setSelectedEpisode', episode)
|
||||
this.$store.commit('globals/setShowEditPodcastEpisodeModal', true)
|
||||
|
||||
@@ -19,6 +19,14 @@
|
||||
</div>
|
||||
<div v-else-if="!totalShelves && initialized" class="w-full py-16">
|
||||
<p class="text-xl text-center">{{ emptyMessage }}</p>
|
||||
<div v-if="entityName === 'collections' || entityName === 'playlists'" class="flex justify-center mt-4">
|
||||
{{ emptyMessageHelp }}
|
||||
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
|
||||
<a href="https://www.audiobookshelf.org/guides/collections" target="_blank" class="inline-flex">
|
||||
<span class="material-symbols text-xl w-5 text-gray-200">help_outline</span>
|
||||
</a>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
<!-- Clear filter only available on Library bookshelf -->
|
||||
<div v-if="entityName === 'items'" class="flex justify-center mt-2">
|
||||
<ui-btn v-if="hasFilter" color="primary" @click="clearFilter">{{ $strings.ButtonClearFilter }}</ui-btn>
|
||||
@@ -109,6 +117,11 @@ export default {
|
||||
}
|
||||
return this.$strings.MessageNoResults
|
||||
},
|
||||
emptyMessageHelp() {
|
||||
if (this.page === 'collections') return this.$strings.MessageBookshelfNoCollectionsHelp
|
||||
if (this.page === 'playlists') return this.$strings.MessageNoUserPlaylistsHelp
|
||||
return ''
|
||||
},
|
||||
entityName() {
|
||||
if (!this.page) return 'items'
|
||||
return this.page
|
||||
|
||||
@@ -31,13 +31,6 @@
|
||||
<p cy-id="placeholderAuthorText" aria-hidden="true" class="text-center" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'em' }">{{ authorCleaned }}</p>
|
||||
</div>
|
||||
|
||||
<div v-if="seriesSequenceList" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20 text-right" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', padding: `${0.1}em ${0.25}em` }" style="background-color: #78350f">
|
||||
<p :style="{ fontSize: 0.8 + 'em' }">#{{ seriesSequenceList }}</p>
|
||||
</div>
|
||||
<div v-else-if="booksInSeries" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', padding: `${0.1}em ${0.25}em` }" style="background-color: #cd9d49dd">
|
||||
<p :style="{ fontSize: 0.8 + 'em' }">{{ booksInSeries }}</p>
|
||||
</div>
|
||||
|
||||
<!-- No progress shown for podcasts (unless showing podcast episode) -->
|
||||
<div cy-id="progressBar" v-if="!isPodcast || episodeProgress" class="absolute bottom-0 left-0 h-1e max-w-full z-20 rounded-b box-shadow-progressbar" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: coverWidth * userProgressPercent + 'px' }"></div>
|
||||
|
||||
@@ -244,6 +237,7 @@ export default {
|
||||
return this.mediaMetadata.series
|
||||
},
|
||||
seriesName() {
|
||||
if (this.collapsedSeries?.name) return this.collapsedSeries.name
|
||||
return this.series?.name || null
|
||||
},
|
||||
seriesSequence() {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div ref="container" class="w-full rounded-lg bg-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
|
||||
<div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
|
||||
<div v-if="show" class="w-full h-full py-4">
|
||||
<div class="w-full overflow-y-auto overflow-x-hidden max-h-96">
|
||||
<div class="flex px-8 items-center py-2">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div ref="container" class="w-full rounded-lg bg-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
|
||||
<div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
|
||||
<div v-if="show" class="w-full h-full">
|
||||
<div class="py-4 px-4">
|
||||
<h1 v-if="!showBatchCollectionModal" class="text-2xl">{{ $strings.LabelAddToCollection }}</h1>
|
||||
@@ -19,9 +19,20 @@
|
||||
</template>
|
||||
</transition-group>
|
||||
</div>
|
||||
<div v-if="!collections.length" class="flex h-32 items-center justify-center">
|
||||
<p class="text-xl">{{ $strings.MessageNoCollections }}</p>
|
||||
<div v-if="!collections.length" class="flex h-32 items-center justify-center text-center px-2">
|
||||
<div>
|
||||
<p class="text-xl mb-2">{{ $strings.MessageNoCollections }}</p>
|
||||
<div class="text-sm flex items-center justify-center text-gray-200">
|
||||
<p>{{ $strings.MessageBookshelfNoCollectionsHelp }}</p>
|
||||
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
|
||||
<a href="https://www.audiobookshelf.org/guides/collections" target="_blank" class="inline-flex">
|
||||
<span class="material-symbols text-xl w-5 text-gray-200">help_outline</span>
|
||||
</a>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full h-px bg-white bg-opacity-10" />
|
||||
<form @submit.prevent="submitCreateCollection">
|
||||
<div class="flex px-4 py-2 items-center text-center border-b border-white border-opacity-10 text-white text-opacity-80">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex items-center px-4 py-2 justify-start relative hover:bg-bg" @mouseover="mouseover" @mouseleave="mouseleave">
|
||||
<div class="flex items-center px-4 py-2 justify-start relative hover:bg-black-400" @mouseover="mouseover" @mouseleave="mouseleave">
|
||||
<div v-if="isBookIncluded" class="absolute top-0 left-0 h-full w-1 bg-success z-10" />
|
||||
<div class="w-20 max-w-20 text-center">
|
||||
<covers-collection-cover :book-items="books" :width="80" :height="40 * bookCoverAspectRatio" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
|
||||
@@ -196,6 +196,9 @@ export default {
|
||||
methods: {
|
||||
async goPrevBook() {
|
||||
if (this.currentBookshelfIndex - 1 < 0) return
|
||||
// Remove focus from active input
|
||||
document.activeElement?.blur?.()
|
||||
|
||||
var prevBookId = this.bookshelfBookIds[this.currentBookshelfIndex - 1]
|
||||
this.processing = true
|
||||
var prevBook = await this.$axios.$get(`/api/items/${prevBookId}?expanded=1`).catch((error) => {
|
||||
@@ -215,6 +218,9 @@ export default {
|
||||
},
|
||||
async goNextBook() {
|
||||
if (this.currentBookshelfIndex >= this.bookshelfBookIds.length - 1) return
|
||||
// Remove focus from active input
|
||||
document.activeElement?.blur?.()
|
||||
|
||||
this.processing = true
|
||||
var nextBookId = this.bookshelfBookIds[this.currentBookshelfIndex + 1]
|
||||
var nextBook = await this.$axios.$get(`/api/items/${nextBookId}?expanded=1`).catch((error) => {
|
||||
@@ -300,4 +306,4 @@ export default {
|
||||
.tab.tab-selected {
|
||||
height: 41px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div ref="container" class="w-full rounded-lg bg-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
|
||||
<div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
|
||||
<div v-if="show" class="w-full h-full">
|
||||
<div class="py-4 px-4">
|
||||
<h1 v-if="!isBatch" class="text-2xl">{{ $strings.LabelAddToPlaylist }}</h1>
|
||||
@@ -19,8 +19,18 @@
|
||||
</template>
|
||||
</transition-group>
|
||||
</div>
|
||||
<div v-if="!playlists.length" class="flex h-32 items-center justify-center">
|
||||
<p class="text-xl">{{ $strings.MessageNoUserPlaylists }}</p>
|
||||
<div v-if="!playlists.length" class="flex h-32 items-center justify-center text-center px-2">
|
||||
<div>
|
||||
<p class="text-xl mb-2">{{ $strings.MessageNoUserPlaylists }}</p>
|
||||
<div class="text-sm flex items-center justify-center text-gray-200">
|
||||
<p>{{ $strings.MessageNoUserPlaylistsHelp }}</p>
|
||||
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
|
||||
<a href="https://www.audiobookshelf.org/guides/collections" target="_blank" class="inline-flex">
|
||||
<span class="material-symbols text-xl w-5 text-gray-200">help_outline</span>
|
||||
</a>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full h-px bg-white bg-opacity-10" />
|
||||
<form @submit.prevent="submitCreatePlaylist">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex items-center px-4 py-2 justify-start relative hover:bg-bg" @mouseover="mouseover" @mouseleave="mouseleave">
|
||||
<div class="flex items-center px-4 py-2 justify-start relative hover:bg-black-400" @mouseover="mouseover" @mouseleave="mouseleave">
|
||||
<div v-if="isItemIncluded" class="absolute top-0 left-0 h-full w-1 bg-success z-10" />
|
||||
<div class="w-16 max-w-16 text-center">
|
||||
<covers-playlist-cover :items="items" :width="64" :height="64" />
|
||||
|
||||
@@ -117,8 +117,12 @@ export default {
|
||||
methods: {
|
||||
async goPrevEpisode() {
|
||||
if (this.currentEpisodeIndex - 1 < 0) return
|
||||
// Remove focus from active input
|
||||
document.activeElement?.blur?.()
|
||||
|
||||
const prevEpisodeId = this.episodeTableEpisodeIds[this.currentEpisodeIndex - 1]
|
||||
this.processing = true
|
||||
|
||||
const prevEpisode = await this.$axios.$get(`/api/podcasts/${this.libraryItem.id}/episode/${prevEpisodeId}`).catch((error) => {
|
||||
const errorMsg = error.response && error.response.data ? error.response.data : 'Failed to fetch episode'
|
||||
this.$toast.error(errorMsg)
|
||||
@@ -134,8 +138,12 @@ export default {
|
||||
},
|
||||
async goNextEpisode() {
|
||||
if (this.currentEpisodeIndex >= this.episodeTableEpisodeIds.length - 1) return
|
||||
// Remove focus from active input
|
||||
document.activeElement?.blur?.()
|
||||
|
||||
this.processing = true
|
||||
const nextEpisodeId = this.episodeTableEpisodeIds[this.currentEpisodeIndex + 1]
|
||||
|
||||
const nextEpisode = await this.$axios.$get(`/api/podcasts/${this.libraryItem.id}/episode/${nextEpisodeId}`).catch((error) => {
|
||||
const errorMsg = error.response && error.response.data ? error.response.data : 'Failed to fetch book'
|
||||
this.$toast.error(errorMsg)
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<div>
|
||||
<div class="flex flex-wrap">
|
||||
<div class="w-1/5 p-1">
|
||||
<ui-text-input-with-label v-model="newEpisode.season" :label="$strings.LabelSeason" />
|
||||
<ui-text-input-with-label v-model="newEpisode.season" trim-whitespace :label="$strings.LabelSeason" />
|
||||
</div>
|
||||
<div class="w-1/5 p-1">
|
||||
<ui-text-input-with-label v-model="newEpisode.episode" :label="$strings.LabelEpisode" />
|
||||
<ui-text-input-with-label v-model="newEpisode.episode" trim-whitespace :label="$strings.LabelEpisode" />
|
||||
</div>
|
||||
<div class="w-1/5 p-1">
|
||||
<ui-dropdown v-model="newEpisode.episodeType" :label="$strings.LabelEpisodeType" :items="episodeTypes" small />
|
||||
@@ -14,10 +14,10 @@
|
||||
<ui-text-input-with-label v-model="pubDateInput" ref="pubdate" type="datetime-local" :label="$strings.LabelPubDate" @input="updatePubDate" />
|
||||
</div>
|
||||
<div class="w-full p-1">
|
||||
<ui-text-input-with-label v-model="newEpisode.title" :label="$strings.LabelTitle" />
|
||||
<ui-text-input-with-label v-model="newEpisode.title" :label="$strings.LabelTitle" trim-whitespace />
|
||||
</div>
|
||||
<div class="w-full p-1">
|
||||
<ui-textarea-with-label v-model="newEpisode.subtitle" :label="$strings.LabelSubtitle" :rows="3" />
|
||||
<ui-textarea-with-label v-model="newEpisode.subtitle" :label="$strings.LabelSubtitle" :rows="3" trim-whitespace />
|
||||
</div>
|
||||
<div class="w-full p-1">
|
||||
<ui-rich-text-editor :label="$strings.LabelDescription" v-model="newEpisode.description" />
|
||||
|
||||
@@ -215,6 +215,10 @@ export default {
|
||||
inputBlur() {
|
||||
if (!this.isFocused) return
|
||||
|
||||
if (typeof this.textInput === 'string') {
|
||||
this.textInput = this.textInput.trim()
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (document.activeElement === this.$refs.input) {
|
||||
return
|
||||
@@ -231,6 +235,11 @@ export default {
|
||||
},
|
||||
forceBlur() {
|
||||
this.isFocused = false
|
||||
|
||||
if (typeof this.textInput === 'string') {
|
||||
this.textInput = this.textInput.trim()
|
||||
}
|
||||
|
||||
if (this.textInput) this.submitForm()
|
||||
if (this.$refs.input) this.$refs.input.blur()
|
||||
},
|
||||
@@ -289,11 +298,12 @@ export default {
|
||||
this.selectedMenuItemIndex = null
|
||||
},
|
||||
submitForm() {
|
||||
if (!this.textInput) return
|
||||
if (!this.textInput || !this.textInput.trim?.()) return
|
||||
|
||||
this.textInput = this.textInput.trim()
|
||||
|
||||
const cleaned = this.textInput.trim()
|
||||
const matchesItem = this.items.find((i) => {
|
||||
return i.name === cleaned
|
||||
return i.name === this.textInput
|
||||
})
|
||||
|
||||
if (matchesItem) {
|
||||
|
||||
@@ -40,7 +40,8 @@ export default {
|
||||
showCopy: Boolean,
|
||||
step: [String, Number],
|
||||
min: [String, Number],
|
||||
customInputClass: String
|
||||
customInputClass: String,
|
||||
trimWhitespace: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -101,9 +102,13 @@ export default {
|
||||
this.$emit('focus')
|
||||
},
|
||||
blurred() {
|
||||
if (this.trimWhitespace && typeof this.inputValue === 'string') {
|
||||
this.inputValue = this.inputValue.trim()
|
||||
}
|
||||
this.isFocused = false
|
||||
this.$emit('blur')
|
||||
},
|
||||
|
||||
change(e) {
|
||||
this.$emit('change', e.target.value)
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<em v-if="note" class="font-normal text-xs pl-2">{{ note }}</em>
|
||||
</label>
|
||||
</slot>
|
||||
<ui-text-input :placeholder="placeholder || label" :inputId="identifier" ref="input" v-model="inputValue" :disabled="disabled" :readonly="readonly" :type="type" :show-copy="showCopy" class="w-full" :class="inputClass" @blur="inputBlurred" />
|
||||
<ui-text-input :placeholder="placeholder || label" :inputId="identifier" ref="input" v-model="inputValue" :disabled="disabled" :readonly="readonly" :type="type" :show-copy="showCopy" class="w-full" :class="inputClass" :trim-whitespace="trimWhitespace" @blur="inputBlurred" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -24,7 +24,8 @@ export default {
|
||||
readonly: Boolean,
|
||||
disabled: Boolean,
|
||||
inputClass: String,
|
||||
showCopy: Boolean
|
||||
showCopy: Boolean,
|
||||
trimWhitespace: Boolean
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
|
||||
@@ -351,8 +351,10 @@ export default {
|
||||
background-color: white;
|
||||
}
|
||||
trix-editor {
|
||||
max-height: calc(4 * 1lh);
|
||||
height: calc(4 * 1lh);
|
||||
min-height: calc(4 * 1lh);
|
||||
overflow-y: auto;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
trix-editor * {
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
<form class="w-full h-full px-2 md:px-4 py-6" @submit.prevent="submitForm">
|
||||
<div class="flex flex-wrap -mx-1">
|
||||
<div class="w-full md:w-1/2 px-1">
|
||||
<ui-text-input-with-label ref="titleInput" v-model="details.title" :label="$strings.LabelTitle" @input="handleInputChange" />
|
||||
<ui-text-input-with-label ref="titleInput" v-model="details.title" :label="$strings.LabelTitle" trim-whitespace @input="handleInputChange" />
|
||||
</div>
|
||||
<div class="flex-grow px-1 mt-2 md:mt-0">
|
||||
<ui-text-input-with-label ref="subtitleInput" v-model="details.subtitle" :label="$strings.LabelSubtitle" @input="handleInputChange" />
|
||||
<ui-text-input-with-label ref="subtitleInput" v-model="details.subtitle" :label="$strings.LabelSubtitle" trim-whitespace @input="handleInputChange" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -42,19 +42,19 @@
|
||||
<ui-multi-select ref="narratorsSelect" v-model="details.narrators" :label="$strings.LabelNarrators" :items="narrators" @input="handleInputChange" />
|
||||
</div>
|
||||
<div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0">
|
||||
<ui-text-input-with-label ref="isbnInput" v-model="details.isbn" label="ISBN" @input="handleInputChange" />
|
||||
<ui-text-input-with-label ref="isbnInput" v-model="details.isbn" label="ISBN" trim-whitespace @input="handleInputChange" />
|
||||
</div>
|
||||
<div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0">
|
||||
<ui-text-input-with-label ref="asinInput" v-model="details.asin" label="ASIN" @input="handleInputChange" />
|
||||
<ui-text-input-with-label ref="asinInput" v-model="details.asin" label="ASIN" trim-whitespace @input="handleInputChange" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mt-2 -mx-1">
|
||||
<div class="w-full md:w-1/4 px-1">
|
||||
<ui-text-input-with-label ref="publisherInput" v-model="details.publisher" :label="$strings.LabelPublisher" @input="handleInputChange" />
|
||||
<ui-text-input-with-label ref="publisherInput" v-model="details.publisher" :label="$strings.LabelPublisher" trim-whitespace @input="handleInputChange" />
|
||||
</div>
|
||||
<div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0">
|
||||
<ui-text-input-with-label ref="languageInput" v-model="details.language" :label="$strings.LabelLanguage" @input="handleInputChange" />
|
||||
<ui-text-input-with-label ref="languageInput" v-model="details.language" :label="$strings.LabelLanguage" trim-whitespace @input="handleInputChange" />
|
||||
</div>
|
||||
<div class="flex-grow px-1 pt-6 mt-2 md:mt-0">
|
||||
<div class="flex justify-center">
|
||||
|
||||
@@ -124,6 +124,7 @@ export default {
|
||||
this.updateSelectionMode(false)
|
||||
},
|
||||
editEpisode({ libraryItem, episode }) {
|
||||
this.$store.commit('setEpisodeTableEpisodeIds', [episode.id])
|
||||
this.$store.commit('setSelectedLibraryItem', libraryItem)
|
||||
this.$store.commit('globals/setSelectedEpisode', episode)
|
||||
this.$store.commit('globals/setShowEditPodcastEpisodeModal', true)
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
<form class="w-full h-full px-4 py-6" @submit.prevent="submitForm">
|
||||
<div class="flex -mx-1">
|
||||
<div class="w-1/2 px-1">
|
||||
<ui-text-input-with-label ref="titleInput" v-model="details.title" :label="$strings.LabelTitle" @input="handleInputChange" />
|
||||
<ui-text-input-with-label ref="titleInput" v-model="details.title" :label="$strings.LabelTitle" trim-whitespace @input="handleInputChange" />
|
||||
</div>
|
||||
<div class="flex-grow px-1">
|
||||
<ui-text-input-with-label ref="authorInput" v-model="details.author" :label="$strings.LabelAuthor" @input="handleInputChange" />
|
||||
<ui-text-input-with-label ref="authorInput" v-model="details.author" :label="$strings.LabelAuthor" trim-whitespace @input="handleInputChange" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ui-text-input-with-label ref="feedUrlInput" v-model="details.feedUrl" :label="$strings.LabelRSSFeedURL" class="mt-2" @input="handleInputChange" />
|
||||
<ui-text-input-with-label ref="feedUrlInput" v-model="details.feedUrl" :label="$strings.LabelRSSFeedURL" trim-whitespace class="mt-2" @input="handleInputChange" />
|
||||
|
||||
<ui-textarea-with-label ref="descriptionInput" v-model="details.description" :rows="3" :label="$strings.LabelDescription" class="mt-2" @input="handleInputChange" />
|
||||
|
||||
@@ -25,13 +25,13 @@
|
||||
|
||||
<div class="flex mt-2 -mx-1">
|
||||
<div class="w-1/4 px-1">
|
||||
<ui-text-input-with-label ref="releaseDateInput" v-model="details.releaseDate" :label="$strings.LabelReleaseDate" @input="handleInputChange" />
|
||||
<ui-text-input-with-label ref="releaseDateInput" v-model="details.releaseDate" :label="$strings.LabelReleaseDate" trim-whitespace @input="handleInputChange" />
|
||||
</div>
|
||||
<div class="w-1/4 px-1">
|
||||
<ui-text-input-with-label ref="itunesIdInput" v-model="details.itunesId" label="iTunes ID" @input="handleInputChange" />
|
||||
<ui-text-input-with-label ref="itunesIdInput" v-model="details.itunesId" label="iTunes ID" trim-whitespace @input="handleInputChange" />
|
||||
</div>
|
||||
<div class="w-1/4 px-1">
|
||||
<ui-text-input-with-label ref="languageInput" v-model="details.language" :label="$strings.LabelLanguage" @input="handleInputChange" />
|
||||
<ui-text-input-with-label ref="languageInput" v-model="details.language" :label="$strings.LabelLanguage" trim-whitespace @input="handleInputChange" />
|
||||
</div>
|
||||
<div class="flex-grow px-1 pt-6">
|
||||
<div class="flex justify-center">
|
||||
|
||||
4
client/package-lock.json
generated
4
client/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "2.18.1",
|
||||
"version": "2.19.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "2.18.1",
|
||||
"version": "2.19.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@nuxtjs/axios": "^5.13.6",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "2.18.1",
|
||||
"version": "2.19.0",
|
||||
"buildNumber": 1,
|
||||
"description": "Self-hosted audiobook and podcast client",
|
||||
"main": "index.js",
|
||||
|
||||
@@ -414,11 +414,8 @@ export default {
|
||||
|
||||
const audioEl = this.audioEl || document.createElement('audio')
|
||||
var src = audioTrack.contentUrl + `?token=${this.userToken}`
|
||||
if (this.$isDev) {
|
||||
src = `${process.env.serverUrl}${src}`
|
||||
}
|
||||
|
||||
audioEl.src = src
|
||||
audioEl.src = `${process.env.serverUrl}${src}`
|
||||
audioEl.id = 'chapter-audio'
|
||||
document.body.appendChild(audioEl)
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<div v-if="openMapOptions" class="flex flex-wrap">
|
||||
<div v-if="!isPodcastLibrary && !isMapAppend" class="flex items-center px-4 h-18 w-1/2">
|
||||
<ui-checkbox v-model="selectedBatchUsage.subtitle" />
|
||||
<ui-text-input-with-label ref="subtitleInput" v-model="batchDetails.subtitle" :disabled="!selectedBatchUsage.subtitle" :label="$strings.LabelSubtitle" class="mb-5 ml-4" />
|
||||
<ui-text-input-with-label ref="subtitleInput" v-model="batchDetails.subtitle" :disabled="!selectedBatchUsage.subtitle" :label="$strings.LabelSubtitle" trim-whitespace class="mb-5 ml-4" />
|
||||
</div>
|
||||
<div v-if="!isPodcastLibrary" class="flex items-center px-4 h-18 w-1/2">
|
||||
<ui-checkbox v-model="selectedBatchUsage.authors" />
|
||||
@@ -31,7 +31,7 @@
|
||||
</div>
|
||||
<div v-if="!isPodcastLibrary && !isMapAppend" class="flex items-center px-4 h-18 w-1/2">
|
||||
<ui-checkbox v-model="selectedBatchUsage.publishedYear" />
|
||||
<ui-text-input-with-label ref="publishedYearInput" v-model="batchDetails.publishedYear" :disabled="!selectedBatchUsage.publishedYear" :label="$strings.LabelPublishYear" class="mb-5 ml-4" />
|
||||
<ui-text-input-with-label ref="publishedYearInput" v-model="batchDetails.publishedYear" :disabled="!selectedBatchUsage.publishedYear" :label="$strings.LabelPublishYear" trim-whitespace class="mb-5 ml-4" />
|
||||
</div>
|
||||
<div v-if="!isPodcastLibrary" class="flex items-center px-4 h-18 w-1/2">
|
||||
<ui-checkbox v-model="selectedBatchUsage.series" />
|
||||
@@ -51,11 +51,11 @@
|
||||
</div>
|
||||
<div v-if="!isPodcastLibrary && !isMapAppend" class="flex items-center px-4 h-18 w-1/2">
|
||||
<ui-checkbox v-model="selectedBatchUsage.publisher" />
|
||||
<ui-text-input-with-label ref="publisherInput" v-model="batchDetails.publisher" :disabled="!selectedBatchUsage.publisher" :label="$strings.LabelPublisher" class="mb-5 ml-4" />
|
||||
<ui-text-input-with-label ref="publisherInput" v-model="batchDetails.publisher" :disabled="!selectedBatchUsage.publisher" :label="$strings.LabelPublisher" trim-whitespace class="mb-5 ml-4" />
|
||||
</div>
|
||||
<div v-if="!isMapAppend" class="flex items-center px-4 h-18 w-1/2">
|
||||
<ui-checkbox v-model="selectedBatchUsage.language" />
|
||||
<ui-text-input-with-label ref="languageInput" v-model="batchDetails.language" :disabled="!selectedBatchUsage.language" :label="$strings.LabelLanguage" class="mb-5 ml-4" />
|
||||
<ui-text-input-with-label ref="languageInput" v-model="batchDetails.language" :disabled="!selectedBatchUsage.language" :label="$strings.LabelLanguage" trim-whitespace class="mb-5 ml-4" />
|
||||
</div>
|
||||
<div v-if="!isMapAppend" class="flex items-center px-4 h-18 w-1/2">
|
||||
<ui-checkbox v-model="selectedBatchUsage.explicit" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default class AudioTrack {
|
||||
constructor(track, userToken) {
|
||||
constructor(track, userToken, routerBasePath) {
|
||||
this.index = track.index || 0
|
||||
this.startOffset = track.startOffset || 0 // Total time of all previous tracks
|
||||
this.duration = track.duration || 0
|
||||
@@ -9,20 +9,27 @@ export default class AudioTrack {
|
||||
this.metadata = track.metadata || {}
|
||||
|
||||
this.userToken = userToken
|
||||
this.routerBasePath = routerBasePath || ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for CastPlayer
|
||||
*/
|
||||
get fullContentUrl() {
|
||||
if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return `${process.env.serverUrl}${this.contentUrl}?token=${this.userToken}`
|
||||
}
|
||||
return `${window.location.origin}${this.contentUrl}?token=${this.userToken}`
|
||||
return `${window.location.origin}${this.routerBasePath}${this.contentUrl}?token=${this.userToken}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for LocalPlayer
|
||||
*/
|
||||
get relativeContentUrl() {
|
||||
if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl
|
||||
|
||||
return this.contentUrl + `?token=${this.userToken}`
|
||||
return `${this.routerBasePath}${this.contentUrl}?token=${this.userToken}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ export default class PlayerHandler {
|
||||
|
||||
console.log('[PlayerHandler] Preparing Session', session)
|
||||
|
||||
var audioTracks = session.audioTracks.map((at) => new AudioTrack(at, this.userToken))
|
||||
var audioTracks = session.audioTracks.map((at) => new AudioTrack(at, this.userToken, this.ctx.$config.routerBasePath))
|
||||
|
||||
this.ctx.playerLoading = true
|
||||
this.isHlsTranscode = true
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
"ButtonAddLibrary": "Tilføj Bibliotek",
|
||||
"ButtonAddPodcasts": "Tilføj podcasts",
|
||||
"ButtonAddUser": "Tilføj bruger",
|
||||
"ButtonAddYourFirstLibrary": "Tilføj din første bibliotek",
|
||||
"ButtonAddYourFirstLibrary": "Tilføj dit første bibliotek",
|
||||
"ButtonApply": "Anvend",
|
||||
"ButtonApplyChapters": "Anvend kapitler",
|
||||
"ButtonAuthors": "Forfattere",
|
||||
"ButtonBack": "Tilbage",
|
||||
"ButtonBatchEditPopulateFromExisting": "Opret fra eksisterende",
|
||||
"ButtonBatchEditPopulateMapDetails": "Opret fra kortlægnings detaljer",
|
||||
"ButtonBrowseForFolder": "Gennemse mappe",
|
||||
"ButtonCancel": "Annuller",
|
||||
"ButtonCancelEncode": "Annuller kodning",
|
||||
@@ -91,7 +93,7 @@
|
||||
"ButtonScrollLeft": "Rul til Venstre",
|
||||
"ButtonScrollRight": "Rul til Højre",
|
||||
"ButtonSearch": "Søg",
|
||||
"ButtonSelectFolderPath": "Vælg Mappen Sti",
|
||||
"ButtonSelectFolderPath": "Vælg Mappe Sti",
|
||||
"ButtonSeries": "Serier",
|
||||
"ButtonSetChaptersFromTracks": "Sæt kapitler fra spor",
|
||||
"ButtonShare": "Del",
|
||||
@@ -213,7 +215,7 @@
|
||||
"LabelAbridgedChecked": "Forkortet (kontrolleret)",
|
||||
"LabelAbridgedUnchecked": "Uforkortet (ikke kontrolleret)",
|
||||
"LabelAccessibleBy": "Tilgængelig af",
|
||||
"LabelAccountType": "Kontotype",
|
||||
"LabelAccountType": "Brugertype",
|
||||
"LabelAccountTypeAdmin": "Administrator",
|
||||
"LabelAccountTypeGuest": "Gæst",
|
||||
"LabelAccountTypeUser": "Bruger",
|
||||
@@ -224,7 +226,7 @@
|
||||
"LabelAddToPlaylistBatch": "Tilføj {0} Elementer til Afspilningsliste",
|
||||
"LabelAddedAt": "Tilføjet",
|
||||
"LabelAddedDate": "Tilføjet {0}",
|
||||
"LabelAdminUsersOnly": "Kun Administratorbrugere",
|
||||
"LabelAdminUsersOnly": "Kun Administratorer",
|
||||
"LabelAll": "Alle",
|
||||
"LabelAllUsers": "Alle Brugere",
|
||||
"LabelAllUsersExcludingGuests": "Alle bruger eksklusiv gæster",
|
||||
@@ -443,7 +445,7 @@
|
||||
"LabelNarrator": "Fortæller",
|
||||
"LabelNarrators": "Fortællere",
|
||||
"LabelNew": "Ny",
|
||||
"LabelNewPassword": "Nyt kodeord",
|
||||
"LabelNewPassword": "Ny adgangskode",
|
||||
"LabelNewestAuthors": "Nyeste forfattere",
|
||||
"LabelNewestEpisodes": "Nyeste episoder",
|
||||
"LabelNextBackupDate": "Næste sikkerhedskopi dato",
|
||||
@@ -465,12 +467,12 @@
|
||||
"LabelNumberOfBooks": "Antal bøger",
|
||||
"LabelNumberOfEpisodes": "# afsnit",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "Navnet af OpenID claimet som indeholder avancerede brugerhandlinger inden i applikationen som vil gælde for ikke administrative roller (<b>hvis konfigureret</b>). Hvis et claim mangler fra svaret vil adgang til ABS blive nægtet. Hvis en enkelt indstilling/option mangler, vil det bliver behandlet som <code>false</code>. Sørg for at identity provider's claim matcher den forventede struktur:",
|
||||
"LabelOpenIDClaims": "Efterlad de følgende indstillinger tomme for at deaktivere avancerede grupper og adgangsstyring for automatisk at tilføje dem til 'User' gruppen.",
|
||||
"LabelOpenIDClaims": "Efterlad de følgende indstillinger tomme for at deaktivere avanceret gruppe og adgangsindstilling, ved automatisk at assigne 'Bruger' grupper.",
|
||||
"LabelOpenIDGroupClaimDescription": "Navnet af det OpenID claim som skal indeholde brugerens grupper. Mest kendt som <code>groups</code>. <b>hvis konfigureret</b>, vil applikationen automatiske tildele roller baseret p[ brugerens gruppemedlemsskaber, givet disse grupper er navngivet (uden forbehold for store og små bogstaver) 'admin', 'user' eller 'guest' i claimet. Claimet burde indeholde en liste (og hvis brugeren tilhøre flere grupper) som applikationen vil tildele roller med højeste adgangsnvieau. Hvis ingen grupper matcher vil adgang blive nægtet.",
|
||||
"LabelOpenRSSFeed": "Åbn RSS-feed",
|
||||
"LabelOverwrite": "Overskriv",
|
||||
"LabelPaginationPageXOfY": "Side {0} af {1}",
|
||||
"LabelPassword": "Kodeord",
|
||||
"LabelPassword": "Adgangskode",
|
||||
"LabelPath": "Sti",
|
||||
"LabelPermanent": "Permanent",
|
||||
"LabelPermissionsAccessAllLibraries": "Kan få adgang til alle biblioteker",
|
||||
@@ -484,6 +486,7 @@
|
||||
"LabelPersonalYearReview": "Dit år i review ({0})",
|
||||
"LabelPhotoPathURL": "Foto sti/URL",
|
||||
"LabelPlayMethod": "Afspilningsmetode",
|
||||
"LabelPlaybackRateIncrementDecrement": "Afspilningshastighed øges/sænkes med",
|
||||
"LabelPlayerChapterNumberMarker": "{0} af {1}",
|
||||
"LabelPlaylists": "Afspilningslister",
|
||||
"LabelPodcast": "Podcast",
|
||||
@@ -573,12 +576,12 @@
|
||||
"LabelSettingsLibraryMarkAsFinishedWhen": "Marker medie indhold som færdigt når",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Spring til tidligere bøger i Fortsæt serie",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Fortsæt Serien siden hylde viser de første bøger som ikke er startet i serier med mindst en bog som ikke er startet og ingen bøger i gang. Aktivering af denne indstilling vil fortsætte serien fra den sidst gennemførte bog modsat den først ikke startede bog.",
|
||||
"LabelSettingsParseSubtitles": "Fortolk undertekster",
|
||||
"LabelSettingsParseSubtitles": "Fortolk undertitler",
|
||||
"LabelSettingsParseSubtitlesHelp": "Udtræk undertekster fra lydbogsmappenavne.<br>Undertitler skal adskilles af \" - \"<br>f.eks. \"Bogtitel - En undertitel her\" har undertitlen \"En undertitel her\"",
|
||||
"LabelSettingsPreferMatchedMetadata": "Foretræk matchede metadata",
|
||||
"LabelSettingsPreferMatchedMetadataHelp": "Matchede data vil tilsidesætte elementdetaljer ved brug af Hurtig Match. Som standard udfylder Hurtig Match kun manglende detaljer.",
|
||||
"LabelSettingsSkipMatchingBooksWithASIN": "Spring over matchende bøger, der allerede har en ASIN",
|
||||
"LabelSettingsSkipMatchingBooksWithISBN": "Spring over matchende bøger, der allerede har en ISBN",
|
||||
"LabelSettingsSkipMatchingBooksWithISBN": "Spring matchende bøger over, som allerede har et ISBN-nummer",
|
||||
"LabelSettingsSortingIgnorePrefixes": "Ignorer præfikser ved sortering",
|
||||
"LabelSettingsSortingIgnorePrefixesHelp": "f.eks. for præfikset \"the\" vil bogtitlen \"The Book Title\" blive sorteret som \"Book Title, The\"",
|
||||
"LabelSettingsSquareBookCovers": "Brug kvadratiske bogomslag",
|
||||
@@ -662,7 +665,7 @@
|
||||
"LabelTrailer": "Trailer",
|
||||
"LabelType": "Type",
|
||||
"LabelUnabridged": "Uforkortet",
|
||||
"LabelUndo": "Undo",
|
||||
"LabelUndo": "Fortryd",
|
||||
"LabelUnknown": "Ukendt",
|
||||
"LabelUnknownPublishDate": "Ukendt publiceringsdato",
|
||||
"LabelUpdateCover": "Opdater omslag",
|
||||
@@ -704,8 +707,11 @@
|
||||
"MessageBackupsLocationEditNote": "Note: Opdatering af backup sti vil ikke fjerne eller modificere eksisterende backups",
|
||||
"MessageBackupsLocationNoEditNote": "Note: Backup sti er sat igennem miljøvariabel og kan ikke ændres her.",
|
||||
"MessageBackupsLocationPathEmpty": "Backup sti kan ikke være tom",
|
||||
"MessageBatchEditPopulateMapDetailsAllHelp": "Opret felter slået til med data fra alle genstande. Felter med flere værdier vil blive sammenflettet",
|
||||
"MessageBatchEditPopulateMapDetailsItemHelp": "Opret kort med værdier der er slået til fra felter med data fra denne genstand",
|
||||
"MessageBatchQuickMatchDescription": "Quick Match vil forsøge at tilføje manglende omslag og metadata til de valgte elementer. Aktivér indstillingerne nedenfor for at tillade Quick Match at overskrive eksisterende omslag og/eller metadata.",
|
||||
"MessageBookshelfNoCollections": "Du har ikke oprettet nogen samlinger endnu",
|
||||
"MessageBookshelfNoCollectionsHelp": "Samlinger er offentlige. Alle brugere med adgang til biblioteket kan se dem.",
|
||||
"MessageBookshelfNoRSSFeeds": "Ingen RSS-feeds er åbne",
|
||||
"MessageBookshelfNoResultsForFilter": "Ingen resultater for filter \"{0}: {1}\"",
|
||||
"MessageBookshelfNoResultsForQuery": "Intet resultat for query",
|
||||
@@ -816,6 +822,7 @@
|
||||
"MessageNoTasksRunning": "Ingen opgaver kører",
|
||||
"MessageNoUpdatesWereNecessary": "Ingen opdateringer var nødvendige",
|
||||
"MessageNoUserPlaylists": "Du har ingen afspilningslister",
|
||||
"MessageNoUserPlaylistsHelp": "Playlister er private. Kun brugere som opretter dem kan se dem.",
|
||||
"MessageNotYetImplemented": "Endnu ikke implementeret",
|
||||
"MessageOpmlPreviewNote": "Note: Dette er en forhåndsvisning af den indlæste OPML fil. Podcast titel vil blive taget fra RSS feedet.",
|
||||
"MessageOr": "eller",
|
||||
|
||||
@@ -645,7 +645,7 @@
|
||||
"LabelTimeToShift": "Zeit bis zum Wechsel in Sekunden",
|
||||
"LabelTitle": "Titel",
|
||||
"LabelToolsEmbedMetadata": "Metadaten einbetten",
|
||||
"LabelToolsEmbedMetadataDescription": "Bettet die Metadaten einschließlich des Titelbildes und der Kapitel in die Audiodatein ein.",
|
||||
"LabelToolsEmbedMetadataDescription": "Bettet die Metadaten einschließlich des Titelbildes und der Kapitel in die Audiodateien ein.",
|
||||
"LabelToolsM4bEncoder": "M4B Kodierer",
|
||||
"LabelToolsMakeM4b": "M4B-Datei erstellen",
|
||||
"LabelToolsMakeM4bDescription": "Erstellt eine M4B-Datei (Endung \".m4b\") welche mehrere mp3-Dateien in einer einzigen Datei inkl. derer Metadaten (Beschreibung, Titelbild, Kapitel, ...) zusammenfasst. M4B-Datei können darüber hinaus Lesezeichen speichern und mit einem Abspielschutz (Passwort) versehen werden.",
|
||||
|
||||
@@ -711,6 +711,7 @@
|
||||
"MessageBatchEditPopulateMapDetailsItemHelp": "Populate enabled map details fields with data from this item",
|
||||
"MessageBatchQuickMatchDescription": "Quick Match will attempt to add missing covers and metadata for the selected items. Enable the options below to allow Quick Match to overwrite existing covers and/or metadata.",
|
||||
"MessageBookshelfNoCollections": "You haven't made any collections yet",
|
||||
"MessageBookshelfNoCollectionsHelp": "Collections are public. All users with access to the library can see them.",
|
||||
"MessageBookshelfNoRSSFeeds": "No RSS feeds are open",
|
||||
"MessageBookshelfNoResultsForFilter": "No results for filter \"{0}: {1}\"",
|
||||
"MessageBookshelfNoResultsForQuery": "No results for query",
|
||||
@@ -821,6 +822,7 @@
|
||||
"MessageNoTasksRunning": "No Tasks Running",
|
||||
"MessageNoUpdatesWereNecessary": "No updates were necessary",
|
||||
"MessageNoUserPlaylists": "You have no playlists",
|
||||
"MessageNoUserPlaylistsHelp": "Playlists are private. Only the user who creates them can see them.",
|
||||
"MessageNotYetImplemented": "Not yet implemented",
|
||||
"MessageOpmlPreviewNote": "Note: This is a preview of the parsed OPML file. The actual podcast title will be taken from the RSS feed.",
|
||||
"MessageOr": "or",
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
"ButtonApplyChapters": "Appliquer aux chapitres",
|
||||
"ButtonAuthors": "Auteurs",
|
||||
"ButtonBack": "Retour",
|
||||
"ButtonBatchEditPopulateFromExisting": "Remplir à partir de l'existant",
|
||||
"ButtonBatchEditPopulateMapDetails": "Remplir les détails de la carte",
|
||||
"ButtonBrowseForFolder": "Naviguer vers le répertoire",
|
||||
"ButtonCancel": "Annuler",
|
||||
"ButtonCancelEncode": "Annuler l’encodage",
|
||||
@@ -484,6 +486,7 @@
|
||||
"LabelPersonalYearReview": "Bilan de l’année ({0})",
|
||||
"LabelPhotoPathURL": "Chemin / URL des photos",
|
||||
"LabelPlayMethod": "Méthode d’écoute",
|
||||
"LabelPlaybackRateIncrementDecrement": "Augmentation/Diminition de la vitesse de lecture",
|
||||
"LabelPlayerChapterNumberMarker": "{0} sur {1}",
|
||||
"LabelPlaylists": "Listes de lecture",
|
||||
"LabelPodcast": "Podcast",
|
||||
@@ -704,6 +707,7 @@
|
||||
"MessageBackupsLocationEditNote": "Remarque : Mettre à jour l'emplacement de sauvegarde ne déplacera pas ou ne modifiera pas les sauvegardes existantes",
|
||||
"MessageBackupsLocationNoEditNote": "Remarque : l’emplacement de sauvegarde est défini via une variable d’environnement et ne peut pas être modifié ici.",
|
||||
"MessageBackupsLocationPathEmpty": "L'emplacement de secours ne peut pas être vide",
|
||||
"MessageBatchEditPopulateMapDetailsAllHelp": "Remplir les champs disponibles avec les données de tous les éléments. les champs avec des valeurs multiples seront fusionnés",
|
||||
"MessageBatchQuickMatchDescription": "La recherche par correspondance rapide tentera d’ajouter les couvertures et métadonnées manquantes pour les éléments sélectionnés. Activez les options ci-dessous pour permettre la Recherche par correspondance d’écraser les couvertures et/ou métadonnées existantes.",
|
||||
"MessageBookshelfNoCollections": "Vous n’avez pas encore de collections",
|
||||
"MessageBookshelfNoRSSFeeds": "Aucun flux RSS n’est ouvert",
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
"ButtonApplyChapters": "Primijeni poglavlja",
|
||||
"ButtonAuthors": "Autori",
|
||||
"ButtonBack": "Natrag",
|
||||
"ButtonBatchEditPopulateFromExisting": "Popuni iz postojećeg",
|
||||
"ButtonBatchEditPopulateMapDetails": "Popuni mapirane pojedinosti",
|
||||
"ButtonBrowseForFolder": "Pronađi mapu",
|
||||
"ButtonCancel": "Odustani",
|
||||
"ButtonCancelEncode": "Otkaži kodiranje",
|
||||
@@ -288,7 +290,7 @@
|
||||
"LabelCustomCronExpression": "Prilagođeni CRON izraz:",
|
||||
"LabelDatetime": "Datum i vrijeme",
|
||||
"LabelDays": "Dani",
|
||||
"LabelDeleteFromFileSystemCheckbox": "Izbriši datoteke (uklonite oznaku ako stavku želite izbrisati samo iz baze podataka)",
|
||||
"LabelDeleteFromFileSystemCheckbox": "Izbriši datoteke (uklonite kvačicu ako stavku želite izbrisati samo iz baze podataka)",
|
||||
"LabelDescription": "Opis",
|
||||
"LabelDeselectAll": "Odznači sve",
|
||||
"LabelDevice": "Uređaj",
|
||||
@@ -399,7 +401,7 @@
|
||||
"LabelLastBookAdded": "Zadnja dodana knjiga",
|
||||
"LabelLastBookUpdated": "Zadnja ažurirana knjiga",
|
||||
"LabelLastSeen": "Zadnji puta viđen",
|
||||
"LabelLastTime": "Zadnji puta",
|
||||
"LabelLastTime": "Zadnje vrijeme",
|
||||
"LabelLastUpdate": "Zadnje ažuriranje",
|
||||
"LabelLayout": "Prikaz",
|
||||
"LabelLayoutSinglePage": "Jedna stranica",
|
||||
@@ -484,6 +486,7 @@
|
||||
"LabelPersonalYearReview": "Vaš godišnji pregled ({0})",
|
||||
"LabelPhotoPathURL": "Putanja ili URL fotografije",
|
||||
"LabelPlayMethod": "Način reprodukcije",
|
||||
"LabelPlaybackRateIncrementDecrement": "Korak povećanja/smanjenja brzine reprodukcije",
|
||||
"LabelPlayerChapterNumberMarker": "{0} od {1}",
|
||||
"LabelPlaylists": "Popisi za izvođenje",
|
||||
"LabelPodcast": "Podcast",
|
||||
@@ -704,8 +707,11 @@
|
||||
"MessageBackupsLocationEditNote": "Napomena: Uređivanje lokacije za sigurnosne kopije ne premješta ili mijenja postojeće sigurnosne kopije",
|
||||
"MessageBackupsLocationNoEditNote": "Napomena: Lokacija za sigurnosne kopije zadana je kroz varijablu okoline i ovdje se ne može izmijeniti.",
|
||||
"MessageBackupsLocationPathEmpty": "Putanja do lokacije za sigurnosne kopije ne može ostati prazna",
|
||||
"MessageBatchEditPopulateMapDetailsAllHelp": "Nadopunjuje omogućena polja podatcima iz svih stavki. Polja s višestrukim podatcima će se spojiti",
|
||||
"MessageBatchEditPopulateMapDetailsItemHelp": "Popuni omogućena polja mapiranih pojedinosti s podatcima iz ove stavke",
|
||||
"MessageBatchQuickMatchDescription": "Brzo prepoznavanje za odabrane će stavke pokušati dodati naslovnice i meta-podatke koji nedostaju. Uključite donje opcije ako želite da Brzo prepoznavanje prepiše postojeće naslovnice i/ili meta-podatke.",
|
||||
"MessageBookshelfNoCollections": "Niste izradili niti jednu zbirku",
|
||||
"MessageBookshelfNoCollectionsHelp": "Zbirke su javne. Svi korisnici s pristupom knjižnici mogu ih vidjeti.",
|
||||
"MessageBookshelfNoRSSFeeds": "Nema otvorenih RSS izvora",
|
||||
"MessageBookshelfNoResultsForFilter": "Nema rezultata za filter \"{0}: {1}\"",
|
||||
"MessageBookshelfNoResultsForQuery": "Vaš upit nema rezultata",
|
||||
@@ -721,7 +727,7 @@
|
||||
"MessageConfirmDeleteDevice": "Sigurno želite izbrisati e-čitač \"{0}\"?",
|
||||
"MessageConfirmDeleteFile": "Ovo će izbrisati datoteke s datotečnog sustava. Jeste li sigurni?",
|
||||
"MessageConfirmDeleteLibrary": "Sigurno želite trajno izbrisati knjižnicu \"{0}\"?",
|
||||
"MessageConfirmDeleteLibraryItem": "Ovo će izbrisati knjižničku stavku iz datoteke i vašeg datotečnog sustava. Jeste li sigurni?",
|
||||
"MessageConfirmDeleteLibraryItem": "Ovo će izbrisati knjižničku stavku iz baze podataka i s datotečnog sustava. Jeste li sigurni?",
|
||||
"MessageConfirmDeleteLibraryItems": "Ovo će izbrisati {0} knjižničkih stavki iz baze podataka i datotečnog sustava. Jeste li sigurni?",
|
||||
"MessageConfirmDeleteMetadataProvider": "Sigurno želite izbrisati prilagođenog pružatelja meta-podataka \"{0}\"?",
|
||||
"MessageConfirmDeleteNotification": "Sigurno želite izbrisati ovu obavijest?",
|
||||
@@ -816,6 +822,7 @@
|
||||
"MessageNoTasksRunning": "Nema zadataka koji se izvode",
|
||||
"MessageNoUpdatesWereNecessary": "Ažuriranje nije bilo potrebno",
|
||||
"MessageNoUserPlaylists": "Nemate popisa za izvođenje",
|
||||
"MessageNoUserPlaylistsHelp": "Popisi za izvođenje su privatni. Može ih vidjeti samo korisnik koji ih je izradio.",
|
||||
"MessageNotYetImplemented": "Još nije implementirano",
|
||||
"MessageOpmlPreviewNote": "Napomena: Ovo je pretpregled raščlanjene OPML datoteke. Stvarni naslov podcasta preuzet će se iz RSS izvora.",
|
||||
"MessageOr": "ili",
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
{}
|
||||
{
|
||||
"ButtonAdd": "追加"
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
"ButtonApplyChapters": "Применить главы",
|
||||
"ButtonAuthors": "Авторы",
|
||||
"ButtonBack": "Назад",
|
||||
"ButtonBatchEditPopulateFromExisting": "Заполнить из существующих",
|
||||
"ButtonBatchEditPopulateMapDetails": "Заполнить данные карты",
|
||||
"ButtonBrowseForFolder": "Выбрать папку",
|
||||
"ButtonCancel": "Отмена",
|
||||
"ButtonCancelEncode": "Отменить кодирование",
|
||||
@@ -301,7 +303,7 @@
|
||||
"LabelDownload": "Скачать",
|
||||
"LabelDownloadNEpisodes": "Скачать {0} эпизодов",
|
||||
"LabelDownloadable": "Загружаемый",
|
||||
"LabelDuration": "Длина",
|
||||
"LabelDuration": "Продолжительность",
|
||||
"LabelDurationComparisonExactMatch": "(точное совпадение)",
|
||||
"LabelDurationComparisonLonger": "({0} дольше)",
|
||||
"LabelDurationComparisonShorter": "({0} короче)",
|
||||
@@ -432,7 +434,7 @@
|
||||
"LabelMetadataProvider": "Провайдер",
|
||||
"LabelMinute": "Минуты",
|
||||
"LabelMinutes": "Минуты",
|
||||
"LabelMissing": "Потеряно",
|
||||
"LabelMissing": "Отсутствует",
|
||||
"LabelMissingEbook": "Нет e-книги",
|
||||
"LabelMissingSupplementaryEbook": "Нет дополнительной e-книги",
|
||||
"LabelMobileRedirectURIs": "Разрешенные URI перенаправления с мобильных устройств",
|
||||
@@ -463,7 +465,7 @@
|
||||
"LabelNotificationsMaxQueueSize": "Макс. размер очереди для событий уведомлений",
|
||||
"LabelNotificationsMaxQueueSizeHelp": "События ограничены 1 в секунду. События будут игнорированы если в очереди максимальное количество. Это предотвращает спам сообщениями.",
|
||||
"LabelNumberOfBooks": "Количество книг",
|
||||
"LabelNumberOfEpisodes": "# Эпизодов",
|
||||
"LabelNumberOfEpisodes": "# из эпизодов",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "Имя утверждения OpenID, содержащего расширенные разрешения на действия пользователя в приложении, которые будут применяться к ролям, не являющимся администраторами (<b>если они настроены</b>). Если утверждение отсутствует в ответе, в доступе к ABS будет отказано. Если одна опция отсутствует, она будет рассматриваться как <code>false</code>. Убедитесь, что утверждение поставщика удостоверений соответствует ожидаемой структуре:",
|
||||
"LabelOpenIDClaims": "Оставьте следующие параметры пустыми, чтобы отключить расширенное назначение групп и разрешений, будет автоматически присвоена группа «Пользователь».",
|
||||
"LabelOpenIDGroupClaimDescription": "Имя утверждения OpenID, содержащего список групп пользователя. Обычно их называют <code>groups</code>. <b>Если эта настройка</b> настроена, приложение будет автоматически назначать роли на основе членства пользователя в группах при условии, что эти группы названы в утверждении без учета регистра \"admin\", \"user\" или \"guest\". Утверждение должно содержать список, и если пользователь принадлежит к нескольким группам, то приложение назначит роль, соответствующую самому высокому уровню доступа. Если ни одна из групп не совпадает, доступ будет запрещен.",
|
||||
@@ -484,6 +486,7 @@
|
||||
"LabelPersonalYearReview": "Итоги прошедшего года ({0})",
|
||||
"LabelPhotoPathURL": "Путь к фото/URL",
|
||||
"LabelPlayMethod": "Метод воспроизведения",
|
||||
"LabelPlaybackRateIncrementDecrement": "Величина увеличения/уменьшения скорости воспроизведения",
|
||||
"LabelPlayerChapterNumberMarker": "{0} из {1}",
|
||||
"LabelPlaylists": "Плейлисты",
|
||||
"LabelPodcast": "Подкаст",
|
||||
@@ -651,7 +654,7 @@
|
||||
"LabelToolsMakeM4bDescription": "Создает .M4B файл аудиокниги с встроенными метаданными, обложкой и главами.",
|
||||
"LabelToolsSplitM4b": "Разделить M4B на MP3 файлы",
|
||||
"LabelToolsSplitM4bDescription": "Создает MP3 файла из M4B, разделяет на главы с встроенными метаданными, обложкой и главами.",
|
||||
"LabelTotalDuration": "Общая длина",
|
||||
"LabelTotalDuration": "Общая продолжительность",
|
||||
"LabelTotalTimeListened": "Всего прослушано",
|
||||
"LabelTrackFromFilename": "Трек из Имени файла",
|
||||
"LabelTrackFromMetadata": "Трек из Метаданных",
|
||||
@@ -704,8 +707,11 @@
|
||||
"MessageBackupsLocationEditNote": "Примечание: Обновление местоположения резервной копии не приведет к перемещению или изменению существующих резервных копий",
|
||||
"MessageBackupsLocationNoEditNote": "Примечание: Местоположение резервного копирования задается с помощью переменной среды и не может быть изменено здесь.",
|
||||
"MessageBackupsLocationPathEmpty": "Путь к расположению резервной копии не может быть пустым",
|
||||
"MessageBatchEditPopulateMapDetailsAllHelp": "Заполнить включенные поля данными из всех элементов. Поля с несколькими значениями будут объединены",
|
||||
"MessageBatchEditPopulateMapDetailsItemHelp": "Заполнить активированные поля сведений о карте данными из этого элемента",
|
||||
"MessageBatchQuickMatchDescription": "Быстрый Поиск попытается добавить отсутствующие обложки и метаданные для выбранных элементов. Включите параметры ниже, чтобы разрешить Быстрому Поиску перезаписывать существующие обложки и/или метаданные.",
|
||||
"MessageBookshelfNoCollections": "Вы еще не создали ни одной коллекции",
|
||||
"MessageBookshelfNoCollectionsHelp": "Коллекции являются общедоступными. Все пользователи, имеющие доступ к библиотеке, могут их просматривать.",
|
||||
"MessageBookshelfNoRSSFeeds": "Нет открытых RSS-каналов",
|
||||
"MessageBookshelfNoResultsForFilter": "Нет Результатов для фильтра \"{0}: {1}\"",
|
||||
"MessageBookshelfNoResultsForQuery": "Нет результатов для запроса",
|
||||
@@ -816,6 +822,7 @@
|
||||
"MessageNoTasksRunning": "Нет выполняемых задач",
|
||||
"MessageNoUpdatesWereNecessary": "Обновления не требовались",
|
||||
"MessageNoUserPlaylists": "У вас нет плейлистов",
|
||||
"MessageNoUserPlaylistsHelp": "Списки воспроизведения являются конфиденциальными. Только пользователь, который их создает, может их видеть.",
|
||||
"MessageNotYetImplemented": "Пока не реализовано",
|
||||
"MessageOpmlPreviewNote": "Примечание: Это предварительный просмотр разобранного файла OPML. Фактическое название подкаста будет взято из RSS-канала.",
|
||||
"MessageOr": "или",
|
||||
|
||||
@@ -434,7 +434,7 @@
|
||||
"LabelMetadataProvider": "Ponudnik metapodatkov",
|
||||
"LabelMinute": "Minuta",
|
||||
"LabelMinutes": "Minute",
|
||||
"LabelMissing": "Manjkajoče",
|
||||
"LabelMissing": "Manjka",
|
||||
"LabelMissingEbook": "Nima nobene e-knjige",
|
||||
"LabelMissingSupplementaryEbook": "Nima nobene dodatne e-knjige",
|
||||
"LabelMobileRedirectURIs": "Dovoljeni mobilni preusmeritveni URI-ji",
|
||||
@@ -486,6 +486,7 @@
|
||||
"LabelPersonalYearReview": "Pregled tvojega leta ({0})",
|
||||
"LabelPhotoPathURL": "Slika pot/URL",
|
||||
"LabelPlayMethod": "Metoda predvajanja",
|
||||
"LabelPlaybackRateIncrementDecrement": "Korak povečanja/zmanjšanja hitrosti predvajanja",
|
||||
"LabelPlayerChapterNumberMarker": "{0} od {1}",
|
||||
"LabelPlaylists": "Seznami predvajanja",
|
||||
"LabelPodcast": "Podcast",
|
||||
@@ -710,6 +711,7 @@
|
||||
"MessageBatchEditPopulateMapDetailsItemHelp": "Napolni omogočena polja s podrobnostmi zemljevida s podatki iz tega elementa",
|
||||
"MessageBatchQuickMatchDescription": "Hitro ujemanje bo poskušal dodati manjkajoče naslovnice in metapodatke za izbrane elemente. Omogočite spodnje možnosti, da omogočite hitremu ujemanju, da prepiše obstoječe naslovnice in/ali metapodatke.",
|
||||
"MessageBookshelfNoCollections": "Ustvaril nisi še nobene zbirke",
|
||||
"MessageBookshelfNoCollectionsHelp": "Zbirke so javne. Vsi uporabniki z dostopom do knjižnice jih lahko vidijo.",
|
||||
"MessageBookshelfNoRSSFeeds": "Noben vir RSS ni odprt",
|
||||
"MessageBookshelfNoResultsForFilter": "Ni rezultatov za filter \"{0}: {1}\"",
|
||||
"MessageBookshelfNoResultsForQuery": "Ni rezultatov za poizvedbo",
|
||||
@@ -820,6 +822,7 @@
|
||||
"MessageNoTasksRunning": "Nobeno opravili ne teče",
|
||||
"MessageNoUpdatesWereNecessary": "Posodobitve niso bile potrebne",
|
||||
"MessageNoUserPlaylists": "Nimate seznamov predvajanja",
|
||||
"MessageNoUserPlaylistsHelp": "Seznami predvajanj so zasebni. Samo uporabniki, ki jih ustvarijo, jih lahko vidijo.",
|
||||
"MessageNotYetImplemented": "Še ni implementirano",
|
||||
"MessageOpmlPreviewNote": "Opomba: To je predogled razčlenjene datoteke OPML. Dejanski naslov podcasta bo vzet iz vira RSS.",
|
||||
"MessageOr": "ali",
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
"HeaderAccount": "Konto",
|
||||
"HeaderAddCustomMetadataProvider": "Addera egen källa för metadata",
|
||||
"HeaderAdvanced": "Avancerad",
|
||||
"HeaderAppriseNotificationSettings": "Apprise Meddelandeinställningar",
|
||||
"HeaderAppriseNotificationSettings": "Inställningar av meddelanden med Apprise",
|
||||
"HeaderAudioTracks": "Ljudspår",
|
||||
"HeaderAudiobookTools": "Hantering av ljudboksfil",
|
||||
"HeaderAuthentication": "Autentisering",
|
||||
@@ -153,6 +153,7 @@
|
||||
"HeaderMetadataToEmbed": "Metadata som kommer att adderas",
|
||||
"HeaderNewAccount": "Nytt konto",
|
||||
"HeaderNewLibrary": "Nytt bibliotek",
|
||||
"HeaderNotificationCreate": "Addera ett meddelande",
|
||||
"HeaderNotifications": "Meddelanden",
|
||||
"HeaderOpenRSSFeed": "Öppna RSS-flöde",
|
||||
"HeaderOtherFiles": "Andra filer",
|
||||
@@ -205,7 +206,7 @@
|
||||
"LabelAccountTypeUser": "Användare",
|
||||
"LabelActivity": "Aktivitet",
|
||||
"LabelAddToCollection": "Lägg till i en samling",
|
||||
"LabelAddToCollectionBatch": "Lägg till {0} böcker i en Samling",
|
||||
"LabelAddToCollectionBatch": "Lägg till {0} böcker i samlingen",
|
||||
"LabelAddToPlaylist": "Lägg till i en spellista",
|
||||
"LabelAddToPlaylistBatch": "Lägg till {0} objekt i Spellistan",
|
||||
"LabelAddedAt": "Datum adderad",
|
||||
@@ -215,7 +216,7 @@
|
||||
"LabelAllUsers": "Alla användare",
|
||||
"LabelAllUsersExcludingGuests": "Alla användare utom gäster",
|
||||
"LabelAllUsersIncludingGuests": "Alla användare inklusive gäster",
|
||||
"LabelAlreadyInYourLibrary": "Redan i din samling",
|
||||
"LabelAlreadyInYourLibrary": "Finns redan i samlingen",
|
||||
"LabelApiToken": "API-token",
|
||||
"LabelAppend": "Lägg till",
|
||||
"LabelAudioBitrate": "Bitrate för ljud (t.ex. 128k)",
|
||||
@@ -483,7 +484,7 @@
|
||||
"LabelSelectEpisodesShowing": "Välj {0} avsnitt som visas",
|
||||
"LabelSelectUsers": "Välj användare",
|
||||
"LabelSendEbookToDevice": "Skicka e-bok till...",
|
||||
"LabelSequence": "Sekvens",
|
||||
"LabelSequence": "Sekvensnummer",
|
||||
"LabelSeries": "Serier",
|
||||
"LabelSeriesName": "Serienamn",
|
||||
"LabelSeriesProgress": "Status för serier",
|
||||
@@ -637,7 +638,7 @@
|
||||
"MessageAddToPlayerQueue": "Lägg till i spellistan",
|
||||
"MessageAppriseDescription": "För att använda den här funktionen behöver du ha en instans av <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> igång eller en API som hanterar dessa begäranden. <br />Apprise API-urlen bör vara hela URL-sökvägen för att skicka meddelandet, t.ex., om din API-instans är tillgänglig på <code>http://192.168.1.1:8337</code>, bör du ange <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageBackupsDescription": "Säkerhetskopior inkluderar användare, användarnas framsteg, biblioteksobjekt, serverinställningar<br>och bilder lagrade i <code>/metadata/items</code> & <code>/metadata/authors</code>.<br>De inkluderar <strong>INTE</strong> några filer lagrade i dina biblioteksmappar.",
|
||||
"MessageBackupsLocationEditNote": "OBS: När du ändrar plats för säkerhetskopiorna så flyttas INTE gamla säkerhetskopior dit",
|
||||
"MessageBackupsLocationEditNote": "OBS: När du ändrar plats för säkerhetskopiorna så flyttas INTE gamla säkerhetskopior dit.",
|
||||
"MessageBackupsLocationNoEditNote": "OBS: Platsen där säkerhetskopiorna lagras bestäms av en central inställning och kan inte ändras här.",
|
||||
"MessageBackupsLocationPathEmpty": "Uppgiften om platsen för lagring av säkerhetskopior kan inte lämnas tom",
|
||||
"MessageBatchQuickMatchDescription": "Quick Match kommer försöka lägga till saknade omslag och metadata för de valda föremålen. Aktivera alternativen nedan för att tillåta Quick Match att överskriva befintliga omslag och/eller metadata.",
|
||||
@@ -660,8 +661,10 @@
|
||||
"MessageConfirmDeleteLibraryItem": "Detta kommer att radera objektet från databasen och ditt filsystem. Är du säker?",
|
||||
"MessageConfirmDeleteLibraryItems": "Detta kommer att radera {0} biblioteksobjekt från databasen och ditt filsystem. Är du säker?",
|
||||
"MessageConfirmDeleteMetadataProvider": "Är du säker på att du vill radera din egen källa för metadata \"{0}\"?",
|
||||
"MessageConfirmDeleteNotification": "Är du säker på att du vill radera detta meddelande?",
|
||||
"MessageConfirmDeleteSession": "Är du säker på att du vill radera detta lyssningstillfälle?",
|
||||
"MessageConfirmForceReScan": "Är du säker på att du vill tvinga omgenomsökning?",
|
||||
"MessageConfirmEmbedMetadataInAudioFiles": "Är du säker på att du vill infoga metadata i {0} ljudfiler?",
|
||||
"MessageConfirmForceReScan": "Är du säker på att du vill starta en ny skanning?",
|
||||
"MessageConfirmMarkAllEpisodesFinished": "Är du säker på att du vill markera alla avsnitt som avslutade?",
|
||||
"MessageConfirmMarkAllEpisodesNotFinished": "Är du säker på att du vill markera alla avsnitt som ej avslutade?",
|
||||
"MessageConfirmMarkItemFinished": "Är du säker på att du vill markera \"{0}\" som avslutad?",
|
||||
@@ -678,7 +681,7 @@
|
||||
"MessageConfirmRemoveEpisode": "Är du säker på att du vill ta bort avsnittet \"{0}\"?",
|
||||
"MessageConfirmRemoveEpisodes": "Är du säker på att du vill ta bort {0} avsnitt?",
|
||||
"MessageConfirmRemoveListeningSessions": "Är du säker på att du vill radera {0} lyssningstillfällen?",
|
||||
"MessageConfirmRemoveMetadataFiles": "Är du säker på att du vill radera 'metadata.{0}' filerna i alla mappar i ditt bibliotek?",
|
||||
"MessageConfirmRemoveMetadataFiles": "Är du säker på att du vill radera filerna 'metadata.{0}' i alla mappar i ditt bibliotek?",
|
||||
"MessageConfirmRemoveNarrator": "Är du säker på att du vill ta bort uppläsaren \"{0}\"?",
|
||||
"MessageConfirmRemovePlaylist": "Är du säker på att du vill ta bort din spellista \"{0}\"?",
|
||||
"MessageConfirmRenameGenre": "Är du säker på att du vill byta namn på kategorin \"{0}\" till \"{1}\" för alla objekt?",
|
||||
@@ -748,7 +751,7 @@
|
||||
"MessageOr": "eller",
|
||||
"MessagePauseChapter": "Pausa kapiteluppspelning",
|
||||
"MessagePlayChapter": "Lyssna på kapitlets början",
|
||||
"MessagePlaylistCreateFromCollection": "Skapa spellista från samling",
|
||||
"MessagePlaylistCreateFromCollection": "Skapa en spellista från samlingen",
|
||||
"MessagePleaseWait": "Vänta ett ögonblick...",
|
||||
"MessagePodcastHasNoRSSFeedForMatching": "Podcasten har ingen RSS-flödes-URL att använda för matchning",
|
||||
"MessageQuickMatchDescription": "Adderar uppgifter som saknas samt en omslagsbild från<br>första träffen i resultatet vid sökningen från '{0}'.<br>Skriver inte över befintliga uppgifter om inte<br>inställningen 'Prioritera matchad metadata' är aktiverad.",
|
||||
@@ -760,6 +763,7 @@
|
||||
"MessageResetChaptersConfirm": "Är du säker på att du vill återställa alla kapitel och ångra de ändringarna du gjort?",
|
||||
"MessageRestoreBackupConfirm": "Är du säker på att du vill läsa in säkerhetskopian som skapades den",
|
||||
"MessageRestoreBackupWarning": "Att återställa en säkerhetskopia kommer att skriva över hela databasen som finns i /config och omslagsbilder i /metadata/items & /metadata/authors.<br /><br />Säkerhetskopior ändrar inte några filer i dina biblioteksmappar. Om du har aktiverat serverinställningar för att lagra omslagskonst och metadata i dina biblioteksmappar säkerhetskopieras eller skrivs de inte över.<br /><br />Alla klienter som använder din server kommer att uppdateras automatiskt.",
|
||||
"MessageScheduleLibraryScanNote": "För de flesta användare rekommenderas att denna funktion ej aktiveras. Istället bör funktionen 'Watcher' vara aktiverad. Watcher kommer då automatiskt identifiera förändringar i biblioteket. För vissa filsystem (som t.ex. NFS) fungerar inte Watcher. Då kan schemalagda skanningar av biblioteken användas istället.",
|
||||
"MessageSearchResultsFor": "Sökresultat för",
|
||||
"MessageSelected": "{0} valda",
|
||||
"MessageServerCouldNotBeReached": "Servern kunde inte nås",
|
||||
@@ -783,15 +787,15 @@
|
||||
"MessageXLibraryIsEmpty": "Biblioteket {0} är tomt!",
|
||||
"MessageYourAudiobookDurationIsLonger": "Varaktigheten på din ljudbok är längre än den hittade varaktigheten",
|
||||
"MessageYourAudiobookDurationIsShorter": "Varaktigheten på din ljudbok är kortare än den hittade varaktigheten",
|
||||
"NoteChangeRootPassword": "Rotanvändaren är den enda användaren som kan ha ett tomt lösenord",
|
||||
"NoteChangeRootPassword": "Användaren 'root' är den enda användaren som kan vara utan lösenord",
|
||||
"NoteChapterEditorTimes": "OBS: Starttiden för första kapitlet måste vara 0:00 och starttiden för det sista kapitlet får inte överstiga ljudbokens totala varaktighet.",
|
||||
"NoteFolderPicker": "Obs: Mappar som redan är kartlagda kommer inte att visas",
|
||||
"NoteRSSFeedPodcastAppsHttps": "Varning: De flesta podcastappar kräver att RSS-flödets URL används med HTTPS",
|
||||
"NoteRSSFeedPodcastAppsPubDate": "Varning: 1 eller flera av dina avsnitt har inte ett publiceringsdatum. Vissa podcastappar kräver detta.",
|
||||
"NoteUploaderFoldersWithMediaFiles": "Mappar med flera mediefiler hanteras som separata objekt i biblioteket.",
|
||||
"NoteFolderPicker": "OBS: Mappar som redan är kopplade kommer inte att visas",
|
||||
"NoteRSSFeedPodcastAppsHttps": "VARNING: De flesta applikationer för podcasts kräver att URL:en för RSS-flödet använder HTTPS",
|
||||
"NoteRSSFeedPodcastAppsPubDate": "VARNING: Ett eller flera av dina avsnitt har inte ett publiceringsdatum. Vissa applikationer för podcasts kräver detta.",
|
||||
"NoteUploaderFoldersWithMediaFiles": "Mappar som innehåller mediefiler hanteras som separata objekt i biblioteket.",
|
||||
"NoteUploaderOnlyAudioFiles": "Om du bara laddar upp ljudfiler kommer varje ljudfil att hanteras som en separat ljudbok.",
|
||||
"NoteUploaderUnsupportedFiles": "Oaccepterade filer ignoreras. När du väljer eller släpper en mapp ignoreras andra filer som inte finns i ett objektmapp.",
|
||||
"PlaceholderNewCollection": "Nytt samlingsnamn",
|
||||
"PlaceholderNewCollection": "Nytt namn på samlingen",
|
||||
"PlaceholderNewFolderPath": "Nytt sökväg till mappen",
|
||||
"PlaceholderNewPlaylist": "Nytt namn på spellistan",
|
||||
"PlaceholderSearch": "Sök...",
|
||||
@@ -851,7 +855,9 @@
|
||||
"ToastDeviceTestEmailFailed": "Misslyckades med att skicka ett testmail",
|
||||
"ToastDeviceTestEmailSuccess": "Ett testmail har skickats",
|
||||
"ToastEmailSettingsUpdateSuccess": "Inställningarna av e-post har uppdaterats",
|
||||
"ToastEncodeCancelSucces": "Omkodningen avbruten",
|
||||
"ToastFailedToLoadData": "Misslyckades med att ladda data",
|
||||
"ToastFailedToUpdate": "Misslyckades med att uppdatera",
|
||||
"ToastInvalidImageUrl": "Felaktig URL-adress till omslagsbilden",
|
||||
"ToastInvalidMaxEpisodesToDownload": "Ogiltigt maximalt antal avsnitt att ladda ner",
|
||||
"ToastInvalidUrl": "Felaktig URL-adress",
|
||||
@@ -878,7 +884,12 @@
|
||||
"ToastNameRequired": "Ett namn måste anges",
|
||||
"ToastNewUserCreatedFailed": "Misslyckades med att skapa kontot \"{0}\"",
|
||||
"ToastNewUserCreatedSuccess": "Ett nytt konto har skapats",
|
||||
"ToastNewUserLibraryError": "Minst ett bibliotek måste anges",
|
||||
"ToastNewUserPasswordError": "Ett lösenord måste anges. Endast användaren 'root' kan vara utan lösenord.",
|
||||
"ToastNewUserUsernameError": "Ange ett användarnamn",
|
||||
"ToastNoUpdatesNecessary": "Inga uppdateringar var nödvändiga",
|
||||
"ToastNotificationCreateFailed": "Misslyckades med att skapa meddelandet",
|
||||
"ToastNotificationDeleteFailed": "Misslyckades med att radera meddelandet",
|
||||
"ToastPlaylistCreateFailed": "Det gick inte att skapa spellistan",
|
||||
"ToastPlaylistCreateSuccess": "Spellistan skapad",
|
||||
"ToastPlaylistRemoveSuccess": "Spellistan har tagits bort",
|
||||
@@ -887,11 +898,14 @@
|
||||
"ToastPodcastCreateSuccess": "Podcasten skapad framgångsrikt",
|
||||
"ToastProviderCreatedFailed": "Misslyckades med att addera en källa",
|
||||
"ToastProviderCreatedSuccess": "En ny källa har adderats",
|
||||
"ToastProviderNameAndUrlRequired": "Ett namn och en URL-adress krävs",
|
||||
"ToastProviderRemoveSuccess": "Källan har tagits bort",
|
||||
"ToastRSSFeedCloseFailed": "Misslyckades med att stänga RSS-flödet",
|
||||
"ToastRSSFeedCloseSuccess": "RSS-flödet stängt",
|
||||
"ToastRemoveFailed": "Misslyckades med att radera",
|
||||
"ToastRemoveItemFromCollectionFailed": "Misslyckades med att ta bort objektet från samlingen",
|
||||
"ToastRemoveItemFromCollectionSuccess": "Objektet borttaget från samlingen",
|
||||
"ToastRenameFailed": "Misslyckades med att ändra namn",
|
||||
"ToastSelectAtLeastOneUser": "Åtminstone en användare måste väljas",
|
||||
"ToastSendEbookToDeviceFailed": "Misslyckades med att skicka e-boken till enheten",
|
||||
"ToastSendEbookToDeviceSuccess": "E-boken skickad till enheten \"{0}\"",
|
||||
@@ -912,5 +926,6 @@
|
||||
"ToastUserDeleteSuccess": "Användaren borttagen",
|
||||
"ToastUserPasswordChangeSuccess": "Lösenordet har ändrats",
|
||||
"ToastUserPasswordMismatch": "Lösenorden är inte identiska",
|
||||
"ToastUserPasswordMustChange": "Det nya lösenordet kan inte vara samma som det gamla"
|
||||
"ToastUserPasswordMustChange": "Det nya lösenordet kan inte vara samma som det gamla",
|
||||
"ToastUserRootRequireName": "Ett användarnamn för 'root' måste anges"
|
||||
}
|
||||
|
||||
1
client/strings/tr.json
Normal file
1
client/strings/tr.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -434,7 +434,7 @@
|
||||
"LabelMetadataProvider": "Джерело метаданих",
|
||||
"LabelMinute": "Хвилина",
|
||||
"LabelMinutes": "Хвилини",
|
||||
"LabelMissing": "Бракує",
|
||||
"LabelMissing": "Відсутня",
|
||||
"LabelMissingEbook": "Без електронної книги",
|
||||
"LabelMissingSupplementaryEbook": "Без додаткової електронної книги",
|
||||
"LabelMobileRedirectURIs": "Дозволені адреси перенаправлення",
|
||||
@@ -486,6 +486,7 @@
|
||||
"LabelPersonalYearReview": "Ваші підсумки року ({0})",
|
||||
"LabelPhotoPathURL": "Шлях/URL фото",
|
||||
"LabelPlayMethod": "Метод відтворення",
|
||||
"LabelPlaybackRateIncrementDecrement": "Величина збільшення/зменшення швидкості відтворення",
|
||||
"LabelPlayerChapterNumberMarker": "{0} з {1}",
|
||||
"LabelPlaylists": "Списки відтворення",
|
||||
"LabelPodcast": "Подкаст",
|
||||
@@ -710,6 +711,7 @@
|
||||
"MessageBatchEditPopulateMapDetailsItemHelp": "Заповніть увімкнені поля деталей карти даними з цього елемента",
|
||||
"MessageBatchQuickMatchDescription": "Швидкий пошук спробує знайти відсутні обкладинки та метадані обраних елементів. Увімкніть налаштування нижче, аби дозволити заміну наявних обкладинок та/або метаданих під час швидкого пошуку.",
|
||||
"MessageBookshelfNoCollections": "Ви не створили жодної добірки",
|
||||
"MessageBookshelfNoCollectionsHelp": "Колекції публічні. Їх можуть бачити всі користувачі, які мають доступ до бібліотеки.",
|
||||
"MessageBookshelfNoRSSFeeds": "Немає відкритих RSS-каналів",
|
||||
"MessageBookshelfNoResultsForFilter": "Немає результатів з фільтром \"{0}: {1}\"",
|
||||
"MessageBookshelfNoResultsForQuery": "Немає результатів за запитом",
|
||||
@@ -820,6 +822,7 @@
|
||||
"MessageNoTasksRunning": "Немає активних завдань",
|
||||
"MessageNoUpdatesWereNecessary": "Оновлень не потрібно",
|
||||
"MessageNoUserPlaylists": "У вас немає списків відтворення",
|
||||
"MessageNoUserPlaylistsHelp": "Списки відтворення приватні. Лише користувач, який їх створює, може бачити їх.",
|
||||
"MessageNotYetImplemented": "Ще не реалізовано",
|
||||
"MessageOpmlPreviewNote": "Примітка: це попередній перегляд OPML-файлу. Актуальна назва подкасту буде завантажена з RSS-каналу.",
|
||||
"MessageOr": "або",
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
"ButtonApplyChapters": "应用到章节",
|
||||
"ButtonAuthors": "作者",
|
||||
"ButtonBack": "返回",
|
||||
"ButtonBatchEditPopulateFromExisting": "用现有内容填充",
|
||||
"ButtonBatchEditPopulateMapDetails": "填充地图详细信息",
|
||||
"ButtonBrowseForFolder": "浏览文件夹",
|
||||
"ButtonCancel": "取消",
|
||||
"ButtonCancelEncode": "取消编码",
|
||||
@@ -432,7 +434,7 @@
|
||||
"LabelMetadataProvider": "元数据提供商",
|
||||
"LabelMinute": "分钟",
|
||||
"LabelMinutes": "分钟",
|
||||
"LabelMissing": "丢失",
|
||||
"LabelMissing": "丢失的",
|
||||
"LabelMissingEbook": "没有电子书",
|
||||
"LabelMissingSupplementaryEbook": "没有补充电子书",
|
||||
"LabelMobileRedirectURIs": "允许移动应用重定向 URI",
|
||||
@@ -484,6 +486,7 @@
|
||||
"LabelPersonalYearReview": "你的年度回顾 ({0})",
|
||||
"LabelPhotoPathURL": "图片路径或 URL",
|
||||
"LabelPlayMethod": "播放方法",
|
||||
"LabelPlaybackRateIncrementDecrement": "播放速率增加/减少量",
|
||||
"LabelPlayerChapterNumberMarker": "{0} 于 {1}",
|
||||
"LabelPlaylists": "播放列表",
|
||||
"LabelPodcast": "播客",
|
||||
@@ -704,8 +707,11 @@
|
||||
"MessageBackupsLocationEditNote": "注意: 更新备份位置不会移动或修改现有备份",
|
||||
"MessageBackupsLocationNoEditNote": "注意: 备份位置是通过环境变量设置的, 不能在此处更改.",
|
||||
"MessageBackupsLocationPathEmpty": "备份位置路径不能为空",
|
||||
"MessageBatchEditPopulateMapDetailsAllHelp": "使用所有项目的数据填充已启用的字段. 具有多个值的字段将被合并",
|
||||
"MessageBatchEditPopulateMapDetailsItemHelp": "使用此项目的数据填充已启用的地图详细信息字段",
|
||||
"MessageBatchQuickMatchDescription": "快速匹配将尝试为所选项目添加缺少的封面和元数据. 启用以下选项以允许快速匹配覆盖现有封面和或元数据.",
|
||||
"MessageBookshelfNoCollections": "你尚未进行任何收藏",
|
||||
"MessageBookshelfNoCollectionsHelp": "收藏是公开的. 所有有权访问图书馆的用户都可以看到它们.",
|
||||
"MessageBookshelfNoRSSFeeds": "没有打开的 RSS 源",
|
||||
"MessageBookshelfNoResultsForFilter": "过滤器无结果 \"{0}: {1}\"",
|
||||
"MessageBookshelfNoResultsForQuery": "没有可查询的结果",
|
||||
@@ -816,6 +822,7 @@
|
||||
"MessageNoTasksRunning": "没有正在运行的任务",
|
||||
"MessageNoUpdatesWereNecessary": "无需更新",
|
||||
"MessageNoUserPlaylists": "你没有播放列表",
|
||||
"MessageNoUserPlaylistsHelp": "播放列表是私密的. 只有创建播放列表的用户才能看到.",
|
||||
"MessageNotYetImplemented": "尚未实施",
|
||||
"MessageOpmlPreviewNote": "注意: 这是解析的OPML文件的预览. 实际的播客标题将从 RSS 提要中获取.",
|
||||
"MessageOr": "或",
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "audiobookshelf",
|
||||
"version": "2.18.1",
|
||||
"version": "2.19.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "audiobookshelf",
|
||||
"version": "2.18.1",
|
||||
"version": "2.19.0",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf",
|
||||
"version": "2.18.1",
|
||||
"version": "2.19.0",
|
||||
"buildNumber": 1,
|
||||
"description": "Self-hosted audiobook and podcast server",
|
||||
"main": "index.js",
|
||||
|
||||
@@ -251,6 +251,7 @@ class CollectionController {
|
||||
/**
|
||||
* DELETE: /api/collections/:id/book/:bookId
|
||||
* Remove a single book from a collection. Re-order books
|
||||
* Users with update permission can remove books from collections
|
||||
* TODO: bookId is actually libraryItemId. Clients need updating to use bookId
|
||||
*
|
||||
* @param {CollectionControllerRequest} req
|
||||
@@ -427,7 +428,8 @@ class CollectionController {
|
||||
req.collection = collection
|
||||
}
|
||||
|
||||
if (req.method == 'DELETE' && !req.user.canDelete) {
|
||||
// Users with update permission can remove books from collections
|
||||
if (req.method == 'DELETE' && !req.params.bookId && !req.user.canDelete) {
|
||||
Logger.warn(`[CollectionController] User "${req.user.username}" attempted to delete without permission`)
|
||||
return res.sendStatus(403)
|
||||
} else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
|
||||
|
||||
@@ -286,7 +286,7 @@ class Book extends Model {
|
||||
const track = structuredClone(af)
|
||||
track.title = af.metadata.filename
|
||||
track.startOffset = startOffset
|
||||
track.contentUrl = `${global.RouterBasePath}/api/items/${libraryItemId}/file/${track.ino}`
|
||||
track.contentUrl = `/api/items/${libraryItemId}/file/${track.ino}`
|
||||
startOffset += track.duration
|
||||
return track
|
||||
})
|
||||
@@ -365,7 +365,7 @@ class Book extends Model {
|
||||
if (payload.metadata) {
|
||||
const metadataStringKeys = ['title', 'subtitle', 'publishedYear', 'publishedDate', 'publisher', 'description', 'isbn', 'asin', 'language']
|
||||
metadataStringKeys.forEach((key) => {
|
||||
if (typeof payload.metadata[key] === 'string' && this[key] !== payload.metadata[key]) {
|
||||
if ((typeof payload.metadata[key] === 'string' || payload.metadata[key] === null) && this[key] !== payload.metadata[key]) {
|
||||
this[key] = payload.metadata[key] || null
|
||||
|
||||
if (key === 'title') {
|
||||
|
||||
@@ -202,8 +202,9 @@ class Podcast extends Model {
|
||||
} else if (key === 'itunesPageUrl') {
|
||||
newKey = 'itunesPageURL'
|
||||
}
|
||||
if (typeof payload.metadata[key] === 'string' && payload.metadata[key] !== this[newKey]) {
|
||||
this[newKey] = payload.metadata[key]
|
||||
if ((typeof payload.metadata[key] === 'string' || payload.metadata[key] === null) && payload.metadata[key] !== this[newKey]) {
|
||||
this[newKey] = payload.metadata[key] || null
|
||||
|
||||
if (key === 'title') {
|
||||
this.titleIgnorePrefix = getTitleIgnorePrefix(this.title)
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ class PodcastEpisode extends Model {
|
||||
const track = structuredClone(this.audioFile)
|
||||
track.startOffset = 0
|
||||
track.title = this.audioFile.metadata.filename
|
||||
track.contentUrl = `${global.RouterBasePath}/api/items/${libraryItemId}/file/${track.ino}`
|
||||
track.contentUrl = `/api/items/${libraryItemId}/file/${track.ino}`
|
||||
return track
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class AudioTrack {
|
||||
this.duration = audioFile.duration
|
||||
this.title = audioFile.metadata.filename || ''
|
||||
|
||||
this.contentUrl = `${global.RouterBasePath}/api/items/${itemId}/file/${audioFile.ino}`
|
||||
this.contentUrl = `/api/items/${itemId}/file/${audioFile.ino}`
|
||||
this.mimeType = audioFile.mimeType
|
||||
this.codec = audioFile.codec || null
|
||||
this.metadata = audioFile.metadata.clone()
|
||||
@@ -44,4 +44,4 @@ class AudioTrack {
|
||||
this.mimeType = 'application/vnd.apple.mpegurl'
|
||||
}
|
||||
}
|
||||
module.exports = AudioTrack
|
||||
module.exports = AudioTrack
|
||||
|
||||
@@ -286,10 +286,23 @@ module.exports.downloadFile = (url, filepath, contentTypeFilter = null) => {
|
||||
return reject(new Error(`Invalid content type "${response.headers?.['content-type'] || ''}"`))
|
||||
}
|
||||
|
||||
const totalSize = parseInt(response.headers['content-length'], 10)
|
||||
let downloadedSize = 0
|
||||
|
||||
// Write to filepath
|
||||
const writer = fs.createWriteStream(filepath)
|
||||
response.data.pipe(writer)
|
||||
|
||||
let lastProgress = 0
|
||||
response.data.on('data', (chunk) => {
|
||||
downloadedSize += chunk.length
|
||||
const progress = totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0
|
||||
if (progress >= lastProgress + 5) {
|
||||
Logger.debug(`[fileUtils] File "${Path.basename(filepath)}" download progress: ${progress}% (${downloadedSize}/${totalSize} bytes)`)
|
||||
lastProgress = progress
|
||||
}
|
||||
})
|
||||
|
||||
writer.on('finish', resolve)
|
||||
writer.on('error', reject)
|
||||
})
|
||||
|
||||
@@ -35,11 +35,18 @@ module.exports.nameToLastFirst = (firstLast) => {
|
||||
return `${nameObj.last_name}, ${nameObj.first_name}`
|
||||
}
|
||||
|
||||
// Handle any name string
|
||||
/**
|
||||
* Parses a name string into an array of names
|
||||
*
|
||||
* @param {string} nameString - The name string to parse
|
||||
* @returns {{ names: string[] }} Array of names
|
||||
*/
|
||||
module.exports.parse = (nameString) => {
|
||||
if (!nameString) return null
|
||||
|
||||
var splitNames = []
|
||||
let splitNames = []
|
||||
const isCommaSeparated = nameString.includes(',')
|
||||
|
||||
// Example &LF: Friedman, Milton & Friedman, Rose
|
||||
if (nameString.includes('&')) {
|
||||
nameString.split('&').forEach((asa) => (splitNames = splitNames.concat(asa.split(','))))
|
||||
@@ -59,17 +66,18 @@ module.exports.parse = (nameString) => {
|
||||
}
|
||||
}
|
||||
|
||||
var names = []
|
||||
let names = []
|
||||
|
||||
// 1 name FIRST LAST
|
||||
if (splitNames.length === 1) {
|
||||
names.push(parseName(nameString))
|
||||
} else {
|
||||
var firstChunkIsALastName = checkIsALastName(splitNames[0])
|
||||
var isEvenNum = splitNames.length % 2 === 0
|
||||
// Determines whether this is formatted as last, first or first last (only if using comma separator)
|
||||
// Example: "Smith; James Jones" -> ["Smith", "James Jones"]
|
||||
let firstChunkIsALastName = !isCommaSeparated ? false : checkIsALastName(splitNames[0])
|
||||
let isEvenNum = splitNames.length % 2 === 0
|
||||
|
||||
if (!isEvenNum && firstChunkIsALastName) {
|
||||
// console.error('Multi-name LAST,FIRST entry has a straggler (could be roman numerals or a suffix), ignore it')
|
||||
splitNames = splitNames.slice(0, splitNames.length - 1)
|
||||
}
|
||||
|
||||
|
||||
@@ -311,6 +311,7 @@ module.exports.getPodcastFeed = (feedUrl, excludeEpisodeMetadata = false) => {
|
||||
responseType: 'arraybuffer',
|
||||
headers: {
|
||||
Accept: 'application/rss+xml, application/xhtml+xml, application/xml, */*;q=0.8',
|
||||
'Accept-Encoding': 'gzip, compress, deflate',
|
||||
'User-Agent': userAgent
|
||||
},
|
||||
httpAgent: global.DisableSsrfRequestFilter?.(feedUrl) ? null : ssrfFilter(feedUrl),
|
||||
|
||||
99
test/server/utils/parsers/parseNameString.test.js
Normal file
99
test/server/utils/parsers/parseNameString.test.js
Normal file
@@ -0,0 +1,99 @@
|
||||
const chai = require('chai')
|
||||
const expect = chai.expect
|
||||
const { parse, nameToLastFirst } = require('../../../../server/utils/parsers/parseNameString')
|
||||
|
||||
describe('parseNameString', () => {
|
||||
describe('parse', () => {
|
||||
it('returns null if nameString is empty', () => {
|
||||
const result = parse('')
|
||||
expect(result).to.be.null
|
||||
})
|
||||
|
||||
it('parses single name in First Last format', () => {
|
||||
const result = parse('John Smith')
|
||||
expect(result.names).to.deep.equal(['John Smith'])
|
||||
})
|
||||
|
||||
it('parses single name in Last, First format', () => {
|
||||
const result = parse('Smith, John')
|
||||
expect(result.names).to.deep.equal(['John Smith'])
|
||||
})
|
||||
|
||||
it('parses multiple names separated by &', () => {
|
||||
const result = parse('John Smith & Jane Doe')
|
||||
expect(result.names).to.deep.equal(['John Smith', 'Jane Doe'])
|
||||
})
|
||||
|
||||
it('parses multiple names separated by "and"', () => {
|
||||
const result = parse('John Smith and Jane Doe')
|
||||
expect(result.names).to.deep.equal(['John Smith', 'Jane Doe'])
|
||||
})
|
||||
|
||||
it('parses multiple names separated by comma and "and"', () => {
|
||||
const result = parse('John Smith, Jane Doe and John Doe')
|
||||
expect(result.names).to.deep.equal(['John Smith', 'Jane Doe', 'John Doe'])
|
||||
})
|
||||
|
||||
it('parses multiple names separated by semicolon', () => {
|
||||
const result = parse('John Smith; Jane Doe')
|
||||
expect(result.names).to.deep.equal(['John Smith', 'Jane Doe'])
|
||||
})
|
||||
|
||||
it('parses multiple names in Last, First format', () => {
|
||||
const result = parse('Smith, John, Doe, Jane')
|
||||
expect(result.names).to.deep.equal(['John Smith', 'Jane Doe'])
|
||||
})
|
||||
|
||||
it('parses multiple names with single word name', () => {
|
||||
const result = parse('John Smith, Jones, James Doe, Ludwig von Mises')
|
||||
expect(result.names).to.deep.equal(['John Smith', 'Jones', 'James Doe', 'Ludwig von Mises'])
|
||||
})
|
||||
|
||||
it('parses multiple names with single word name listed first (semicolon separator)', () => {
|
||||
const result = parse('Jones; John Smith; James Doe; Ludwig von Mises')
|
||||
expect(result.names).to.deep.equal(['Jones', 'John Smith', 'James Doe', 'Ludwig von Mises'])
|
||||
})
|
||||
|
||||
it('handles names with suffixes', () => {
|
||||
const result = parse('Smith, John Jr.')
|
||||
expect(result.names).to.deep.equal(['John Jr. Smith'])
|
||||
})
|
||||
|
||||
it('handles compound last names', () => {
|
||||
const result = parse('von Mises, Ludwig')
|
||||
expect(result.names).to.deep.equal(['Ludwig von Mises'])
|
||||
})
|
||||
|
||||
it('handles Chinese/Japanese/Korean names', () => {
|
||||
const result = parse('张三, 李四')
|
||||
expect(result.names).to.deep.equal(['张三', '李四'])
|
||||
})
|
||||
|
||||
it('removes duplicate names', () => {
|
||||
const result = parse('John Smith & John Smith')
|
||||
expect(result.names).to.deep.equal(['John Smith'])
|
||||
})
|
||||
|
||||
it('filters out empty names', () => {
|
||||
const result = parse('John Smith,')
|
||||
expect(result.names).to.deep.equal(['John Smith'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('nameToLastFirst', () => {
|
||||
it('converts First Last to Last, First format', () => {
|
||||
const result = nameToLastFirst('John Smith')
|
||||
expect(result).to.equal('Smith, John')
|
||||
})
|
||||
|
||||
it('returns last name only when no first name', () => {
|
||||
const result = nameToLastFirst('Smith')
|
||||
expect(result).to.equal('Smith')
|
||||
})
|
||||
|
||||
it('handles names with middle names', () => {
|
||||
const result = nameToLastFirst('John Middle Smith')
|
||||
expect(result).to.equal('Smith, John Middle')
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user