Merge branch 'master' into feat/book-series-info

This commit is contained in:
Greg Lorenzen 2025-02-17 11:58:40 -08:00
commit 5e0a12394c
11 changed files with 101 additions and 24 deletions

View File

@ -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) {

View File

@ -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' }">

View File

@ -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",

View File

@ -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
View File

@ -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",

View File

@ -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",

View File

@ -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']
} }
} }
] ]

View File

@ -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

View File

@ -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)

View 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

View 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('{}')
})
})