mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
fix(reader): report frame index in fixed-layout contents
Ensure that fixed-layout EPUB content structures expose their frame index and overlayer attributes during retrieval. This prevents the navigation subsystem from falling back to the final page of the document due to unverified page position indexes. Closes #541
This commit is contained in:
@@ -85,7 +85,7 @@ export class FixedLayout extends HTMLElement {
|
||||
iframe.setAttribute('scrolling', 'no')
|
||||
iframe.setAttribute('part', 'filter')
|
||||
this.#root.append(element)
|
||||
if (!src) return { blank: true, element, iframe }
|
||||
if (!src) return { index, blank: true, element, iframe }
|
||||
return new Promise((resolve) => {
|
||||
iframe.addEventListener(
|
||||
'load',
|
||||
@@ -93,13 +93,24 @@ export class FixedLayout extends HTMLElement {
|
||||
const doc = iframe.contentDocument
|
||||
this.dispatchEvent(new CustomEvent('load', { detail: { doc, index } }))
|
||||
const { width, height } = getViewport(doc, this.defaultViewport)
|
||||
resolve({
|
||||
const frame = {
|
||||
index,
|
||||
element,
|
||||
iframe,
|
||||
width: parseFloat(width),
|
||||
height: parseFloat(height),
|
||||
onZoom,
|
||||
})
|
||||
}
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('create-overlayer', {
|
||||
detail: {
|
||||
doc,
|
||||
index,
|
||||
attach: (overlayer) => (frame.overlayer = overlayer),
|
||||
},
|
||||
}),
|
||||
)
|
||||
resolve(frame)
|
||||
},
|
||||
{ once: true },
|
||||
)
|
||||
@@ -301,10 +312,14 @@ export class FixedLayout extends HTMLElement {
|
||||
if (!s) return this.goToSpread(this.#index - 1, this.rtl ? 'left' : 'right', 'page')
|
||||
}
|
||||
getContents() {
|
||||
return Array.from(this.#root.querySelectorAll('iframe'), (frame) => ({
|
||||
doc: frame.contentDocument,
|
||||
// TODO: index, overlayer
|
||||
}))
|
||||
const frames = this.#center ? [this.#center] : [this.#left, this.#right]
|
||||
return frames
|
||||
.filter((frame) => frame && !frame.blank && typeof frame.index === 'number' && frame.index >= 0 && frame.iframe?.contentDocument)
|
||||
.map((frame) => ({
|
||||
index: frame.index,
|
||||
overlayer: frame.overlayer,
|
||||
doc: frame.iframe.contentDocument,
|
||||
}))
|
||||
}
|
||||
destroy() {
|
||||
this.#observer.unobserve(this)
|
||||
|
||||
@@ -45,6 +45,11 @@ describe('Foliate navigation', () => {
|
||||
let getKoreaderDocFragmentIndex: (sections: { id: string }[], index: number) => number | null
|
||||
let getKoboSpanValue: (range: Range | null) => string | null
|
||||
let getPageProgressionRtl: (bookDir: string | null | undefined, contentRtl: boolean) => boolean
|
||||
let FixedLayout: new () => HTMLElement & {
|
||||
open: (book: { rendition: { layout: string; spread: string }; sections: { load: () => Promise<string> }[] }) => void
|
||||
goTo: (target: { index: number }) => Promise<void>
|
||||
getContents: () => { index?: number; doc?: Document }[]
|
||||
}
|
||||
let Paginator: new () => {
|
||||
sections: { load: () => Promise<string | null> }[]
|
||||
goTo: (target: { index: number }) => Promise<void>
|
||||
@@ -54,6 +59,7 @@ describe('Foliate navigation', () => {
|
||||
installBrowserGlobals()
|
||||
|
||||
const viewModulePath = '../../../../../public/assets/foliate/view.js'
|
||||
const fixedLayoutModulePath = '../../../../../public/assets/foliate/fixed-layout.js'
|
||||
const paginatorModulePath = '../../../../../public/assets/foliate/paginator.js'
|
||||
;({ View, getKoreaderProgress, getKoreaderDocFragmentIndex, getKoboSpanValue } = (await import(viewModulePath)) as {
|
||||
View: typeof View
|
||||
@@ -61,6 +67,7 @@ describe('Foliate navigation', () => {
|
||||
getKoreaderDocFragmentIndex: typeof getKoreaderDocFragmentIndex
|
||||
getKoboSpanValue: typeof getKoboSpanValue
|
||||
})
|
||||
;({ FixedLayout } = (await import(fixedLayoutModulePath)) as { FixedLayout: typeof FixedLayout })
|
||||
;({ Paginator, getPageProgressionRtl } = (await import(paginatorModulePath)) as {
|
||||
Paginator: typeof Paginator
|
||||
getPageProgressionRtl: typeof getPageProgressionRtl
|
||||
@@ -117,6 +124,52 @@ describe('Foliate navigation', () => {
|
||||
await expect(paginator.goTo({ index: 0 })).rejects.toThrow('Failed to load section 0')
|
||||
})
|
||||
|
||||
it('reports fixed-layout frame indexes for navigation success checks', async () => {
|
||||
const originalCreateElement = document.createElement.bind(document)
|
||||
const createElementSpy = vi.spyOn(document, 'createElement').mockImplementation((tagName: string) => {
|
||||
const element = originalCreateElement(tagName)
|
||||
if (tagName === 'iframe') {
|
||||
const frameDoc = document.implementation.createHTMLDocument('fixed-layout-frame')
|
||||
frameDoc.head.innerHTML = '<meta name="viewport" content="width=100,height=200">'
|
||||
Object.defineProperty(element, 'contentDocument', { configurable: true, value: frameDoc })
|
||||
Object.defineProperty(element, 'src', {
|
||||
configurable: true,
|
||||
get() {
|
||||
return this.getAttribute('src') ?? ''
|
||||
},
|
||||
set(value) {
|
||||
this.setAttribute('src', value)
|
||||
queueMicrotask(() => this.dispatchEvent(new window.Event('load')))
|
||||
},
|
||||
})
|
||||
}
|
||||
return element
|
||||
})
|
||||
|
||||
const fixedLayout = new FixedLayout()
|
||||
document.body.append(fixedLayout)
|
||||
|
||||
try {
|
||||
fixedLayout.open({
|
||||
rendition: { layout: 'pre-paginated', spread: 'none' },
|
||||
sections: [
|
||||
{ load: vi.fn<() => Promise<string>>().mockResolvedValue('section-0.xhtml') },
|
||||
{ load: vi.fn<() => Promise<string>>().mockResolvedValue('section-1.xhtml') },
|
||||
],
|
||||
})
|
||||
|
||||
await fixedLayout.goTo({ index: 0 })
|
||||
|
||||
const contents = fixedLayout.getContents()
|
||||
expect(contents).toHaveLength(1)
|
||||
expect(contents[0]).toMatchObject({ index: 0 })
|
||||
expect(contents[0]?.doc?.documentElement).toBeTruthy()
|
||||
} finally {
|
||||
fixedLayout.remove()
|
||||
createElementSpy.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
it('uses explicit OPF page progression before content direction', () => {
|
||||
expect(getPageProgressionRtl('rtl', false)).toBe(true)
|
||||
expect(getPageProgressionRtl('ltr', true)).toBe(false)
|
||||
|
||||
Reference in New Issue
Block a user