mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-08 18:31:43 +00:00
Merge remote-tracking branch 'remotes/upstream/master'
This commit is contained in:
commit
443232034b
21 changed files with 209 additions and 81 deletions
33
.github/pull_request_template.md
vendored
Normal file
33
.github/pull_request_template.md
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
<!--
|
||||||
|
For Work In Progress Pull Requests, please use the Draft PR feature,
|
||||||
|
see https://github.blog/2019-02-14-introducing-draft-pull-requests/ for further details.
|
||||||
|
|
||||||
|
If you do not follow this template, the PR may be closed without review.
|
||||||
|
|
||||||
|
Please ensure all checks pass.
|
||||||
|
If you are a new contributor, the workflows will need to be manually approved before they run.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Brief summary
|
||||||
|
|
||||||
|
<!-- Please provide a brief summary of what your PR attempts to achieve. -->
|
||||||
|
|
||||||
|
## Which issue is fixed?
|
||||||
|
|
||||||
|
<!-- Which issue number does this PR fix? Ex: "Fixes #1234" -->
|
||||||
|
|
||||||
|
## In-depth Description
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Describe your solution in more depth.
|
||||||
|
How does it work? Why is this the best solution?
|
||||||
|
Does it solve a problem that affects multiple users or is this an edge case for your setup?
|
||||||
|
-->
|
||||||
|
|
||||||
|
## How have you tested this?
|
||||||
|
|
||||||
|
<!-- Please describe in detail with reproducible steps how you tested your changes. -->
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
<!-- If your PR includes any changes to the web client, please include screenshots or a short video from before and after your changes. -->
|
||||||
|
|
@ -18,6 +18,23 @@
|
||||||
<p dir="auto" class="text-lg font-semibold mb-6">{{ title }}</p>
|
<p dir="auto" class="text-lg font-semibold mb-6">{{ title }}</p>
|
||||||
<div v-if="description" dir="auto" class="default-style" v-html="description" />
|
<div v-if="description" dir="auto" class="default-style" v-html="description" />
|
||||||
<p v-else class="mb-2">{{ $strings.MessageNoDescription }}</p>
|
<p v-else class="mb-2">{{ $strings.MessageNoDescription }}</p>
|
||||||
|
|
||||||
|
<div class="w-full h-px bg-white/5 my-4" />
|
||||||
|
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-grow">
|
||||||
|
<p class="font-semibold text-xs mb-1">{{ $strings.LabelFilename }}</p>
|
||||||
|
<p class="mb-2 text-xs">
|
||||||
|
{{ audioFileFilename }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow">
|
||||||
|
<p class="font-semibold text-xs mb-1">{{ $strings.LabelSize }}</p>
|
||||||
|
<p class="mb-2 text-xs">
|
||||||
|
{{ audioFileSize }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -54,7 +71,7 @@ export default {
|
||||||
return this.episode.description || ''
|
return this.episode.description || ''
|
||||||
},
|
},
|
||||||
media() {
|
media() {
|
||||||
return this.libraryItem ? this.libraryItem.media || {} : {}
|
return this.libraryItem?.media || {}
|
||||||
},
|
},
|
||||||
mediaMetadata() {
|
mediaMetadata() {
|
||||||
return this.media.metadata || {}
|
return this.media.metadata || {}
|
||||||
|
|
@ -65,6 +82,14 @@ export default {
|
||||||
podcastAuthor() {
|
podcastAuthor() {
|
||||||
return this.mediaMetadata.author
|
return this.mediaMetadata.author
|
||||||
},
|
},
|
||||||
|
audioFileFilename() {
|
||||||
|
return this.episode.audioFile?.metadata?.filename || ''
|
||||||
|
},
|
||||||
|
audioFileSize() {
|
||||||
|
const size = this.episode.audioFile?.metadata?.size || 0
|
||||||
|
|
||||||
|
return this.$bytesPretty(size)
|
||||||
|
},
|
||||||
bookCoverAspectRatio() {
|
bookCoverAspectRatio() {
|
||||||
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <p v-if="!episodes.length" class="py-4 text-center text-lg">{{ $strings.MessageNoEpisodes }}</p> -->
|
|
||||||
<div v-if="episodes.length" class="w-full py-3 mx-auto flex">
|
<div v-if="episodes.length" class="w-full py-3 mx-auto flex">
|
||||||
<form @submit.prevent="submit" class="flex flex-grow">
|
<form @submit.prevent="submit" class="flex flex-grow">
|
||||||
<ui-text-input v-model="search" @input="inputUpdate" type="search" :placeholder="$strings.PlaceholderSearchEpisode" class="flex-grow mr-2 text-sm md:text-base" />
|
<ui-text-input v-model="search" @input="inputUpdate" type="search" :placeholder="$strings.PlaceholderSearchEpisode" class="flex-grow mr-2 text-sm md:text-base" />
|
||||||
|
|
@ -515,6 +514,10 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
filterSortChanged() {
|
filterSortChanged() {
|
||||||
|
// Save filterKey and sortKey to local storage
|
||||||
|
localStorage.setItem('podcastEpisodesFilter', this.filterKey)
|
||||||
|
localStorage.setItem('podcastEpisodesSortBy', this.sortKey + (this.sortDesc ? '-desc' : ''))
|
||||||
|
|
||||||
this.init()
|
this.init()
|
||||||
},
|
},
|
||||||
refresh() {
|
refresh() {
|
||||||
|
|
@ -537,6 +540,11 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.filterKey = localStorage.getItem('podcastEpisodesFilter') || 'incomplete'
|
||||||
|
const sortBy = localStorage.getItem('podcastEpisodesSortBy') || 'publishedAt-desc'
|
||||||
|
this.sortKey = sortBy.split('-')[0]
|
||||||
|
this.sortDesc = sortBy.split('-')[1] === 'desc'
|
||||||
|
|
||||||
this.episodesCopy = this.episodes.map((ep) => ({ ...ep }))
|
this.episodesCopy = this.episodes.map((ep) => ({ ...ep }))
|
||||||
this.initListeners()
|
this.initListeners()
|
||||||
this.init()
|
this.init()
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,8 @@ export default {
|
||||||
var validOtherFiles = []
|
var validOtherFiles = []
|
||||||
var ignoredFiles = []
|
var ignoredFiles = []
|
||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
// var filetype = this.checkFileType(file.name)
|
|
||||||
if (!file.filetype) ignoredFiles.push(file)
|
if (!file.filetype) ignoredFiles.push(file)
|
||||||
else {
|
else {
|
||||||
// file.filetype = filetype
|
|
||||||
if (file.filetype === 'audio' || (file.filetype === 'ebook' && mediaType === 'book')) validItemFiles.push(file)
|
if (file.filetype === 'audio' || (file.filetype === 'ebook' && mediaType === 'book')) validItemFiles.push(file)
|
||||||
else validOtherFiles.push(file)
|
else validOtherFiles.push(file)
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +163,7 @@ export default {
|
||||||
|
|
||||||
var firstBookPath = Path.dirname(firstBookFile.filepath)
|
var firstBookPath = Path.dirname(firstBookFile.filepath)
|
||||||
|
|
||||||
var dirs = firstBookPath.split('/').filter(d => !!d && d !== '.')
|
var dirs = firstBookPath.split('/').filter((d) => !!d && d !== '.')
|
||||||
if (dirs.length) {
|
if (dirs.length) {
|
||||||
audiobook.title = dirs.pop()
|
audiobook.title = dirs.pop()
|
||||||
if (dirs.length > 1) {
|
if (dirs.length > 1) {
|
||||||
|
|
@ -189,7 +187,7 @@ export default {
|
||||||
var firstAudioFile = podcast.itemFiles[0]
|
var firstAudioFile = podcast.itemFiles[0]
|
||||||
if (!firstAudioFile.filepath) return podcast // No path
|
if (!firstAudioFile.filepath) return podcast // No path
|
||||||
var firstPath = Path.dirname(firstAudioFile.filepath)
|
var firstPath = Path.dirname(firstAudioFile.filepath)
|
||||||
var dirs = firstPath.split('/').filter(d => !!d && d !== '.')
|
var dirs = firstPath.split('/').filter((d) => !!d && d !== '.')
|
||||||
if (dirs.length) {
|
if (dirs.length) {
|
||||||
podcast.title = dirs.length > 1 ? dirs[1] : dirs[0]
|
podcast.title = dirs.length > 1 ? dirs[1] : dirs[0]
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -212,13 +210,15 @@ export default {
|
||||||
}
|
}
|
||||||
var ignoredFiles = itemData.ignoredFiles
|
var ignoredFiles = itemData.ignoredFiles
|
||||||
var index = 1
|
var index = 1
|
||||||
var items = itemData.items.filter((ab) => {
|
var items = itemData.items
|
||||||
if (!ab.itemFiles.length) {
|
.filter((ab) => {
|
||||||
if (ab.otherFiles.length) ignoredFiles = ignoredFiles.concat(ab.otherFiles)
|
if (!ab.itemFiles.length) {
|
||||||
if (ab.ignoredFiles.length) ignoredFiles = ignoredFiles.concat(ab.ignoredFiles)
|
if (ab.otherFiles.length) ignoredFiles = ignoredFiles.concat(ab.otherFiles)
|
||||||
}
|
if (ab.ignoredFiles.length) ignoredFiles = ignoredFiles.concat(ab.ignoredFiles)
|
||||||
return ab.itemFiles.length
|
}
|
||||||
}).map(ab => this.cleanItem(ab, mediaType, index++))
|
return ab.itemFiles.length
|
||||||
|
})
|
||||||
|
.map((ab) => this.cleanItem(ab, mediaType, index++))
|
||||||
return {
|
return {
|
||||||
items,
|
items,
|
||||||
ignoredFiles
|
ignoredFiles
|
||||||
|
|
@ -259,7 +259,7 @@ export default {
|
||||||
|
|
||||||
otherFiles.forEach((file) => {
|
otherFiles.forEach((file) => {
|
||||||
var dir = Path.dirname(file.filepath)
|
var dir = Path.dirname(file.filepath)
|
||||||
var findItem = Object.values(itemMap).find(b => dir.startsWith(b.path))
|
var findItem = Object.values(itemMap).find((b) => dir.startsWith(b.path))
|
||||||
if (findItem) {
|
if (findItem) {
|
||||||
findItem.otherFiles.push(file)
|
findItem.otherFiles.push(file)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -270,18 +270,18 @@ export default {
|
||||||
var items = []
|
var items = []
|
||||||
var index = 1
|
var index = 1
|
||||||
// If book media type and all files are audio files then treat each one as an audiobook
|
// If book media type and all files are audio files then treat each one as an audiobook
|
||||||
if (itemMap[''] && !otherFiles.length && mediaType === 'book' && !itemMap[''].itemFiles.some(f => f.filetype !== 'audio')) {
|
if (itemMap[''] && !otherFiles.length && mediaType === 'book' && !itemMap[''].itemFiles.some((f) => f.filetype !== 'audio')) {
|
||||||
items = itemMap[''].itemFiles.map((audioFile) => {
|
items = itemMap[''].itemFiles.map((audioFile) => {
|
||||||
return this.cleanItem({ itemFiles: [audioFile], otherFiles: [], ignoredFiles: [] }, mediaType, index++)
|
return this.cleanItem({ itemFiles: [audioFile], otherFiles: [], ignoredFiles: [] }, mediaType, index++)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
items = Object.values(itemMap).map(i => this.cleanItem(i, mediaType, index++))
|
items = Object.values(itemMap).map((i) => this.cleanItem(i, mediaType, index++))
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items,
|
items,
|
||||||
ignoredFiles: ignoredFiles
|
ignoredFiles: ignoredFiles
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
client/package-lock.json
generated
4
client/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.17.1",
|
"version": "2.17.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.17.1",
|
"version": "2.17.2",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/axios": "^5.13.6",
|
"@nuxtjs/axios": "^5.13.6",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.17.1",
|
"version": "2.17.2",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast client",
|
"description": "Self-hosted audiobook and podcast client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="page-wrapper" class="page p-0 sm:p-6 overflow-y-auto" :class="streamLibraryItem ? 'streaming' : ''">
|
<div id="page-wrapper" class="page p-1 sm:p-6 overflow-y-auto" :class="streamLibraryItem ? 'streaming' : ''">
|
||||||
<div class="w-full max-w-6xl mx-auto">
|
<div class="w-full max-w-6xl mx-auto">
|
||||||
<!-- Library & folder picker -->
|
<!-- Library & folder picker -->
|
||||||
<div class="flex my-6 -mx-2">
|
<div class="flex flex-wrap my-6 md:-mx-2">
|
||||||
<div class="w-1/5 px-2">
|
<div class="w-full md:w-1/5 px-2">
|
||||||
<ui-dropdown v-model="selectedLibraryId" :items="libraryItems" :label="$strings.LabelLibrary" :disabled="!!items.length" @input="libraryChanged" />
|
<ui-dropdown v-model="selectedLibraryId" :items="libraryItems" :label="$strings.LabelLibrary" :disabled="!!items.length" @input="libraryChanged" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-3/5 px-2">
|
<div class="w-full md:w-3/5 px-2">
|
||||||
<ui-dropdown v-model="selectedFolderId" :items="folderItems" :disabled="!selectedLibraryId || !!items.length" :label="$strings.LabelFolder" />
|
<ui-dropdown v-model="selectedFolderId" :items="folderItems" :disabled="!selectedLibraryId || !!items.length" :label="$strings.LabelFolder" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/5 px-2">
|
<div class="w-full md:w-1/5 px-2">
|
||||||
<ui-text-input-with-label :value="selectedLibraryMediaType" readonly :label="$strings.LabelMediaType" />
|
<ui-text-input-with-label :value="selectedLibraryMediaType" readonly :label="$strings.LabelMediaType" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!selectedLibraryIsPodcast" class="flex items-center mb-6">
|
<div v-if="!selectedLibraryIsPodcast" class="flex items-center mb-6 px-2 md:px-0">
|
||||||
<label class="flex cursor-pointer pt-4">
|
<label class="flex cursor-pointer pt-4">
|
||||||
<ui-toggle-switch v-model="fetchMetadata.enabled" class="inline-flex" />
|
<ui-toggle-switch v-model="fetchMetadata.enabled" class="inline-flex" />
|
||||||
<span class="pl-2 text-base">{{ $strings.LabelAutoFetchMetadata }}</span>
|
<span class="pl-2 text-base">{{ $strings.LabelAutoFetchMetadata }}</span>
|
||||||
|
|
@ -33,13 +33,13 @@
|
||||||
</widgets-alert>
|
</widgets-alert>
|
||||||
|
|
||||||
<!-- Picker display -->
|
<!-- Picker display -->
|
||||||
<div v-if="!items.length && !ignoredFiles.length" class="w-full mx-auto border border-white border-opacity-20 px-12 pt-12 pb-4 my-12 relative" :class="isDragging ? 'bg-primary bg-opacity-40' : 'border-dashed'">
|
<div v-if="!items.length && !ignoredFiles.length" class="w-full mx-auto border border-white border-opacity-20 px-4 md:px-12 pt-12 pb-4 my-12 relative" :class="isDragging ? 'bg-primary bg-opacity-40' : 'border-dashed'">
|
||||||
<p class="text-2xl text-center">{{ isDragging ? $strings.LabelUploaderDropFiles : $strings.LabelUploaderDragAndDrop }}</p>
|
<p class="text-2xl text-center">{{ isDragging ? $strings.LabelUploaderDropFiles : isIOS ? $strings.LabelUploaderDragAndDropFilesOnly : $strings.LabelUploaderDragAndDrop }}</p>
|
||||||
<p class="text-center text-sm my-5">{{ $strings.MessageOr }}</p>
|
<p class="text-center text-sm my-5">{{ $strings.MessageOr }}</p>
|
||||||
<div class="w-full max-w-xl mx-auto">
|
<div class="w-full max-w-xl mx-auto">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<ui-btn class="w-full mx-1" @click="openFilePicker">{{ $strings.ButtonChooseFiles }}</ui-btn>
|
<ui-btn class="w-full mx-1" @click="openFilePicker">{{ $strings.ButtonChooseFiles }}</ui-btn>
|
||||||
<ui-btn class="w-full mx-1" @click="openFolderPicker">{{ $strings.ButtonChooseAFolder }}</ui-btn>
|
<ui-btn v-if="!isIOS" class="w-full mx-1" @click="openFolderPicker">{{ $strings.ButtonChooseAFolder }} </ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-8 text-center">
|
<div class="pt-8 text-center">
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="text-sm text-white text-opacity-70">
|
<p class="text-sm text-white text-opacity-70">
|
||||||
{{ $strings.NoteUploaderFoldersWithMediaFiles }} <span v-if="selectedLibraryMediaType === 'book'">{{ $strings.NoteUploaderOnlyAudioFiles }}</span>
|
<span v-if="!isIOS">{{ $strings.NoteUploaderFoldersWithMediaFiles }}</span> <span v-if="selectedLibraryMediaType === 'book'">{{ $strings.NoteUploaderOnlyAudioFiles }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -84,8 +84,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input ref="fileInput" type="file" multiple :accept="inputAccept" class="hidden" @change="inputChanged" />
|
<input ref="fileInput" type="file" multiple :accept="isIOS ? '' : inputAccept" class="hidden" @change="inputChanged" />
|
||||||
<input ref="fileFolderInput" type="file" webkitdirectory multiple :accept="inputAccept" class="hidden" @change="inputChanged" />
|
<input ref="fileFolderInput" type="file" webkitdirectory multiple :accept="inputAccept" class="hidden" @change="inputChanged" v-if="!isIOS" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -127,6 +127,10 @@ export default {
|
||||||
})
|
})
|
||||||
return extensions
|
return extensions
|
||||||
},
|
},
|
||||||
|
isIOS() {
|
||||||
|
const ua = window.navigator.userAgent
|
||||||
|
return /iPad|iPhone|iPod/.test(ua) && !window.MSStream
|
||||||
|
},
|
||||||
streamLibraryItem() {
|
streamLibraryItem() {
|
||||||
return this.$store.state.streamLibraryItem
|
return this.$store.state.streamLibraryItem
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -127,5 +127,30 @@
|
||||||
"HeaderCollectionItems": "عناصر المجموعة",
|
"HeaderCollectionItems": "عناصر المجموعة",
|
||||||
"HeaderCover": "الغلاف",
|
"HeaderCover": "الغلاف",
|
||||||
"HeaderCurrentDownloads": "التنزيلات الجارية",
|
"HeaderCurrentDownloads": "التنزيلات الجارية",
|
||||||
"HeaderCustomMessageOnLogin": "رسالة مخصصة عند تسجيل الدخول"
|
"HeaderCustomMessageOnLogin": "رسالة مخصصة عند تسجيل الدخول",
|
||||||
|
"HeaderCustomMetadataProviders": "مقدمو البيانات الوصفية المخصصة",
|
||||||
|
"HeaderDetails": "التفاصيل",
|
||||||
|
"HeaderDownloadQueue": "تنزيل قائمة الانتظار",
|
||||||
|
"HeaderEbookFiles": "ملفات الكتب الإلكترونية",
|
||||||
|
"HeaderEmail": "البريد الإلكتروني",
|
||||||
|
"HeaderEmailSettings": "إعدادات البريد الإلكتروني",
|
||||||
|
"HeaderEpisodes": "الحلقات",
|
||||||
|
"HeaderEreaderDevices": "أجهزة قراءة الكتب الإلكترونية",
|
||||||
|
"HeaderEreaderSettings": "إعدادات القارئ الإلكتروني",
|
||||||
|
"HeaderFiles": "ملفات",
|
||||||
|
"HeaderFindChapters": "البحث عن الفصول",
|
||||||
|
"HeaderIgnoredFiles": "الملفات المتجاهلة",
|
||||||
|
"HeaderItemFiles": "ملفات العنصر",
|
||||||
|
"HeaderItemMetadataUtils": "بيانات تعريف العنصر",
|
||||||
|
"HeaderLastListeningSession": "آخر جلسة استماع",
|
||||||
|
"HeaderLatestEpisodes": "أحدث الحلقات",
|
||||||
|
"HeaderLibraries": "المكتبات",
|
||||||
|
"HeaderLibraryFiles": "ملفات المكتبة",
|
||||||
|
"HeaderLibraryStats": "إحصائيات المكتبة",
|
||||||
|
"HeaderListeningSessions": "جلسات الاستماع",
|
||||||
|
"HeaderListeningStats": "جلسات الاستماع",
|
||||||
|
"HeaderLogin": "تسجيل الدخول",
|
||||||
|
"HeaderLogs": "السجلات",
|
||||||
|
"HeaderManageGenres": "إدارة الانواع",
|
||||||
|
"HeaderManageTags": "إدارة العلامات"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -663,6 +663,7 @@
|
||||||
"LabelUpdateDetailsHelp": "Allow overwriting of existing details for the selected books when a match is located",
|
"LabelUpdateDetailsHelp": "Allow overwriting of existing details for the selected books when a match is located",
|
||||||
"LabelUpdatedAt": "Updated At",
|
"LabelUpdatedAt": "Updated At",
|
||||||
"LabelUploaderDragAndDrop": "Drag & drop files or folders",
|
"LabelUploaderDragAndDrop": "Drag & drop files or folders",
|
||||||
|
"LabelUploaderDragAndDropFilesOnly": "Drag & drop files",
|
||||||
"LabelUploaderDropFiles": "Drop files",
|
"LabelUploaderDropFiles": "Drop files",
|
||||||
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
|
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
|
||||||
"LabelUseAdvancedOptions": "Use Advanced Options",
|
"LabelUseAdvancedOptions": "Use Advanced Options",
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@
|
||||||
"ButtonChooseAFolder": "בחר תיקייה",
|
"ButtonChooseAFolder": "בחר תיקייה",
|
||||||
"ButtonChooseFiles": "בחר קבצים",
|
"ButtonChooseFiles": "בחר קבצים",
|
||||||
"ButtonClearFilter": "נקה סינון",
|
"ButtonClearFilter": "נקה סינון",
|
||||||
"ButtonCloseFeed": "סגור פיד",
|
"ButtonCloseFeed": "סגור ערוץ",
|
||||||
|
"ButtonCloseSession": "סגור סשן פתוח",
|
||||||
"ButtonCollections": "אוספים",
|
"ButtonCollections": "אוספים",
|
||||||
"ButtonConfigureScanner": "הגדר סורק",
|
"ButtonConfigureScanner": "הגדר סורק",
|
||||||
"ButtonCreate": "צור",
|
"ButtonCreate": "צור",
|
||||||
|
|
@ -28,6 +29,7 @@
|
||||||
"ButtonEdit": "ערוך",
|
"ButtonEdit": "ערוך",
|
||||||
"ButtonEditChapters": "ערוך פרקים",
|
"ButtonEditChapters": "ערוך פרקים",
|
||||||
"ButtonEditPodcast": "ערוך פודקאסט",
|
"ButtonEditPodcast": "ערוך פודקאסט",
|
||||||
|
"ButtonEnable": "הפעל",
|
||||||
"ButtonForceReScan": "סרוק מחדש בכוח",
|
"ButtonForceReScan": "סרוק מחדש בכוח",
|
||||||
"ButtonFullPath": "נתיב מלא",
|
"ButtonFullPath": "נתיב מלא",
|
||||||
"ButtonHide": "הסתר",
|
"ButtonHide": "הסתר",
|
||||||
|
|
@ -46,19 +48,24 @@
|
||||||
"ButtonNevermind": "לא משנה",
|
"ButtonNevermind": "לא משנה",
|
||||||
"ButtonNext": "הבא",
|
"ButtonNext": "הבא",
|
||||||
"ButtonNextChapter": "פרק הבא",
|
"ButtonNextChapter": "פרק הבא",
|
||||||
|
"ButtonNextItemInQueue": "פריט הבא בתור",
|
||||||
"ButtonOk": "אישור",
|
"ButtonOk": "אישור",
|
||||||
"ButtonOpenFeed": "פתח פיד",
|
"ButtonOpenFeed": "פתח פיד",
|
||||||
"ButtonOpenManager": "פתח מנהל",
|
"ButtonOpenManager": "פתח מנהל",
|
||||||
"ButtonPause": "השהה",
|
"ButtonPause": "השהה",
|
||||||
"ButtonPlay": "נגן",
|
"ButtonPlay": "נגן",
|
||||||
|
"ButtonPlayAll": "נגן הכל",
|
||||||
"ButtonPlaying": "מנגן",
|
"ButtonPlaying": "מנגן",
|
||||||
"ButtonPlaylists": "רשימות השמעה",
|
"ButtonPlaylists": "רשימות השמעה",
|
||||||
"ButtonPrevious": "קודם",
|
"ButtonPrevious": "קודם",
|
||||||
"ButtonPreviousChapter": "פרק קודם",
|
"ButtonPreviousChapter": "פרק קודם",
|
||||||
|
"ButtonProbeAudioFile": "בדוק קובץ אודיו",
|
||||||
"ButtonPurgeAllCache": "נקה את כל המטמון",
|
"ButtonPurgeAllCache": "נקה את כל המטמון",
|
||||||
"ButtonPurgeItemsCache": "נקה את מטמון הפריטים",
|
"ButtonPurgeItemsCache": "נקה את מטמון הפריטים",
|
||||||
"ButtonQueueAddItem": "הוסף לתור",
|
"ButtonQueueAddItem": "הוסף לתור",
|
||||||
"ButtonQueueRemoveItem": "הסר מהתור",
|
"ButtonQueueRemoveItem": "הסר מהתור",
|
||||||
|
"ButtonQuickEmbed": "הטמעה מהירה",
|
||||||
|
"ButtonQuickEmbedMetadata": "הטמעת מטא נתונים מהירה",
|
||||||
"ButtonQuickMatch": "התאמה מהירה",
|
"ButtonQuickMatch": "התאמה מהירה",
|
||||||
"ButtonReScan": "סרוק מחדש",
|
"ButtonReScan": "סרוק מחדש",
|
||||||
"ButtonRead": "קרא",
|
"ButtonRead": "קרא",
|
||||||
|
|
@ -88,8 +95,10 @@
|
||||||
"ButtonShow": "הצג",
|
"ButtonShow": "הצג",
|
||||||
"ButtonStartM4BEncode": "התחל קידוד M4B",
|
"ButtonStartM4BEncode": "התחל קידוד M4B",
|
||||||
"ButtonStartMetadataEmbed": "התחל הטמעת מטא-נתונים",
|
"ButtonStartMetadataEmbed": "התחל הטמעת מטא-נתונים",
|
||||||
|
"ButtonStats": "סטטיסטיקות",
|
||||||
"ButtonSubmit": "שלח",
|
"ButtonSubmit": "שלח",
|
||||||
"ButtonTest": "בדיקה",
|
"ButtonTest": "בדיקה",
|
||||||
|
"ButtonUnlinkOpenId": "נתק OpenID",
|
||||||
"ButtonUpload": "העלה",
|
"ButtonUpload": "העלה",
|
||||||
"ButtonUploadBackup": "העלה גיבוי",
|
"ButtonUploadBackup": "העלה גיבוי",
|
||||||
"ButtonUploadCover": "העלה כריכה",
|
"ButtonUploadCover": "העלה כריכה",
|
||||||
|
|
@ -102,6 +111,7 @@
|
||||||
"ErrorUploadFetchMetadataNoResults": "לא ניתן לשלוף מטא-נתונים - נסה לעדכן כותרת ו/או יוצר",
|
"ErrorUploadFetchMetadataNoResults": "לא ניתן לשלוף מטא-נתונים - נסה לעדכן כותרת ו/או יוצר",
|
||||||
"ErrorUploadLacksTitle": "חובה לתת כותרת",
|
"ErrorUploadLacksTitle": "חובה לתת כותרת",
|
||||||
"HeaderAccount": "חשבון",
|
"HeaderAccount": "חשבון",
|
||||||
|
"HeaderAddCustomMetadataProvider": "הוסף ספק מטא-נתונים מותאם אישית",
|
||||||
"HeaderAdvanced": "מתקדם",
|
"HeaderAdvanced": "מתקדם",
|
||||||
"HeaderAppriseNotificationSettings": "הגדרות התראות של Apprise",
|
"HeaderAppriseNotificationSettings": "הגדרות התראות של Apprise",
|
||||||
"HeaderAudioTracks": "רצועות קול",
|
"HeaderAudioTracks": "רצועות קול",
|
||||||
|
|
@ -147,13 +157,17 @@
|
||||||
"HeaderMetadataToEmbed": "מטא-נתונים להטמעה",
|
"HeaderMetadataToEmbed": "מטא-נתונים להטמעה",
|
||||||
"HeaderNewAccount": "חשבון חדש",
|
"HeaderNewAccount": "חשבון חדש",
|
||||||
"HeaderNewLibrary": "ספרייה חדשה",
|
"HeaderNewLibrary": "ספרייה חדשה",
|
||||||
|
"HeaderNotificationCreate": "צור התראה",
|
||||||
|
"HeaderNotificationUpdate": "עדכון התראה",
|
||||||
"HeaderNotifications": "התראות",
|
"HeaderNotifications": "התראות",
|
||||||
"HeaderOpenIDConnectAuthentication": "אימות OpenID Connect",
|
"HeaderOpenIDConnectAuthentication": "אימות OpenID Connect",
|
||||||
|
"HeaderOpenListeningSessions": "פתח הפעלות האזנה",
|
||||||
"HeaderOpenRSSFeed": "פתח ערוץ RSS",
|
"HeaderOpenRSSFeed": "פתח ערוץ RSS",
|
||||||
"HeaderOtherFiles": "קבצים אחרים",
|
"HeaderOtherFiles": "קבצים אחרים",
|
||||||
"HeaderPasswordAuthentication": "אימות סיסמה",
|
"HeaderPasswordAuthentication": "אימות סיסמה",
|
||||||
"HeaderPermissions": "הרשאות",
|
"HeaderPermissions": "הרשאות",
|
||||||
"HeaderPlayerQueue": "תור ניגון",
|
"HeaderPlayerQueue": "תור ניגון",
|
||||||
|
"HeaderPlayerSettings": "הגדרות נגן",
|
||||||
"HeaderPlaylist": "רשימת השמעה",
|
"HeaderPlaylist": "רשימת השמעה",
|
||||||
"HeaderPlaylistItems": "פריטי רשימת השמעה",
|
"HeaderPlaylistItems": "פריטי רשימת השמעה",
|
||||||
"HeaderPodcastsToAdd": "פודקאסטים להוספה",
|
"HeaderPodcastsToAdd": "פודקאסטים להוספה",
|
||||||
|
|
@ -165,6 +179,7 @@
|
||||||
"HeaderRemoveEpisodes": "הסר {0} פרקים",
|
"HeaderRemoveEpisodes": "הסר {0} פרקים",
|
||||||
"HeaderSavedMediaProgress": "התקדמות מדיה שמורה",
|
"HeaderSavedMediaProgress": "התקדמות מדיה שמורה",
|
||||||
"HeaderSchedule": "תיזמון",
|
"HeaderSchedule": "תיזמון",
|
||||||
|
"HeaderScheduleEpisodeDownloads": "תזמן הורדת פרקים אוטומטית",
|
||||||
"HeaderScheduleLibraryScans": "קבע סריקות ספרייה אוטומטיות",
|
"HeaderScheduleLibraryScans": "קבע סריקות ספרייה אוטומטיות",
|
||||||
"HeaderSession": "הפעלה",
|
"HeaderSession": "הפעלה",
|
||||||
"HeaderSetBackupSchedule": "קבע לוח זמנים לגיבוי",
|
"HeaderSetBackupSchedule": "קבע לוח זמנים לגיבוי",
|
||||||
|
|
@ -190,6 +205,9 @@
|
||||||
"HeaderYearReview": "שנת {0} בסקירה",
|
"HeaderYearReview": "שנת {0} בסקירה",
|
||||||
"HeaderYourStats": "הסטטיסטיקות שלך",
|
"HeaderYourStats": "הסטטיסטיקות שלך",
|
||||||
"LabelAbridged": "מקוצר",
|
"LabelAbridged": "מקוצר",
|
||||||
|
"LabelAbridgedChecked": "מקוצר (מסומן)",
|
||||||
|
"LabelAbridgedUnchecked": "בלתי מקוצר (לא מסומן)",
|
||||||
|
"LabelAccessibleBy": "נגיש על ידי",
|
||||||
"LabelAccountType": "סוג חשבון",
|
"LabelAccountType": "סוג חשבון",
|
||||||
"LabelAccountTypeAdmin": "מנהל",
|
"LabelAccountTypeAdmin": "מנהל",
|
||||||
"LabelAccountTypeGuest": "אורח",
|
"LabelAccountTypeGuest": "אורח",
|
||||||
|
|
@ -200,13 +218,18 @@
|
||||||
"LabelAddToPlaylist": "הוסף לרשימת השמעה",
|
"LabelAddToPlaylist": "הוסף לרשימת השמעה",
|
||||||
"LabelAddToPlaylistBatch": "הוסף {0} פריטים לרשימת השמעה",
|
"LabelAddToPlaylistBatch": "הוסף {0} פריטים לרשימת השמעה",
|
||||||
"LabelAddedAt": "נוסף בתאריך",
|
"LabelAddedAt": "נוסף בתאריך",
|
||||||
|
"LabelAddedDate": "נוסף ב-{0}",
|
||||||
"LabelAdminUsersOnly": "רק מנהלים",
|
"LabelAdminUsersOnly": "רק מנהלים",
|
||||||
"LabelAll": "הכל",
|
"LabelAll": "הכל",
|
||||||
"LabelAllUsers": "כל המשתמשים",
|
"LabelAllUsers": "כל המשתמשים",
|
||||||
"LabelAllUsersExcludingGuests": "כל המשתמשים, ללא אורחים",
|
"LabelAllUsersExcludingGuests": "כל המשתמשים, ללא אורחים",
|
||||||
"LabelAllUsersIncludingGuests": "כל המשתמשים כולל אורחים",
|
"LabelAllUsersIncludingGuests": "כל המשתמשים כולל אורחים",
|
||||||
"LabelAlreadyInYourLibrary": "כבר קיים בספרייה שלך",
|
"LabelAlreadyInYourLibrary": "כבר קיים בספרייה שלך",
|
||||||
|
"LabelApiToken": "טוקן API",
|
||||||
"LabelAppend": "הוסף לסוף",
|
"LabelAppend": "הוסף לסוף",
|
||||||
|
"LabelAudioBitrate": "קצב סיביות (לדוגמא 128k)",
|
||||||
|
"LabelAudioChannels": "ערוצי קול (1 או 2)",
|
||||||
|
"LabelAudioCodec": "קידוד קול",
|
||||||
"LabelAuthor": "יוצר",
|
"LabelAuthor": "יוצר",
|
||||||
"LabelAuthorFirstLast": "יוצר (שם פרטי שם משפחה)",
|
"LabelAuthorFirstLast": "יוצר (שם פרטי שם משפחה)",
|
||||||
"LabelAuthorLastFirst": "יוצר (שם משפחה, שם פרטי)",
|
"LabelAuthorLastFirst": "יוצר (שם משפחה, שם פרטי)",
|
||||||
|
|
|
||||||
|
|
@ -66,13 +66,13 @@
|
||||||
"ButtonPurgeItemsCache": "Elimina la Cache selezionata",
|
"ButtonPurgeItemsCache": "Elimina la Cache selezionata",
|
||||||
"ButtonQueueAddItem": "Aggiungi alla Coda",
|
"ButtonQueueAddItem": "Aggiungi alla Coda",
|
||||||
"ButtonQueueRemoveItem": "Rimuovi dalla Coda",
|
"ButtonQueueRemoveItem": "Rimuovi dalla Coda",
|
||||||
"ButtonQuickEmbed": "Quick Embed",
|
"ButtonQuickEmbed": "Incorporazione Rapida",
|
||||||
"ButtonQuickEmbedMetadata": "Incorporamento rapido Metadati",
|
"ButtonQuickEmbedMetadata": "Incorporamento rapido Metadati",
|
||||||
"ButtonQuickMatch": "Controlla Metadata Auto",
|
"ButtonQuickMatch": "Controlla Metadata Auto",
|
||||||
"ButtonReScan": "Ri-scansiona",
|
"ButtonReScan": "Ri-scansiona",
|
||||||
"ButtonRead": "Leggi",
|
"ButtonRead": "Leggi",
|
||||||
"ButtonReadLess": "Leggi di Meno",
|
"ButtonReadLess": "Riduci",
|
||||||
"ButtonReadMore": "Leggi di Più",
|
"ButtonReadMore": "Espandi",
|
||||||
"ButtonRefresh": "Aggiorna",
|
"ButtonRefresh": "Aggiorna",
|
||||||
"ButtonRemove": "Rimuovi",
|
"ButtonRemove": "Rimuovi",
|
||||||
"ButtonRemoveAll": "Rimuovi Tutto",
|
"ButtonRemoveAll": "Rimuovi Tutto",
|
||||||
|
|
@ -220,7 +220,7 @@
|
||||||
"LabelAddToPlaylist": "Aggiungi alla playlist",
|
"LabelAddToPlaylist": "Aggiungi alla playlist",
|
||||||
"LabelAddToPlaylistBatch": "Aggiungi {0} file alla Playlist",
|
"LabelAddToPlaylistBatch": "Aggiungi {0} file alla Playlist",
|
||||||
"LabelAddedAt": "Aggiunto il",
|
"LabelAddedAt": "Aggiunto il",
|
||||||
"LabelAddedDate": "{0} aggiunti",
|
"LabelAddedDate": "Aggiunti {0}",
|
||||||
"LabelAdminUsersOnly": "Solo utenti Amministratori",
|
"LabelAdminUsersOnly": "Solo utenti Amministratori",
|
||||||
"LabelAll": "Tutti",
|
"LabelAll": "Tutti",
|
||||||
"LabelAllUsers": "Tutti gli Utenti",
|
"LabelAllUsers": "Tutti gli Utenti",
|
||||||
|
|
@ -495,7 +495,7 @@
|
||||||
"LabelProviderAuthorizationValue": "Authorization Header Value",
|
"LabelProviderAuthorizationValue": "Authorization Header Value",
|
||||||
"LabelPubDate": "Data di pubblicazione",
|
"LabelPubDate": "Data di pubblicazione",
|
||||||
"LabelPublishYear": "Anno di pubblicazione",
|
"LabelPublishYear": "Anno di pubblicazione",
|
||||||
"LabelPublishedDate": "{0} pubblicati",
|
"LabelPublishedDate": "Pubblicati {0}",
|
||||||
"LabelPublishedDecade": "Decennio di pubblicazione",
|
"LabelPublishedDecade": "Decennio di pubblicazione",
|
||||||
"LabelPublishedDecades": "Decenni di pubblicazione",
|
"LabelPublishedDecades": "Decenni di pubblicazione",
|
||||||
"LabelPublisher": "Editore",
|
"LabelPublisher": "Editore",
|
||||||
|
|
@ -682,7 +682,7 @@
|
||||||
"LabelXBooks": "{0} libri",
|
"LabelXBooks": "{0} libri",
|
||||||
"LabelXItems": "{0} oggetti",
|
"LabelXItems": "{0} oggetti",
|
||||||
"LabelYearReviewHide": "Nascondi Anno in rassegna",
|
"LabelYearReviewHide": "Nascondi Anno in rassegna",
|
||||||
"LabelYearReviewShow": "Vedi Anno in rassegna",
|
"LabelYearReviewShow": "Mostra Anno in rassegna",
|
||||||
"LabelYourAudiobookDuration": "La durata dell'audiolibro",
|
"LabelYourAudiobookDuration": "La durata dell'audiolibro",
|
||||||
"LabelYourBookmarks": "I tuoi preferiti",
|
"LabelYourBookmarks": "I tuoi preferiti",
|
||||||
"LabelYourPlaylists": "le tue Playlist",
|
"LabelYourPlaylists": "le tue Playlist",
|
||||||
|
|
@ -779,7 +779,7 @@
|
||||||
"MessageNoBackups": "Nessun Backup",
|
"MessageNoBackups": "Nessun Backup",
|
||||||
"MessageNoBookmarks": "Nessun preferito",
|
"MessageNoBookmarks": "Nessun preferito",
|
||||||
"MessageNoChapters": "Nessun capitolo",
|
"MessageNoChapters": "Nessun capitolo",
|
||||||
"MessageNoCollections": "Nessuna Raccolta",
|
"MessageNoCollections": "Nessuna Collezione",
|
||||||
"MessageNoCoversFound": "Nessuna Cover Trovata",
|
"MessageNoCoversFound": "Nessuna Cover Trovata",
|
||||||
"MessageNoDescription": "Nessuna descrizione",
|
"MessageNoDescription": "Nessuna descrizione",
|
||||||
"MessageNoDevices": "nessun dispositivo",
|
"MessageNoDevices": "nessun dispositivo",
|
||||||
|
|
|
||||||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.17.1",
|
"version": "2.17.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.17.1",
|
"version": "2.17.2",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.17.1",
|
"version": "2.17.2",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast server",
|
"description": "Self-hosted audiobook and podcast server",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,13 @@ Is there a feature you are looking for? [Suggest it](https://github.com/advplyr/
|
||||||
|
|
||||||
Join us on [Discord](https://discord.gg/HQgCbd6E75)
|
Join us on [Discord](https://discord.gg/HQgCbd6E75)
|
||||||
|
|
||||||
|
### Demo
|
||||||
|
|
||||||
|
Check out the web client demo: https://audiobooks.dev/ (thanks for hosting [@Vito0912](https://github.com/Vito0912)!)
|
||||||
|
|
||||||
|
Username/password: `demo`/`demo` (user account)
|
||||||
|
|
||||||
|
|
||||||
### Android App (beta)
|
### Android App (beta)
|
||||||
|
|
||||||
Try it out on the [Google Play Store](https://play.google.com/store/apps/details?id=com.audiobookshelf.app)
|
Try it out on the [Google Play Store](https://play.google.com/store/apps/details?id=com.audiobookshelf.app)
|
||||||
|
|
|
||||||
|
|
@ -406,11 +406,6 @@ class Database {
|
||||||
return Promise.all(oldBooks.map((oldBook) => this.models.book.saveFromOld(oldBook)))
|
return Promise.all(oldBooks.map((oldBook) => this.models.book.saveFromOld(oldBook)))
|
||||||
}
|
}
|
||||||
|
|
||||||
removeLibrary(libraryId) {
|
|
||||||
if (!this.sequelize) return false
|
|
||||||
return this.models.library.removeById(libraryId)
|
|
||||||
}
|
|
||||||
|
|
||||||
createBulkCollectionBooks(collectionBooks) {
|
createBulkCollectionBooks(collectionBooks) {
|
||||||
if (!this.sequelize) return false
|
if (!this.sequelize) return false
|
||||||
return this.models.collectionBook.bulkCreate(collectionBooks)
|
return this.models.collectionBook.bulkCreate(collectionBooks)
|
||||||
|
|
|
||||||
|
|
@ -194,18 +194,21 @@ class Server {
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
|
|
||||||
/**
|
|
||||||
* @temporary
|
|
||||||
* This is necessary for the ebook & cover API endpoint in the mobile apps
|
|
||||||
* The mobile app ereader is using fetch api in Capacitor that is currently difficult to switch to native requests
|
|
||||||
* so we have to allow cors for specific origins to the /api/items/:id/ebook endpoint
|
|
||||||
* The cover image is fetched with XMLHttpRequest in the mobile apps to load into a canvas and extract colors
|
|
||||||
* @see https://ionicframework.com/docs/troubleshooting/cors
|
|
||||||
*
|
|
||||||
* Running in development allows cors to allow testing the mobile apps in the browser
|
|
||||||
* or env variable ALLOW_CORS = '1'
|
|
||||||
*/
|
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
|
// Prevent clickjacking by disallowing iframes
|
||||||
|
res.setHeader('Content-Security-Policy', "frame-ancestors 'self'")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @temporary
|
||||||
|
* This is necessary for the ebook & cover API endpoint in the mobile apps
|
||||||
|
* The mobile app ereader is using fetch api in Capacitor that is currently difficult to switch to native requests
|
||||||
|
* so we have to allow cors for specific origins to the /api/items/:id/ebook endpoint
|
||||||
|
* The cover image is fetched with XMLHttpRequest in the mobile apps to load into a canvas and extract colors
|
||||||
|
* @see https://ionicframework.com/docs/troubleshooting/cors
|
||||||
|
*
|
||||||
|
* Running in development allows cors to allow testing the mobile apps in the browser
|
||||||
|
* or env variable ALLOW_CORS = '1'
|
||||||
|
*/
|
||||||
if (Logger.isDev || req.path.match(/\/api\/items\/([a-z0-9-]{36})\/(ebook|cover)(\/[0-9]+)?/)) {
|
if (Logger.isDev || req.path.match(/\/api\/items\/([a-z0-9-]{36})\/(ebook|cover)(\/[0-9]+)?/)) {
|
||||||
const allowedOrigins = ['capacitor://localhost', 'http://localhost']
|
const allowedOrigins = ['capacitor://localhost', 'http://localhost']
|
||||||
if (global.AllowCors || Logger.isDev || allowedOrigins.some((o) => o === req.get('origin'))) {
|
if (global.AllowCors || Logger.isDev || allowedOrigins.some((o) => o === req.get('origin'))) {
|
||||||
|
|
|
||||||
|
|
@ -504,8 +504,21 @@ class LibraryController {
|
||||||
await this.handleDeleteLibraryItem(libraryItem.mediaType, libraryItem.id, mediaItemIds)
|
await this.handleDeleteLibraryItem(libraryItem.mediaType, libraryItem.id, mediaItemIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set PlaybackSessions libraryId to null
|
||||||
|
const [sessionsUpdated] = await Database.playbackSessionModel.update(
|
||||||
|
{
|
||||||
|
libraryId: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
libraryId: req.library.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Logger.info(`[LibraryController] Updated ${sessionsUpdated} playback sessions to remove library id`)
|
||||||
|
|
||||||
const libraryJson = req.library.toOldJSON()
|
const libraryJson = req.library.toOldJSON()
|
||||||
await Database.removeLibrary(req.library.id)
|
await req.library.destroy()
|
||||||
|
|
||||||
// Re-order libraries
|
// Re-order libraries
|
||||||
await Database.libraryModel.resetDisplayOrder()
|
await Database.libraryModel.resetDisplayOrder()
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,14 @@ async function up({ context: { queryInterface, logger } }) {
|
||||||
type: 'UUID'
|
type: 'UUID'
|
||||||
})
|
})
|
||||||
|
|
||||||
logger.info('[2.17.0 migration] Changing mediaItemShares.mediaItemId column to UUID')
|
if (await queryInterface.tableExists('mediaItemShares')) {
|
||||||
await queryInterface.changeColumn('mediaItemShares', 'mediaItemId', {
|
logger.info('[2.17.0 migration] Changing mediaItemShares.mediaItemId column to UUID')
|
||||||
type: 'UUID'
|
await queryInterface.changeColumn('mediaItemShares', 'mediaItemId', {
|
||||||
})
|
type: 'UUID'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
logger.info('[2.17.0 migration] mediaItemShares table does not exist, skipping column change')
|
||||||
|
}
|
||||||
|
|
||||||
logger.info('[2.17.0 migration] Changing playbackSessions.mediaItemId column to UUID')
|
logger.info('[2.17.0 migration] Changing playbackSessions.mediaItemId column to UUID')
|
||||||
await queryInterface.changeColumn('playbackSessions', 'mediaItemId', {
|
await queryInterface.changeColumn('playbackSessions', 'mediaItemId', {
|
||||||
|
|
|
||||||
|
|
@ -107,19 +107,6 @@ class Library extends Model {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroy library by id
|
|
||||||
* @param {string} libraryId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
static removeById(libraryId) {
|
|
||||||
return this.destroy({
|
|
||||||
where: {
|
|
||||||
id: libraryId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all library ids
|
* Get all library ids
|
||||||
* @returns {Promise<string[]>} array of library ids
|
* @returns {Promise<string[]>} array of library ids
|
||||||
|
|
|
||||||
|
|
@ -479,7 +479,7 @@ class LibraryItem extends Model {
|
||||||
{
|
{
|
||||||
model: this.sequelize.models.series,
|
model: this.sequelize.models.series,
|
||||||
through: {
|
through: {
|
||||||
attributes: ['sequence']
|
attributes: ['id', 'sequence']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -611,7 +611,7 @@ class User extends Model {
|
||||||
*/
|
*/
|
||||||
getOldMediaProgress(libraryItemId, episodeId = null) {
|
getOldMediaProgress(libraryItemId, episodeId = null) {
|
||||||
const mediaProgress = this.mediaProgresses?.find((mp) => {
|
const mediaProgress = this.mediaProgresses?.find((mp) => {
|
||||||
if (episodeId && mp.mediaItemId === episodeId) return true
|
if (episodeId && mp.mediaItemId !== episodeId) return false
|
||||||
return mp.extraData?.libraryItemId === libraryItemId
|
return mp.extraData?.libraryItemId === libraryItemId
|
||||||
})
|
})
|
||||||
return mediaProgress?.getOldMediaProgress() || null
|
return mediaProgress?.getOldMediaProgress() || null
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue