This commit is contained in:
Dave van Iersel 2025-08-23 16:48:18 -05:00 committed by GitHub
commit 4d31efd825
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 91 additions and 30 deletions

View file

@ -1,13 +1,15 @@
<template>
<div id="epub-reader" class="h-full w-full">
<div class="h-full flex items-center justify-center">
<button type="button" aria-label="Previous page" class="w-24 max-w-24 h-full hidden sm:flex items-center overflow-x-hidden justify-center opacity-50 hover:opacity-100">
<button v-if="ereaderSettings.flow === 'paginated'" type="button" aria-label="Previous page" class="w-24 max-w-24 h-full hidden sm:flex items-center overflow-x-hidden justify-center opacity-50 hover:opacity-100">
<span v-if="hasPrev" class="material-symbols text-6xl" @mousedown.prevent @click="prev">chevron_left</span>
</button>
<div id="frame" class="w-full" style="height: 80%">
<div id="viewer"></div>
<div id="frame" class="w-full" :class="isScrolled() ? 'absolute bottom-0 h-[90%]' : 'h-[80%]'">
<div id="viewer" class="flex justify-center" :class="{ 'h-full': isScrolled() }"></div>
</div>
<button type="button" aria-label="Next page" class="w-24 max-w-24 h-full hidden sm:flex items-center justify-center overflow-x-hidden opacity-50 hover:opacity-100">
<button v-if="ereaderSettings.flow === 'paginated'" type="button" aria-label="Next page" class="w-24 max-w-24 h-full hidden sm:flex items-center justify-center overflow-x-hidden opacity-50 hover:opacity-100">
<span v-if="hasNext" class="material-symbols text-6xl" @mousedown.prevent @click="next">chevron_right</span>
</button>
</div>
@ -47,7 +49,9 @@ export default {
fontScale: 100,
lineSpacing: 115,
spread: 'auto',
textStroke: 0
textStroke: 0,
flow: 'paginated',
widthPercentage: 100
}
}
},
@ -85,12 +89,12 @@ export default {
return `ebookLocations-${this.libraryItemId}`
},
readerWidth() {
if (this.windowWidth < 640) return this.windowWidth
return this.windowWidth - 200
let width = Math.floor((this.windowWidth * this.ereaderSettings.widthPercentage) / 100)
return this.windowWidth < 640 || this.isScrolled() ? width : width - 200
},
readerHeight() {
if (this.windowHeight < 400 || !this.playerOpen) return this.windowHeight
return this.windowHeight - 164
return this.windowHeight < 400 || !this.playerOpen ? this.windowHeight : this.windowHeight - 164
},
ebookUrl() {
if (this.fileId) {
@ -103,21 +107,13 @@ export default {
const isDark = theme === 'dark'
const isSepia = theme === 'sepia'
const fontColor = isDark
? '#fff'
: isSepia
? '#5b4636'
: '#000'
const fontColor = isDark ? '#fff' : isSepia ? '#5b4636' : '#000'
const backgroundColor = isDark
? 'rgb(35 35 35)'
: isSepia
? 'rgb(244, 236, 216)'
: 'rgb(255, 255, 255)'
const backgroundColor = isDark ? 'rgb(35 35 35)' : isSepia ? 'rgb(244, 236, 216)' : 'rgb(255, 255, 255)'
const lineSpacing = this.ereaderSettings.lineSpacing / 100
const fontScale = this.ereaderSettings.fontScale / 100
const textStroke = this.ereaderSettings.textStroke / 100
const fontScale = this.ereaderSettings.fontScale / 100
const textStroke = this.ereaderSettings.textStroke / 100
return {
'*': {
@ -144,6 +140,11 @@ export default {
this.rendition.themes.fontSize(`${fontScale}%`)
this.rendition.themes.font(settings.font)
this.rendition.spread(settings.spread || 'auto')
this.rendition.flow(settings.flow || 'paginated')
this.resize()
},
isScrolled() {
return this.ereaderSettings.flow === 'scrolled-continuous'
},
prev() {
if (!this.rendition?.manager) return
@ -312,6 +313,31 @@ export default {
})
}
},
/**
* Adds a bottom spacer div to the last page's iframe content to allow extra scroll space.
* @param {object} view - The view object containing the iframe.
* @param {object} section - The current section object with an index.
*/
addBottomSpacerToLastPage(view, section) {
const isLastPage = section.index === this.book?.spine?.spineItems?.length - 1
const doc = view?.iframe?.contentDocument || view?.iframe?.contentWindow?.document
if (!isLastPage || !doc) return
const existingSpacer = doc.getElementById('bottom-spacer')
if (!this.isScrolled()) {
existingSpacer?.remove()
return
}
if (existingSpacer) return
const newSpacer = doc.createElement('div')
newSpacer.id = 'bottom-spacer'
newSpacer.style.height = Math.floor(window.innerHeight / 2) + 'px'
doc.body.appendChild(newSpacer)
},
initEpub() {
/** @type {EpubReader} */
const reader = this
@ -338,20 +364,17 @@ export default {
/** @type {ePub.Rendition} */
reader.rendition = reader.book.renderTo('viewer', {
width: this.readerWidth,
height: this.readerHeight * 0.8,
allowScriptedContent: this.allowScriptedContent,
spread: 'auto',
snap: true,
manager: 'continuous',
flow: 'paginated'
manager: 'continuous'
})
// load saved progress
reader.rendition.display(this.savedEbookLocation || reader.book.locations.start)
reader.rendition.on('rendered', () => {
reader.rendition.on('rendered', (section, view) => {
this.applyTheme()
this.addBottomSpacerToLastPage(view, section)
})
reader.book.ready
@ -453,7 +476,8 @@ export default {
resize() {
this.windowWidth = window.innerWidth
this.windowHeight = window.innerHeight
this.rendition?.resize(this.readerWidth, this.readerHeight * 0.8)
this.rendition.resize(this.readerWidth, this.readerHeight * (this.isScrolled() ? 0.9 : 0.8))
},
applyTheme() {
if (!this.rendition) return
@ -474,3 +498,10 @@ export default {
}
}
</script>
<style>
.epub-container {
/* Fixes unexpected scroll jump issue with epubjs continuous scroll: see https://github.com/futurepress/epub.js/issues/1303 */
overflow-anchor: none !important;
scrollbar-width: none;
}
</style>

View file

@ -109,11 +109,23 @@
</div>
<ui-range-input v-model="ereaderSettings.textStroke" :min="0" :max="300" :step="5" @input="settingsUpdated" />
</div>
<div class="flex items-center mb-4">
<div class="w-40">
<p class="text-lg">{{ $strings.LabelPageWidth }}:</p>
</div>
<ui-range-input v-model="ereaderSettings.widthPercentage" :min="10" :max="100" :step="5" @input="settingsUpdated" />
</div>
<div class="flex items-center mb-4">
<div class="w-40">
<p class="text-lg">{{ $strings.LabelPageFlow }}:</p>
</div>
<ui-toggle-btns v-model="ereaderSettings.flow" :items="flowItems" @input="settingsUpdated" />
</div>
<div class="flex items-center">
<div class="w-40">
<p class="text-lg">{{ $strings.LabelLayout }}:</p>
</div>
<ui-toggle-btns v-model="ereaderSettings.spread" :items="spreadItems" @input="settingsUpdated" />
<ui-toggle-btns :disabled="ereaderSettings.flow === 'scrolled-continuous'" v-model="ereaderSettings.spread" :items="spreadItems" @input="settingsUpdated" />
</div>
</div>
</modals-modal>
@ -143,7 +155,9 @@ export default {
lineSpacing: 115,
fontBoldness: 100,
spread: 'auto',
textStroke: 0
textStroke: 0,
flow: 'paginated',
widthPercentage: 100
}
}
},
@ -179,6 +193,18 @@ export default {
}
]
},
flowItems() {
return [
{
text: this.$strings.LabelFlowPaginated,
value: 'paginated'
},
{
text: this.$strings.LabelFlowScrolled,
value: 'scrolled-continuous'
}
]
},
themeItems() {
return {
theme: [

View file

@ -378,6 +378,8 @@
"LabelFilterByUser": "Filter by User",
"LabelFindEpisodes": "Find Episodes",
"LabelFinished": "Finished",
"LabelFlowPaginated": "Paginated",
"LabelFlowScrolled": "Scrolled",
"LabelFolder": "Folder",
"LabelFolders": "Folders",
"LabelFontBold": "Bold",
@ -499,6 +501,8 @@
"LabelOpenIDGroupClaimDescription": "Name of the OpenID claim that contains a list of the user's groups. Commonly referred to as <code>groups</code>. <b>If configured</b>, the application will automatically assign roles based on the user's group memberships, provided that these groups are named case-insensitively 'admin', 'user', or 'guest' in the claim. The claim should contain a list, and if a user belongs to multiple groups, the application will assign the role corresponding to the highest level of access. If no group matches, access will be denied.",
"LabelOpenRSSFeed": "Open RSS Feed",
"LabelOverwrite": "Overwrite",
"LabelPageFlow": "Flow",
"LabelPageWidth": "Page Width",
"LabelPaginationPageXOfY": "Page {0} of {1}",
"LabelPassword": "Password",
"LabelPath": "Path",