mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-07-31 13:51:30 +02:00
Merge branch 'master' into feat/book-series-info
This commit is contained in:
commit
5e0a12394c
@ -85,7 +85,8 @@ export default {
|
|||||||
displayTitle: null,
|
displayTitle: null,
|
||||||
currentPlaybackRate: 1,
|
currentPlaybackRate: 1,
|
||||||
syncFailedToast: null,
|
syncFailedToast: null,
|
||||||
coverAspectRatio: 1
|
coverAspectRatio: 1,
|
||||||
|
lastChapterId: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -236,12 +237,16 @@ export default {
|
|||||||
}
|
}
|
||||||
}, 1000)
|
}, 1000)
|
||||||
},
|
},
|
||||||
checkChapterEnd(time) {
|
checkChapterEnd() {
|
||||||
if (!this.currentChapter) return
|
if (!this.currentChapter) return
|
||||||
const chapterEndTime = this.currentChapter.end
|
|
||||||
const tolerance = 0.75
|
// Track chapter transitions by comparing current chapter with last chapter
|
||||||
if (time >= chapterEndTime - tolerance) {
|
if (this.lastChapterId !== this.currentChapter.id) {
|
||||||
this.sleepTimerEnd()
|
// Chapter changed - if we had a previous chapter, this means we crossed a boundary
|
||||||
|
if (this.lastChapterId) {
|
||||||
|
this.sleepTimerEnd()
|
||||||
|
}
|
||||||
|
this.lastChapterId = this.currentChapter.id
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sleepTimerEnd() {
|
sleepTimerEnd() {
|
||||||
@ -301,7 +306,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.sleepTimerType === this.$constants.SleepTimerTypes.CHAPTER && this.sleepTimerSet) {
|
if (this.sleepTimerType === this.$constants.SleepTimerTypes.CHAPTER && this.sleepTimerSet) {
|
||||||
this.checkChapterEnd(time)
|
this.checkChapterEnd()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setDuration(duration) {
|
setDuration(duration) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full h-full overflow-hidden overflow-y-auto px-2 sm:px-4 py-6 relative">
|
<div class="w-full h-full overflow-hidden overflow-y-auto px-2 sm:px-4 py-6 relative">
|
||||||
<div class="flex flex-col sm:flex-row mb-4">
|
<div class="flex flex-col sm:flex-row mb-4">
|
||||||
<div class="relative self-center">
|
<div class="relative self-center md:self-start">
|
||||||
<covers-preview-cover :src="$store.getters['globals/getLibraryItemCoverSrcById'](libraryItemId, libraryItemUpdatedAt, true)" :width="120" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
<covers-preview-cover :src="$store.getters['globals/getLibraryItemCoverSrcById'](libraryItemId, libraryItemUpdatedAt, true)" :width="120" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||||
|
|
||||||
<!-- book cover overlay -->
|
<!-- book cover overlay -->
|
||||||
@ -36,7 +36,7 @@
|
|||||||
<ui-btn small @click="showLocalCovers = !showLocalCovers">{{ showLocalCovers ? $strings.ButtonHide : $strings.ButtonShow }}</ui-btn>
|
<ui-btn small @click="showLocalCovers = !showLocalCovers">{{ showLocalCovers ? $strings.ButtonHide : $strings.ButtonShow }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="showLocalCovers" class="flex items-center justify-center pb-2">
|
<div v-if="showLocalCovers" class="flex items-center justify-center flex-wrap pb-2">
|
||||||
<template v-for="localCoverFile in localCovers">
|
<template v-for="localCoverFile in localCovers">
|
||||||
<div :key="localCoverFile.ino" class="m-0.5 mb-5 border-2 border-transparent hover:border-yellow-300 cursor-pointer" :class="localCoverFile.metadata.path === coverPath ? 'border-yellow-300' : ''" @click="setCover(localCoverFile)">
|
<div :key="localCoverFile.ino" class="m-0.5 mb-5 border-2 border-transparent hover:border-yellow-300 cursor-pointer" :class="localCoverFile.metadata.path === coverPath ? 'border-yellow-300' : ''" @click="setCover(localCoverFile)">
|
||||||
<div class="h-24 bg-primary" :style="{ width: 96 / bookCoverAspectRatio + 'px' }">
|
<div class="h-24 bg-primary" :style="{ width: 96 / bookCoverAspectRatio + 'px' }">
|
||||||
|
4
client/package-lock.json
generated
4
client/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.19.2",
|
"version": "2.19.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.19.2",
|
"version": "2.19.3",
|
||||||
"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.19.2",
|
"version": "2.19.3",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast client",
|
"description": "Self-hosted audiobook and podcast client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.19.2",
|
"version": "2.19.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.19.2",
|
"version": "2.19.3",
|
||||||
"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.19.2",
|
"version": "2.19.3",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast server",
|
"description": "Self-hosted audiobook and podcast server",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
@ -103,7 +103,7 @@ class LibraryItem extends Model {
|
|||||||
{
|
{
|
||||||
model: this.sequelize.models.series,
|
model: this.sequelize.models.series,
|
||||||
through: {
|
through: {
|
||||||
attributes: ['sequence', 'createdAt']
|
attributes: ['id', 'sequence', 'createdAt']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -48,13 +48,7 @@ class Scanner {
|
|||||||
let updatePayload = {}
|
let updatePayload = {}
|
||||||
let hasUpdated = false
|
let hasUpdated = false
|
||||||
|
|
||||||
let existingAuthors = [] // Used for checking if authors or series are now empty
|
|
||||||
let existingSeries = []
|
|
||||||
|
|
||||||
if (libraryItem.isBook) {
|
if (libraryItem.isBook) {
|
||||||
existingAuthors = libraryItem.media.authors.map((a) => a.id)
|
|
||||||
existingSeries = libraryItem.media.series.map((s) => s.id)
|
|
||||||
|
|
||||||
const searchISBN = options.isbn || libraryItem.media.isbn
|
const searchISBN = options.isbn || libraryItem.media.isbn
|
||||||
const searchASIN = options.asin || libraryItem.media.asin
|
const searchASIN = options.asin || libraryItem.media.asin
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ const authorFilters = require('./authorFilters')
|
|||||||
|
|
||||||
const ShareManager = require('../../managers/ShareManager')
|
const ShareManager = require('../../managers/ShareManager')
|
||||||
const { profile } = require('../profiler')
|
const { profile } = require('../profiler')
|
||||||
|
const stringifySequelizeQuery = require('../stringifySequelizeQuery')
|
||||||
const countCache = new Map()
|
const countCache = new Map()
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -345,7 +345,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async findAndCountAll(findOptions, limit, offset) {
|
async findAndCountAll(findOptions, limit, offset) {
|
||||||
const findOptionsKey = JSON.stringify(findOptions)
|
const findOptionsKey = stringifySequelizeQuery(findOptions)
|
||||||
Logger.debug(`[LibraryItemsBookFilters] findOptionsKey: ${findOptionsKey}`)
|
Logger.debug(`[LibraryItemsBookFilters] findOptionsKey: ${findOptionsKey}`)
|
||||||
|
|
||||||
findOptions.limit = limit || null
|
findOptions.limit = limit || null
|
||||||
@ -353,6 +353,7 @@ module.exports = {
|
|||||||
|
|
||||||
if (countCache.has(findOptionsKey)) {
|
if (countCache.has(findOptionsKey)) {
|
||||||
const rows = await Database.bookModel.findAll(findOptions)
|
const rows = await Database.bookModel.findAll(findOptions)
|
||||||
|
|
||||||
return { rows, count: countCache.get(findOptionsKey) }
|
return { rows, count: countCache.get(findOptionsKey) }
|
||||||
} else {
|
} else {
|
||||||
const result = await Database.bookModel.findAndCountAll(findOptions)
|
const result = await Database.bookModel.findAndCountAll(findOptions)
|
||||||
|
25
server/utils/stringifySequelizeQuery.js
Normal file
25
server/utils/stringifySequelizeQuery.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
function stringifySequelizeQuery(findOptions) {
|
||||||
|
function isClass(func) {
|
||||||
|
return typeof func === 'function' && /^class\s/.test(func.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
function replacer(key, value) {
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
const symbols = Object.getOwnPropertySymbols(value).reduce((acc, sym) => {
|
||||||
|
acc[sym.toString()] = value[sym]
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
return { ...value, ...symbols }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isClass(value)) {
|
||||||
|
return `${value.name}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(findOptions, replacer)
|
||||||
|
}
|
||||||
|
module.exports = stringifySequelizeQuery
|
52
test/server/utils/stringifySequeslizeQuery.test.js
Normal file
52
test/server/utils/stringifySequeslizeQuery.test.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
const { expect } = require('chai')
|
||||||
|
const stringifySequelizeQuery = require('../../../server/utils/stringifySequelizeQuery')
|
||||||
|
const Sequelize = require('sequelize')
|
||||||
|
|
||||||
|
class DummyClass {}
|
||||||
|
|
||||||
|
describe('stringifySequelizeQuery', () => {
|
||||||
|
it('should stringify a sequelize query containing an op', () => {
|
||||||
|
const query = {
|
||||||
|
where: {
|
||||||
|
name: 'John',
|
||||||
|
age: {
|
||||||
|
[Sequelize.Op.gt]: 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = stringifySequelizeQuery(query)
|
||||||
|
expect(result).to.equal('{"where":{"name":"John","age":{"Symbol(gt)":20}}}')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should stringify a sequelize query containing a literal', () => {
|
||||||
|
const query = {
|
||||||
|
order: [[Sequelize.literal('libraryItem.title'), 'ASC']]
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = stringifySequelizeQuery(query)
|
||||||
|
expect(result).to.equal('{"order":{"0":{"0":{"val":"libraryItem.title"},"1":"ASC"}}}')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should stringify a sequelize query containing a class', () => {
|
||||||
|
const query = {
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: DummyClass
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = stringifySequelizeQuery(query)
|
||||||
|
expect(result).to.equal('{"include":{"0":{"model":"DummyClass"}}}')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should ignore non-class functions', () => {
|
||||||
|
const query = {
|
||||||
|
logging: (query) => console.log(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = stringifySequelizeQuery(query)
|
||||||
|
expect(result).to.equal('{}')
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user