mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-15 13:51:35 +00:00
Added location slider and bookmarks to Epub reader
keep book and audiobook trackbars from overlapping
This commit is contained in:
parent
432e25565e
commit
f6de6a93ab
4 changed files with 106 additions and 58 deletions
|
|
@ -39,7 +39,8 @@ export default {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {}
|
default: () => {}
|
||||||
},
|
},
|
||||||
playbackRate: Number
|
playbackRate: Number,
|
||||||
|
usePercent: Boolean
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -172,7 +173,7 @@ export default {
|
||||||
this.$refs.hoverTimestampArrow.style.left = posLeft + 'px'
|
this.$refs.hoverTimestampArrow.style.left = posLeft + 'px'
|
||||||
}
|
}
|
||||||
if (this.$refs.hoverTimestampText) {
|
if (this.$refs.hoverTimestampText) {
|
||||||
var hoverText = this.$secondsToTimestamp(progressTime / this._playbackRate)
|
var hoverText = this.usePercent ? ((progressTime / duration) * 100).toFixed(1) + '%' : this.$secondsToTimestamp(progressTime / this._playbackRate)
|
||||||
|
|
||||||
var chapter = this.chapters.find((chapter) => chapter.start <= totalTime && totalTime < chapter.end)
|
var chapter = this.chapters.find((chapter) => chapter.start <= totalTime && totalTime < chapter.end)
|
||||||
if (chapter && chapter.title) {
|
if (chapter && chapter.title) {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full -mt-6">
|
<div class="w-full -mt-6">
|
||||||
<div class="w-full relative mb-1">
|
<div class="w-full relative mb-1">
|
||||||
<div class="absolute -top-10 md:top-0 right-0 lg:right-2 flex items-center h-full">
|
<div class="absolute right-0 lg:right-2 flex items-center h-full" :class="isBook ? '-top-4' : '-top-10 md:top-0'">
|
||||||
<!-- <span class="material-icons text-2xl cursor-pointer" @click="toggleFullscreen(true)">expand_less</span> -->
|
<!-- <span class="material-icons text-2xl cursor-pointer" @click="toggleFullscreen(true)">expand_less</span> -->
|
||||||
|
|
||||||
<ui-tooltip direction="top" :text="$strings.LabelVolume">
|
<ui-tooltip v-if="!isBook" direction="top" :text="$strings.LabelVolume">
|
||||||
<controls-volume-control ref="volumeControl" v-model="volume" @input="setVolume" class="mx-2 hidden md:block" />
|
<controls-volume-control ref="volumeControl" v-model="volume" @input="setVolume" class="mx-2 hidden md:block" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<ui-tooltip direction="top" :text="$strings.LabelSleepTimer">
|
<ui-tooltip v-if="!isBook" direction="top" :text="$strings.LabelSleepTimer">
|
||||||
<div class="cursor-pointer text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showSleepTimer')">
|
<div class="cursor-pointer text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showSleepTimer')">
|
||||||
<span v-if="!sleepTimerSet" class="material-icons text-2xl">snooze</span>
|
<span v-if="!sleepTimerSet" class="material-icons text-2xl">snooze</span>
|
||||||
<div v-else class="flex items-center">
|
<div v-else class="flex items-center">
|
||||||
|
|
@ -18,13 +18,13 @@
|
||||||
</div>
|
</div>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<ui-tooltip v-if="!isPodcast" direction="top" :text="$strings.LabelViewBookmarks">
|
<ui-tooltip v-if="!isPodcast && !isBook" direction="top" :text="$strings.LabelViewBookmarks">
|
||||||
<div class="cursor-pointer text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showBookmarks')">
|
<div class="cursor-pointer text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showBookmarks')">
|
||||||
<span class="material-icons text-2xl">{{ bookmarks.length ? 'bookmarks' : 'bookmark_border' }}</span>
|
<span class="material-icons text-2xl">{{ bookmarks.length ? 'bookmarks' : 'bookmark_border' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<ui-tooltip v-if="chapters.length" direction="top" :text="$strings.LabelViewChapters">
|
<ui-tooltip v-if="chapters.length && !isBook" direction="top" :text="$strings.LabelViewChapters">
|
||||||
<div class="cursor-pointer text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="showChapters">
|
<div class="cursor-pointer text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="showChapters">
|
||||||
<span class="material-icons text-2xl">format_list_bulleted</span>
|
<span class="material-icons text-2xl">format_list_bulleted</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -42,21 +42,32 @@
|
||||||
</div>
|
</div>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
<player-playback-controls v-if="!isBook" :loading="loading" :seek-loading="seekLoading" :playback-rate.sync="playbackRate" :paused="paused" :has-next-chapter="hasNextChapter" @prevChapter="prevChapter" @nextChapter="nextChapter" @jumpForward="jumpForward" @jumpBackward="jumpBackward" @setPlaybackRate="setPlaybackRate" @playPause="playPause" />
|
||||||
<player-playback-controls :loading="loading" :seek-loading="seekLoading" :playback-rate.sync="playbackRate" :paused="paused" :has-next-chapter="hasNextChapter" @prevChapter="prevChapter" @nextChapter="nextChapter" @jumpForward="jumpForward" @jumpBackward="jumpBackward" @setPlaybackRate="setPlaybackRate" @playPause="playPause" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<player-track-bar ref="trackbar" :loading="loading" :chapters="chapters" :duration="duration" :current-chapter="currentChapter" :playback-rate="playbackRate" @seek="seek" />
|
<player-track-bar ref="trackbar" :usePercent="isBook" :loading="loading" :chapters="chapters" :duration="duration" :current-chapter="currentChapter" :playback-rate="playbackRate" @seek="seek" />
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<p ref="currentTimestamp" class="font-mono text-xxs sm:text-sm text-gray-100 pointer-events-auto">00:00:00</p>
|
<div class="min-w-40 flex font-mono text-gray-100 pointer-events-auto">
|
||||||
<p class="font-mono text-sm hidden sm:block text-gray-100 pointer-events-auto"> / {{ progressPercent }}%</p>
|
<p ref="currentTimestamp" class="text-xxs sm:text-sm">00:00:00</p>
|
||||||
<div class="flex-grow" />
|
<p class="text-sm hidden sm:block"> / {{ progressPercent }}%</p>
|
||||||
<p class="text-xs sm:text-sm text-gray-300 pt-0.5">
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-grow flex justify-center text-gray-300">
|
||||||
|
<div v-if="isBook" class="cursor-pointer" @mousedown.prevent @mouseup.prevent @click.stop="prevChapter">
|
||||||
|
<span class="material-icons text-2xl sm:text-3xl">first_page</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="min-w-40 text-center text-xs sm:text-sm pt-0.5">
|
||||||
{{ currentChapterName }} <span v-if="useChapterTrack" class="text-xs text-gray-400"> ({{ currentChapterIndex + 1 }} of {{ chapters.length }})</span>
|
{{ currentChapterName }} <span v-if="useChapterTrack" class="text-xs text-gray-400"> ({{ currentChapterIndex + 1 }} of {{ chapters.length }})</span>
|
||||||
</p>
|
</p>
|
||||||
<div class="flex-grow" />
|
|
||||||
<p class="font-mono text-xxs sm:text-sm text-gray-100 pointer-events-auto">{{ timeRemainingPretty }}</p>
|
<div v-if="isBook" :class="hasNextChapter ? 'cursor-pointer' : 'text-gray-500'" @mousedown.prevent @mouseup.prevent @click.stop="nextChapter">
|
||||||
|
<span class="material-icons text-2xl sm:text-3xl">last_page</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="min-w-40 text-right font-mono text-xxs sm:text-sm text-gray-100 pointer-events-auto">{{ timeRemainingPretty }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<modals-chapters-modal v-model="showChaptersModal" :current-chapter="currentChapter" :playback-rate="playbackRate" :chapters="chapters" @select="selectChapter" />
|
<modals-chapters-modal v-model="showChaptersModal" :current-chapter="currentChapter" :playback-rate="playbackRate" :chapters="chapters" @select="selectChapter" />
|
||||||
|
|
@ -78,7 +89,8 @@ export default {
|
||||||
},
|
},
|
||||||
sleepTimerSet: Boolean,
|
sleepTimerSet: Boolean,
|
||||||
sleepTimerRemaining: Number,
|
sleepTimerRemaining: Number,
|
||||||
isPodcast: Boolean
|
isPodcast: Boolean,
|
||||||
|
isBook: Boolean
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -121,10 +133,8 @@ export default {
|
||||||
return (this.duration - this.currentTime) / this.playbackRate
|
return (this.duration - this.currentTime) / this.playbackRate
|
||||||
},
|
},
|
||||||
timeRemainingPretty() {
|
timeRemainingPretty() {
|
||||||
if (this.timeRemaining < 0) {
|
if (this.isBook) return -Math.ceil(Math.abs(this.timeRemaining))
|
||||||
return this.$secondsToTimestamp(this.timeRemaining * -1)
|
return this.$secondsToTimestamp(-Math.abs(this.timeRemaining))
|
||||||
}
|
|
||||||
return '-' + this.$secondsToTimestamp(this.timeRemaining)
|
|
||||||
},
|
},
|
||||||
progressPercent() {
|
progressPercent() {
|
||||||
const duration = this.useChapterTrack ? this.currentChapterDuration : this.duration
|
const duration = this.useChapterTrack ? this.currentChapterDuration : this.duration
|
||||||
|
|
@ -134,7 +144,7 @@ export default {
|
||||||
return Math.round((100 * time) / duration)
|
return Math.round((100 * time) / duration)
|
||||||
},
|
},
|
||||||
currentChapter() {
|
currentChapter() {
|
||||||
return this.chapters.find((chapter) => chapter.start <= this.currentTime && this.currentTime < chapter.end)
|
return this.chapters.find((chapter) => chapter.start <= this.currentTime && (this.currentTime < chapter.end || this.currentTime == 1))
|
||||||
},
|
},
|
||||||
currentChapterName() {
|
currentChapterName() {
|
||||||
return this.currentChapter ? this.currentChapter.title : ''
|
return this.currentChapter ? this.currentChapter.title : ''
|
||||||
|
|
@ -296,7 +306,7 @@ export default {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const time = this.useChapterTrack ? Math.max(0, this.currentTime - this.currentChapterStart) : this.currentTime
|
const time = this.useChapterTrack ? Math.max(0, this.currentTime - this.currentChapterStart) : this.currentTime
|
||||||
ts.innerText = this.$secondsToTimestamp(time / this.playbackRate)
|
ts.innerText = this.isBook ? 'Page ' + Math.round(time / this.playbackRate) : this.$secondsToTimestamp(time / this.playbackRate)
|
||||||
},
|
},
|
||||||
setBufferTime(bufferTime) {
|
setBufferTime(bufferTime) {
|
||||||
if (this.$refs.trackbar) this.$refs.trackbar.setBufferTime(bufferTime)
|
if (this.$refs.trackbar) this.$refs.trackbar.setBufferTime(bufferTime)
|
||||||
|
|
@ -315,7 +325,7 @@ export default {
|
||||||
this.setPlaybackRate(this.playbackRate)
|
this.setPlaybackRate(this.playbackRate)
|
||||||
},
|
},
|
||||||
settingsUpdated(settings) {
|
settingsUpdated(settings) {
|
||||||
if (settings.playbackRate && this.playbackRate !== settings.playbackRate) {
|
if (!this.isBook && settings.playbackRate && this.playbackRate !== settings.playbackRate) {
|
||||||
this.setPlaybackRate(settings.playbackRate)
|
this.setPlaybackRate(settings.playbackRate)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="epub-reader" class="h-full w-full">
|
<div id="epub-reader" class="h-full w-full flex flex-col">
|
||||||
<div class="h-full flex items-center justify-center">
|
<div class="flex flex-grow 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 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-icons text-6xl" @mousedown.prevent @click="prev">chevron_left</span>
|
<span v-if="hasPrev" class="material-icons text-6xl" @mousedown.prevent @click="prev">chevron_left</span>
|
||||||
</button>
|
</button>
|
||||||
<div id="frame" class="w-full" style="height: 80%">
|
<div id="frame" class="w-full shrink">
|
||||||
<div id="viewer"></div>
|
<div id="viewer"></div>
|
||||||
</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 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-icons text-6xl" @mousedown.prevent @click="next">chevron_right</span>
|
<span v-if="hasNext" class="material-icons text-6xl" @mousedown.prevent @click="next">chevron_right</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="group-data-[theme=light]:invert z-50 px-2 md:px-4 pb-1 md:pb-4 pt-2" :class="themeRules">
|
||||||
|
<player-ui ref="audioPlayer" :chapters="flattenedChapters" isBook @seek="seek" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -41,6 +44,7 @@ export default {
|
||||||
/** @type {ePub.Rendition} */
|
/** @type {ePub.Rendition} */
|
||||||
rendition: null,
|
rendition: null,
|
||||||
chapters: [],
|
chapters: [],
|
||||||
|
flattenedChapters: [],
|
||||||
ereaderSettings: {
|
ereaderSettings: {
|
||||||
theme: 'dark',
|
theme: 'dark',
|
||||||
font: 'serif',
|
font: 'serif',
|
||||||
|
|
@ -139,6 +143,11 @@ export default {
|
||||||
if (!this.rendition?.manager) return
|
if (!this.rendition?.manager) return
|
||||||
return this.rendition?.next()
|
return this.rendition?.next()
|
||||||
},
|
},
|
||||||
|
seek(location) {
|
||||||
|
if (!this.rendition?.manager) return
|
||||||
|
console.log(this.book.locations.cfiFromLocation(Math.round(location)))
|
||||||
|
return this.rendition?.display(this.book.locations.cfiFromPercentage(location))
|
||||||
|
},
|
||||||
goToChapter(href) {
|
goToChapter(href) {
|
||||||
if (!this.rendition?.manager) return
|
if (!this.rendition?.manager) return
|
||||||
return this.rendition?.display(href)
|
return this.rendition?.display(href)
|
||||||
|
|
@ -288,6 +297,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location.end.percentage) {
|
if (location.end.percentage) {
|
||||||
|
this.$refs.audioPlayer.setCurrentTime(location.end.percentage)
|
||||||
this.updateProgress({
|
this.updateProgress({
|
||||||
ebookLocation: location.start.cfi,
|
ebookLocation: location.start.cfi,
|
||||||
ebookProgress: location.end.percentage
|
ebookProgress: location.end.percentage
|
||||||
|
|
@ -298,7 +308,28 @@ export default {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
initEpub() {
|
estimatePages() {
|
||||||
|
// This uses the existing audio player ui by changes the 'playback rate' to scale percentage to pages.
|
||||||
|
|
||||||
|
// Estimating page count isn't straightforward for Epubs.
|
||||||
|
// https://github.com/futurepress/epub.js/issues/744
|
||||||
|
// https://github.com/johnfactotum/foliate/issues/175
|
||||||
|
|
||||||
|
// Method 1: count pages in the current chapter and extrapolate the total number of pages from that.
|
||||||
|
// Downsides: total page count changes when chapters change, only accurate for long chapters.
|
||||||
|
// let currentLocation = this.rendition.currentLocation()
|
||||||
|
// let sectionIndex = currentLocation.start.index
|
||||||
|
// let sectionPages = currentLocation.start.displayed.total
|
||||||
|
// let sectionBaseCFI = this.book.spine.get(sectionIndex).cfiBase
|
||||||
|
// let sectionLocations = this.book.locations._locations.filter((item) => item.startsWith('epubcfi(' + sectionBaseCFI)).length
|
||||||
|
// let totalLocations = this.book.locations.total
|
||||||
|
// let estPages = (totalLocations * sectionPages) / sectionLocations
|
||||||
|
// this.$refs.audioPlayer.playbackRate = 1 / estPages
|
||||||
|
|
||||||
|
// Method 2: use a location size of 1024 characters to approximates pages. Isn't actually a page, more but stable.
|
||||||
|
this.$refs.audioPlayer.playbackRate = 1 / this.book.locations.total
|
||||||
|
},
|
||||||
|
async initEpub() {
|
||||||
/** @type {EpubReader} */
|
/** @type {EpubReader} */
|
||||||
const reader = this
|
const reader = this
|
||||||
|
|
||||||
|
|
@ -330,11 +361,11 @@ export default {
|
||||||
this.applyTheme()
|
this.applyTheme()
|
||||||
})
|
})
|
||||||
|
|
||||||
reader.book.ready.then(() => {
|
await reader.book.ready
|
||||||
// set up event listeners
|
// set up event listeners
|
||||||
reader.rendition.on('relocated', reader.relocated)
|
reader.rendition.on('relocated', reader.relocated)
|
||||||
reader.rendition.on('keydown', reader.keyUp)
|
reader.rendition.on('keydown', reader.keyUp)
|
||||||
|
// reader.rendition.on('rendered', this.estimatePages)
|
||||||
reader.rendition.on('touchstart', (event) => {
|
reader.rendition.on('touchstart', (event) => {
|
||||||
this.$emit('touchstart', event)
|
this.$emit('touchstart', event)
|
||||||
})
|
})
|
||||||
|
|
@ -347,12 +378,16 @@ export default {
|
||||||
if (savedLocations) {
|
if (savedLocations) {
|
||||||
reader.book.locations.load(savedLocations)
|
reader.book.locations.load(savedLocations)
|
||||||
} else {
|
} else {
|
||||||
reader.book.locations.generate().then(() => {
|
await reader.book.locations.generate(1024)
|
||||||
this.checkSaveLocations(reader.book.locations.save())
|
this.checkSaveLocations(reader.book.locations.save())
|
||||||
})
|
|
||||||
}
|
}
|
||||||
this.getChapters()
|
|
||||||
})
|
await this.getChapters()
|
||||||
|
this.$refs.audioPlayer.setDuration(this.book.locations.total)
|
||||||
|
this.$refs.audioPlayer.setDuration(1)
|
||||||
|
|
||||||
|
this.estimatePages()
|
||||||
|
this.$refs.audioPlayer.setCurrentTime(this.book.locations.percentageFromCfi(this.savedEbookLocation || reader.book.locations.start))
|
||||||
},
|
},
|
||||||
getChapters() {
|
getChapters() {
|
||||||
// Load the list of chapters in the book. See https://github.com/futurepress/epub.js/issues/759
|
// Load the list of chapters in the book. See https://github.com/futurepress/epub.js/issues/759
|
||||||
|
|
@ -400,16 +435,17 @@ export default {
|
||||||
}
|
}
|
||||||
return createTree(toc, tocTree).then(() => {
|
return createTree(toc, tocTree).then(() => {
|
||||||
this.chapters = tocTree
|
this.chapters = tocTree
|
||||||
|
this.flattenChapters()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
flattenChapters(chapters) {
|
flattenChapters() {
|
||||||
// Convert the nested epub chapters into something that looks like audiobook chapters for player-ui
|
// Convert the nested epub chapters into something that looks like audiobook chapters for player-ui
|
||||||
const unwrap = (chapters) => {
|
const unwrap = (chapters) => {
|
||||||
return chapters.reduce((acc, chapter) => {
|
return chapters.reduce((acc, chapter) => {
|
||||||
return chapter.subitems ? [...acc, chapter, ...unwrap(chapter.subitems)] : [...acc, chapter]
|
return chapter.subitems ? [...acc, chapter, ...unwrap(chapter.subitems)] : [...acc, chapter]
|
||||||
}, [])
|
}, [])
|
||||||
}
|
}
|
||||||
let flattenedChapters = unwrap(chapters)
|
let flattenedChapters = unwrap(this.chapters)
|
||||||
|
|
||||||
flattenedChapters = flattenedChapters.sort((a, b) => a.start - b.start)
|
flattenedChapters = flattenedChapters.sort((a, b) => a.start - b.start)
|
||||||
for (let i = 0; i < flattenedChapters.length; i++) {
|
for (let i = 0; i < flattenedChapters.length; i++) {
|
||||||
|
|
@ -417,10 +453,11 @@ export default {
|
||||||
if (i < flattenedChapters.length - 1) {
|
if (i < flattenedChapters.length - 1) {
|
||||||
flattenedChapters[i].end = flattenedChapters[i + 1].start
|
flattenedChapters[i].end = flattenedChapters[i + 1].start
|
||||||
} else {
|
} else {
|
||||||
|
// flattenedChapters[i].end = this.book.locations.total
|
||||||
flattenedChapters[i].end = 1
|
flattenedChapters[i].end = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return flattenedChapters
|
this.flattenedChapters = flattenedChapters
|
||||||
},
|
},
|
||||||
resize() {
|
resize() {
|
||||||
this.windowWidth = window.innerWidth
|
this.windowWidth = window.innerWidth
|
||||||
|
|
@ -434,11 +471,11 @@ export default {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
async mounted() {
|
||||||
this.windowWidth = window.innerWidth
|
this.windowWidth = window.innerWidth
|
||||||
this.windowHeight = window.innerHeight
|
this.windowHeight = window.innerHeight
|
||||||
window.addEventListener('resize', this.resize)
|
window.addEventListener('resize', this.resize)
|
||||||
this.initEpub()
|
await this.initEpub()
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
window.removeEventListener('resize', this.resize)
|
window.removeEventListener('resize', this.resize)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
<!-- TOC side nav -->
|
<!-- TOC side nav -->
|
||||||
<div v-if="tocOpen" class="w-full h-full overflow-y-scroll absolute inset-0 bg-black/20 z-20" @click.stop.prevent="toggleToC"></div>
|
<div v-if="tocOpen" class="w-full h-full overflow-y-scroll absolute inset-0 bg-black/20 z-20" @click.stop.prevent="toggleToC"></div>
|
||||||
<div v-if="isEpub" class="w-96 h-full max-h-full absolute top-0 left-0 shadow-xl transition-transform z-30 group-data-[theme=dark]:bg-primary group-data-[theme=dark]:text-white group-data-[theme=light]:bg-white group-data-[theme=light]:text-black" :class="tocOpen ? 'translate-x-0' : '-translate-x-96'" @click.stop.prevent>
|
<div v-if="isEpub" class="w-96 h-full max-h-full absolute top-0 left-0 shadow-xl transition-transform z-50 group-data-[theme=dark]:bg-primary group-data-[theme=dark]:text-white group-data-[theme=light]:bg-white group-data-[theme=light]:text-black" :class="tocOpen ? 'translate-x-0' : '-translate-x-96'" @click.stop.prevent>
|
||||||
<div class="flex flex-col p-4 h-full">
|
<div class="flex flex-col p-4 h-full">
|
||||||
<div class="flex items-center mb-2">
|
<div class="flex items-center mb-2">
|
||||||
<button @click.stop.prevent="toggleToC" type="button" aria-label="Close table of contents" class="inline-flex opacity-80 hover:opacity-100">
|
<button @click.stop.prevent="toggleToC" type="button" aria-label="Close table of contents" class="inline-flex opacity-80 hover:opacity-100">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue