mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-02-19 00:18:56 +01:00
Update global search, fix toggling between automated backup, add open search cover in new tab #83
This commit is contained in:
parent
59d12ef5de
commit
32bc9d5282
@ -18,7 +18,7 @@
|
||||
<p class="text-sm text-white text-opacity-70 leading-3 font-book pl-2">{{ libraryName }}</p>
|
||||
</div> -->
|
||||
<!-- </div> -->
|
||||
<div class="bg-black bg-opacity-20 rounded-sm py-1.5 px-3 flex items-center border border-bg text-white text-opacity-70 cursor-pointer hover:bg-opacity-10 hover:text-opacity-90" @click="clickLibrary">
|
||||
<div class="bg-black bg-opacity-20 rounded-md py-1.5 px-3 flex items-center text-white text-opacity-70 cursor-pointer hover:bg-opacity-10 hover:text-opacity-90" @click="clickLibrary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
|
||||
</svg>
|
||||
|
@ -77,6 +77,11 @@ export default {
|
||||
this.$store.commit('audiobooks/setSearchResults', this.searchResults)
|
||||
this.setBookshelfEntities()
|
||||
})
|
||||
},
|
||||
'$route.query.filter'() {
|
||||
if (this.$route.query.filter && this.$route.query.filter !== this.filterBy) {
|
||||
this.$store.dispatch('user/updateUserSettings', { filterBy: this.$route.query.filter })
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -171,6 +176,7 @@ export default {
|
||||
this.currSearchParams = this.buildSearchParams()
|
||||
|
||||
var entities = this.entities
|
||||
|
||||
var groups = []
|
||||
var currentRow = 0
|
||||
var currentGroup = []
|
||||
|
@ -4,13 +4,16 @@
|
||||
<template v-if="page !== 'search' && !isHome">
|
||||
<p v-if="!selectedSeries" class="font-book">{{ numShowing }} {{ entityName }}</p>
|
||||
<div v-else class="flex items-center">
|
||||
<div @click="seriesBackArrow" class="rounded-full h-10 w-10 flex items-center justify-center hover:bg-white hover:bg-opacity-10 cursor-pointer">
|
||||
<span class="material-icons text-3xl text-white">west</span>
|
||||
<div @click="seriesBackArrow" class="rounded-full h-9 w-9 flex items-center justify-center hover:bg-white hover:bg-opacity-10 cursor-pointer">
|
||||
<span class="material-icons text-2xl text-white">west</span>
|
||||
</div>
|
||||
<!-- <span class="material-icons text-2xl cursor-pointer" @click="seriesBackArrow">west</span> -->
|
||||
<p class="pl-4 font-book text-lg">
|
||||
{{ selectedSeries }} <span class="ml-3 font-mono text-lg bg-black bg-opacity-30 rounded-lg px-1 py-0.5">{{ numShowing }}</span>
|
||||
{{ selectedSeries }}
|
||||
</p>
|
||||
<div class="w-6 h-6 rounded-full bg-black bg-opacity-30 flex items-center justify-center ml-3">
|
||||
<span class="font-mono">{{ numShowing }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="flex h-full px-1 overflow-hidden">
|
||||
<cards-book-cover :audiobook="audiobook" :width="40" />
|
||||
<cards-book-cover :audiobook="audiobook" :width="50" />
|
||||
<div class="flex-grow px-2 searchCardContent h-full">
|
||||
<p class="truncate text-sm">{{ title }}</p>
|
||||
<p class="text-xs text-gray-200 truncate">by {{ author }}</p>
|
||||
@ -38,7 +38,7 @@ export default {
|
||||
<style>
|
||||
.searchCardContent {
|
||||
width: calc(100% - 80px);
|
||||
height: calc(40px * 1.5);
|
||||
height: calc(50px * 1.5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
32
client/components/cards/AuthorSearchCard.vue
Normal file
32
client/components/cards/AuthorSearchCard.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div class="flex h-full px-1 overflow-hidden">
|
||||
<img src="https://rpgplanner.com/wp-content/uploads/2020/06/no-photo-available.png" class="w-40 h-40 max-h-40 object-contain" style="max-height: 40px; max-width: 40px" />
|
||||
<div class="flex-grow px-2 authorSearchCardContent h-full">
|
||||
<p class="truncate text-sm">{{ author }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
author: String
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.searchCardContent {
|
||||
width: calc(100% - 80px);
|
||||
height: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
@ -1,10 +1,14 @@
|
||||
<template>
|
||||
<div class="relative rounded-sm overflow-hidden" :style="{ height: width * 1.6 + 'px', width: width + 'px', maxWidth: width + 'px', minWidth: width + 'px' }">
|
||||
<div class="relative rounded-sm overflow-hidden" :style="{ height: width * 1.6 + 'px', width: width + 'px', maxWidth: width + 'px', minWidth: width + 'px' }" @mouseover="isHovering = true" @mouseleave="isHovering = false">
|
||||
<div class="w-full h-full relative">
|
||||
<div v-if="showCoverBg" class="bg-primary absolute top-0 left-0 w-full h-full">
|
||||
<div class="w-full h-full z-0" ref="coverBg" />
|
||||
</div>
|
||||
<img ref="cover" :src="cover" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0" :class="showCoverBg ? 'object-contain' : 'object-cover'" />
|
||||
|
||||
<a v-if="!imageFailed && showOpenNewTab && isHovering" :href="cover" @click.stop target="_blank" class="absolute bg-primary flex items-center justify-center shadow-sm rounded-full hover:scale-110 transform duration-100" :style="{ top: sizeMultiplier * 0.5 + 'rem', right: sizeMultiplier * 0.5 + 'rem', width: 2.5 * sizeMultiplier + 'rem', height: 2.5 * sizeMultiplier + 'rem' }">
|
||||
<span class="material-icons" :style="{ fontSize: sizeMultiplier * 1.75 + 'rem' }">open_in_new</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div v-if="imageFailed" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-red-100" :style="{ padding: placeholderCoverPadding + 'rem' }">
|
||||
@ -23,12 +27,16 @@ export default {
|
||||
width: {
|
||||
type: Number,
|
||||
default: 120
|
||||
}
|
||||
},
|
||||
showOpenNewTab: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
imageFailed: false,
|
||||
showCoverBg: false
|
||||
showCoverBg: false,
|
||||
isHovering: false,
|
||||
naturalHeight: 0,
|
||||
naturalWidth: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -60,6 +68,9 @@ export default {
|
||||
imageLoaded() {
|
||||
if (this.$refs.cover) {
|
||||
var { naturalWidth, naturalHeight } = this.$refs.cover
|
||||
this.naturalHeight = naturalHeight
|
||||
this.naturalWidth = naturalWidth
|
||||
|
||||
var aspectRatio = naturalHeight / naturalWidth
|
||||
var arDiff = Math.abs(aspectRatio - 1.6)
|
||||
|
||||
|
36
client/components/cards/SeriesSearchCard.vue
Normal file
36
client/components/cards/SeriesSearchCard.vue
Normal file
@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="flex h-full px-1 overflow-hidden">
|
||||
<cards-group-cover :name="series" :book-items="bookItems" :width="60" :height="60" />
|
||||
<div class="flex-grow px-2 seriesSearchCardContent h-full">
|
||||
<p class="truncate text-sm">{{ series }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
series: String,
|
||||
bookItems: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.seriesSearchCardContent {
|
||||
width: calc(100% - 80px);
|
||||
height: 60px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="w-64 ml-8 relative">
|
||||
<div class="w-80 ml-6 relative">
|
||||
<form @submit.prevent="submitSearch">
|
||||
<ui-text-input ref="input" v-model="search" placeholder="Search.." @input="inputUpdate" @focus="focussed" @blur="blurred" class="w-full h-8 text-sm" />
|
||||
</form>
|
||||
@ -7,23 +7,42 @@
|
||||
<span v-if="!search" class="material-icons" style="font-size: 1.2rem">search</span>
|
||||
<span v-else class="material-icons" style="font-size: 1.2rem">close</span>
|
||||
</div>
|
||||
<div v-show="showMenu && (lastSearch || isTyping)" class="absolute z-40 -mt-px w-full bg-bg border border-black-200 shadow-lg max-h-80 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm">
|
||||
<div v-show="showMenu && (lastSearch || isTyping)" class="absolute z-40 -mt-px w-full bg-bg border border-black-200 shadow-lg rounded-md py-1 px-2 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm globalSearchMenu">
|
||||
<ul class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
||||
<li v-if="isTyping" class="py-2 px-2">
|
||||
<p>Typing...</p>
|
||||
<p>Thinking...</p>
|
||||
</li>
|
||||
<li v-else-if="isFetching" class="py-2 px-2">
|
||||
<p>Fetching...</p>
|
||||
</li>
|
||||
<li v-else-if="!items.length" class="py-2 px-2">
|
||||
<li v-else-if="!totalResults" class="py-2 px-2">
|
||||
<p>No Results</p>
|
||||
</li>
|
||||
<template v-else>
|
||||
<template v-for="item in items">
|
||||
<li :key="item.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickedOption(item)">
|
||||
<template v-if="item.type === 'audiobook'">
|
||||
<cards-audiobook-search-card :audiobook="item.data" />
|
||||
</template>
|
||||
<p class="uppercase text-xs text-gray-400 my-1 px-1 font-semibold">Books</p>
|
||||
<template v-for="item in audiobookResults">
|
||||
<li :key="item.audiobook.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option">
|
||||
<nuxt-link :to="`/audiobook/${item.audiobook.id}`">
|
||||
<cards-audiobook-search-card :audiobook="item.audiobook" />
|
||||
</nuxt-link>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<p v-if="authorResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">Authors</p>
|
||||
<template v-for="item in authorResults">
|
||||
<li :key="item.author" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option">
|
||||
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=authors.${$encode(item.author)}`">
|
||||
<cards-author-search-card :author="item.author" />
|
||||
</nuxt-link>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<p v-if="seriesResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">Series</p>
|
||||
<template v-for="item in seriesResults">
|
||||
<li :key="item.series" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickedOption(item.series)">
|
||||
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf/series?series=${$encode(item.series)}`">
|
||||
<cards-series-search-card :series="item.series" :book-items="item.audiobooks" />
|
||||
</nuxt-link>
|
||||
</li>
|
||||
</template>
|
||||
</template>
|
||||
@ -42,7 +61,9 @@ export default {
|
||||
isTyping: false,
|
||||
isFetching: false,
|
||||
search: null,
|
||||
items: [],
|
||||
audiobookResults: [],
|
||||
authorResults: [],
|
||||
seriesResults: [],
|
||||
searchTimeout: null,
|
||||
lastSearch: null
|
||||
}
|
||||
@ -53,6 +74,9 @@ export default {
|
||||
},
|
||||
currentLibraryId() {
|
||||
return this.$store.state.libraries.currentLibraryId
|
||||
},
|
||||
totalResults() {
|
||||
return this.audiobookResults.length + this.seriesResults.length + this.authorResults.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -61,7 +85,9 @@ export default {
|
||||
this.$router.push(`/library/${this.currentLibraryId}/bookshelf/search?query=${this.search}`)
|
||||
|
||||
this.search = null
|
||||
this.items = []
|
||||
this.audiobookResults = []
|
||||
this.authorResults = []
|
||||
this.seriesResults = []
|
||||
this.showMenu = false
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.input) {
|
||||
@ -86,22 +112,19 @@ export default {
|
||||
return
|
||||
}
|
||||
this.isFetching = true
|
||||
var results = await this.$axios.$get(`/api/audiobooks?q=${value}`).catch((error) => {
|
||||
|
||||
var searchResults = await this.$axios.$get(`/api/library/${this.currentLibraryId}/search?q=${value}`).catch((error) => {
|
||||
console.error('Search error', error)
|
||||
return []
|
||||
})
|
||||
this.audiobookResults = searchResults.audiobooks || []
|
||||
this.authorResults = searchResults.authors || []
|
||||
this.seriesResults = searchResults.series || []
|
||||
|
||||
this.isFetching = false
|
||||
if (!this.showMenu) {
|
||||
return
|
||||
}
|
||||
|
||||
this.items = results.map((res) => {
|
||||
return {
|
||||
id: res.id,
|
||||
data: res,
|
||||
type: 'audiobook'
|
||||
}
|
||||
})
|
||||
},
|
||||
inputUpdate(val) {
|
||||
clearTimeout(this.searchTimeout)
|
||||
@ -114,21 +137,25 @@ export default {
|
||||
this.searchTimeout = setTimeout(() => {
|
||||
this.isTyping = false
|
||||
this.runSearch(val)
|
||||
}, 1000)
|
||||
},
|
||||
clickedOption(option) {
|
||||
if (option.type === 'audiobook') {
|
||||
this.$router.push(`/audiobook/${option.data.id}`)
|
||||
}
|
||||
}, 750)
|
||||
},
|
||||
clickClear() {
|
||||
if (this.search) {
|
||||
this.search = null
|
||||
this.items = []
|
||||
this.lastSearch = null
|
||||
this.audiobookResults = []
|
||||
this.authorResults = []
|
||||
this.seriesResults = []
|
||||
this.showMenu = false
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.globalSearchMenu {
|
||||
max-height: 80vh;
|
||||
}
|
||||
</style>
|
@ -53,14 +53,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div v-if="hasSearched" class="flex items-center flex-wrap justify-center max-h-60 overflow-y-scroll mt-2 max-w-full">
|
||||
<div v-if="hasSearched" class="flex items-center flex-wrap justify-center max-h-80 overflow-y-scroll mt-2 max-w-full">
|
||||
<p v-if="!coversFound.length">No Covers Found</p>
|
||||
<template v-for="cover in coversFound">
|
||||
<div :key="cover" class="m-0.5 border-2 border-transparent hover:border-yellow-300 cursor-pointer" :class="cover === imageUrl ? 'border-yellow-300' : ''" @click="setCover(cover)">
|
||||
<div class="h-24 bg-primary" style="width: 60px">
|
||||
<img :src="cover" class="h-full w-full object-contain" />
|
||||
</div>
|
||||
<!-- <img :src="cover" class="h-24 object-cover" style="width: 60px" /> -->
|
||||
<cards-preview-cover :src="cover" :width="80" show-open-new-tab />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "1.4.2",
|
||||
"version": "1.4.3",
|
||||
"description": "Audiobook manager and player",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf",
|
||||
"version": "1.4.2",
|
||||
"version": "1.4.3",
|
||||
"description": "Self-hosted audiobook server for managing and playing audiobooks",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -30,6 +30,7 @@ class ApiController {
|
||||
this.router.get('/find/:method', this.find.bind(this))
|
||||
|
||||
this.router.get('/libraries', this.getLibraries.bind(this))
|
||||
this.router.get('/library/:id/search', this.searchLibrary.bind(this))
|
||||
this.router.get('/library/:id', this.getLibrary.bind(this))
|
||||
this.router.delete('/library/:id', this.deleteLibrary.bind(this))
|
||||
this.router.patch('/library/:id', this.updateLibrary.bind(this))
|
||||
@ -97,6 +98,53 @@ class ApiController {
|
||||
res.json(libraries)
|
||||
}
|
||||
|
||||
searchLibrary(req, res) {
|
||||
var library = this.db.libraries.find(lib => lib.id === req.params.id)
|
||||
if (!library) {
|
||||
return res.status(404).send('Library not found')
|
||||
}
|
||||
if (!req.query.q) {
|
||||
return res.status(400).send('No query string')
|
||||
}
|
||||
var maxResults = req.query.max || 3
|
||||
|
||||
var bookMatches = []
|
||||
var authorMatches = {}
|
||||
var seriesMatches = {}
|
||||
|
||||
var audiobooksInLibrary = this.db.audiobooks.filter(ab => ab.libraryId === library.id)
|
||||
audiobooksInLibrary.forEach((ab) => {
|
||||
var queryResult = ab.searchQuery(req.query.q)
|
||||
if (queryResult.book) {
|
||||
bookMatches.push({
|
||||
audiobook: ab,
|
||||
matchKey: queryResult.book
|
||||
})
|
||||
}
|
||||
if (queryResult.author && !authorMatches[queryResult.author]) {
|
||||
authorMatches[queryResult.author] = {
|
||||
author: queryResult.author
|
||||
}
|
||||
}
|
||||
if (queryResult.series) {
|
||||
if (!seriesMatches[queryResult.series]) {
|
||||
seriesMatches[queryResult.series] = {
|
||||
series: queryResult.series,
|
||||
audiobooks: [ab]
|
||||
}
|
||||
} else {
|
||||
seriesMatches[queryResult.series].audiobooks.push(ab)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
res.json({
|
||||
audiobooks: bookMatches.slice(0, maxResults),
|
||||
authors: Object.values(authorMatches).slice(0, maxResults),
|
||||
series: Object.values(seriesMatches).slice(0, maxResults)
|
||||
})
|
||||
}
|
||||
|
||||
getLibrary(req, res) {
|
||||
var library = this.db.libraries.find(lib => lib.id === req.params.id)
|
||||
if (!library) {
|
||||
@ -563,8 +611,14 @@ class ApiController {
|
||||
if (!settingsUpdate || !isObject(settingsUpdate)) {
|
||||
return res.status(500).send('Invalid settings update object')
|
||||
}
|
||||
|
||||
var madeUpdates = this.db.serverSettings.update(settingsUpdate)
|
||||
if (madeUpdates) {
|
||||
// If backup schedule is updated - update backup manager
|
||||
if (settingsUpdate.backupSchedule !== undefined) {
|
||||
this.backupManager.updateCronSchedule()
|
||||
}
|
||||
|
||||
await this.db.updateEntity('settings', this.db.serverSettings)
|
||||
}
|
||||
return res.json({
|
||||
|
@ -21,6 +21,8 @@ class BackupManager {
|
||||
this.Gid = Gid
|
||||
this.db = db
|
||||
|
||||
this.scheduleTask = null
|
||||
|
||||
this.backups = []
|
||||
}
|
||||
|
||||
@ -28,7 +30,7 @@ class BackupManager {
|
||||
return this.db.serverSettings || {}
|
||||
}
|
||||
|
||||
async init(overrideCron = null) {
|
||||
async init() {
|
||||
var backupsDirExists = await fs.pathExists(this.BackupPath)
|
||||
if (!backupsDirExists) {
|
||||
await fs.ensureDir(this.BackupPath)
|
||||
@ -36,16 +38,34 @@ class BackupManager {
|
||||
}
|
||||
|
||||
await this.loadBackups()
|
||||
this.scheduleCron()
|
||||
}
|
||||
|
||||
scheduleCron() {
|
||||
if (!this.serverSettings.backupSchedule) {
|
||||
Logger.info(`[BackupManager] Auto Backups are disabled`)
|
||||
return
|
||||
}
|
||||
try {
|
||||
var cronSchedule = overrideCron || this.serverSettings.backupSchedule
|
||||
cron.schedule(cronSchedule, this.runBackup.bind(this))
|
||||
var cronSchedule = this.serverSettings.backupSchedule
|
||||
this.scheduleTask = cron.schedule(cronSchedule, this.runBackup.bind(this))
|
||||
} catch (error) {
|
||||
Logger.error(`[BackupManager] Failed to schedule backup cron ${this.serverSettings.backupSchedule}`)
|
||||
Logger.error(`[BackupManager] Failed to schedule backup cron ${this.serverSettings.backupSchedule}`, error)
|
||||
}
|
||||
}
|
||||
|
||||
updateCronSchedule() {
|
||||
if (this.scheduleTask && !this.serverSettings.backupSchedule) {
|
||||
Logger.info(`[BackupManager] Disabling backup schedule`)
|
||||
if (this.scheduleTask.destroy) this.scheduleTask.destroy()
|
||||
this.scheduleTask = null
|
||||
} else if (!this.scheduleTask && this.serverSettings.backupSchedule) {
|
||||
Logger.info(`[BackupManager] Starting backup schedule ${this.serverSettings.backupSchedule}`)
|
||||
this.scheduleCron()
|
||||
} else if (this.serverSettings.backupSchedule) {
|
||||
Logger.info(`[BackupManager] Restarting backup schedule ${this.serverSettings.backupSchedule}`)
|
||||
if (this.scheduleTask.destroy) this.scheduleTask.destroy()
|
||||
this.scheduleCron()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -623,6 +623,10 @@ class Audiobook {
|
||||
return this.book.isSearchMatch(search.toLowerCase().trim())
|
||||
}
|
||||
|
||||
searchQuery(search) {
|
||||
return this.book.getQueryMatches(search.toLowerCase().trim())
|
||||
}
|
||||
|
||||
getAudioFileByIno(ino) {
|
||||
return this.audioFiles.find(af => af.ino === ino)
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ const parseAuthors = require('../utils/parseAuthors')
|
||||
|
||||
class Book {
|
||||
constructor(book = null) {
|
||||
this.olid = null
|
||||
this.title = null
|
||||
this.subtitle = null
|
||||
this.author = null
|
||||
@ -46,7 +45,6 @@ class Book {
|
||||
}
|
||||
|
||||
construct(book) {
|
||||
this.olid = book.olid
|
||||
this.title = book.title
|
||||
this.subtitle = book.subtitle || null
|
||||
this.author = book.author
|
||||
@ -69,7 +67,6 @@ class Book {
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
olid: this.olid,
|
||||
title: this.title,
|
||||
subtitle: this.subtitle,
|
||||
author: this.author,
|
||||
@ -111,7 +108,6 @@ class Book {
|
||||
}
|
||||
|
||||
setData(data) {
|
||||
this.olid = data.olid || null
|
||||
this.title = data.title || null
|
||||
this.subtitle = data.subtitle || null
|
||||
this.author = data.author || null
|
||||
@ -217,6 +213,17 @@ class Book {
|
||||
return this._title.toLowerCase().includes(search) || this._subtitle.toLowerCase().includes(search) || this._author.toLowerCase().includes(search) || this._series.toLowerCase().includes(search)
|
||||
}
|
||||
|
||||
getQueryMatches(search) {
|
||||
var titleMatch = this._title.toLowerCase().includes(search) || this._subtitle.toLowerCase().includes(search)
|
||||
var authorMatch = this._author.toLowerCase().includes(search)
|
||||
var seriesMatch = this._series.toLowerCase().includes(search)
|
||||
return {
|
||||
book: titleMatch ? 'title' : authorMatch ? 'author' : seriesMatch ? 'series' : false,
|
||||
author: authorMatch ? this._author : false,
|
||||
series: seriesMatch ? this._series : false
|
||||
}
|
||||
}
|
||||
|
||||
setDetailsFromFileMetadata(audioFileMetadata) {
|
||||
const MetadataMapArray = [
|
||||
{
|
||||
|
@ -5,6 +5,7 @@ class Library {
|
||||
this.id = null
|
||||
this.name = null
|
||||
this.folders = []
|
||||
this.icon = 'database'
|
||||
|
||||
this.lastScan = 0
|
||||
|
||||
@ -24,6 +25,8 @@ class Library {
|
||||
this.id = library.id
|
||||
this.name = library.name
|
||||
this.folders = (library.folders || []).map(f => new Folder(f))
|
||||
this.icon = library.icon || 'database'
|
||||
|
||||
this.createdAt = library.createdAt
|
||||
this.lastUpdate = library.lastUpdate
|
||||
}
|
||||
@ -33,6 +36,7 @@ class Library {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
folders: (this.folders || []).map(f => f.toJSON()),
|
||||
icon: this.icon,
|
||||
createdAt: this.createdAt,
|
||||
lastUpdate: this.lastUpdate
|
||||
}
|
||||
@ -55,6 +59,7 @@ class Library {
|
||||
return newFolder
|
||||
})
|
||||
}
|
||||
this.icon = data.icon || 'database'
|
||||
this.createdAt = Date.now()
|
||||
this.lastUpdate = Date.now()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user