Footnotes: reject when failing to show footnote

Now `handle()` could return either undefined or a promise, which you can
catch to handle the case where footnote fails to show.

Also improve footnote extraction.
This commit is contained in:
John Factotum
2024-03-26 16:41:30 +08:00
parent b45e08b10b
commit 04b8789267
+36 -22
View File
@@ -32,11 +32,17 @@ const getReferencedType = el => {
const isInline = 'a, span, sup, sub, em, strong, i, b, small, big' const isInline = 'a, span, sup, sub, em, strong, i, b, small, big'
const extractFootnote = (doc, anchor) => { const extractFootnote = (doc, anchor) => {
let el = anchor(doc) let el = anchor(doc)
const target = el
while (el.matches(isInline)) { while (el.matches(isInline)) {
const parent = el.parentElement const parent = el.parentElement
if (!parent) break if (!parent) break
el = parent el = parent
} }
if (el === doc.body) {
const sibling = target.nextElementSibling
if (sibling && !sibling.matches(isInline)) return sibling
throw new Error('Failed to extract footnote')
}
return el return el
} }
@@ -44,41 +50,49 @@ export class FootnoteHandler extends EventTarget {
detectFootnotes = true detectFootnotes = true
#showFragment(book, { index, anchor }, href) { #showFragment(book, { index, anchor }, href) {
const view = document.createElement('foliate-view') const view = document.createElement('foliate-view')
view.addEventListener('load', e => { return new Promise((resolve, reject) => {
const { doc } = e.detail view.addEventListener('load', e => {
const el = anchor(doc) try {
const type = getReferencedType(el) const { doc } = e.detail
const hidden = el?.matches?.('aside') && type === 'footnote' const el = anchor(doc)
if (el) { const type = getReferencedType(el)
const range = el.startContainer ? el : doc.createRange() const hidden = el?.matches?.('aside') && type === 'footnote'
if (!el.startContainer) { if (el) {
if (el.matches('li, aside')) range.selectNodeContents(el) const range = el.startContainer ? el : doc.createRange()
else range.selectNode(el) if (!el.startContainer) {
if (el.matches('li, aside')) range.selectNodeContents(el)
else range.selectNode(el)
}
const frag = range.extractContents()
doc.body.replaceChildren()
doc.body.appendChild(frag)
}
const detail = { view, href, type, hidden, target: el }
this.dispatchEvent(new CustomEvent('render', { detail }))
resolve()
} catch (e) {
reject(e)
} }
const frag = range.extractContents() })
doc.body.replaceChildren() view.open(book)
doc.body.appendChild(frag) .then(() => this.dispatchEvent(new CustomEvent('before-render', { detail: { view } })))
} .then(() => view.goTo(index))
const detail = { view, href, type, hidden, target: el } .catch(reject)
this.dispatchEvent(new CustomEvent('render', { detail }))
}) })
view.open(book)
.then(() => this.dispatchEvent(new CustomEvent('before-render', { detail: { view } })))
.then(() => view.goTo(index))
} }
handle(book, e) { handle(book, e) {
const { a, href } = e.detail const { a, href } = e.detail
const { yes, maybe } = isFootnoteReference(a) const { yes, maybe } = isFootnoteReference(a)
if (yes) { if (yes) {
e.preventDefault() e.preventDefault()
Promise.resolve(book.resolveHref(href)).then(target => return Promise.resolve(book.resolveHref(href)).then(target =>
this.#showFragment(book, target, href)) this.#showFragment(book, target, href))
} }
else if (this.detectFootnotes && maybe()) { else if (this.detectFootnotes && maybe()) {
e.preventDefault() e.preventDefault()
Promise.resolve(book.resolveHref(href)).then(({ index, anchor }) => { return Promise.resolve(book.resolveHref(href)).then(({ index, anchor }) => {
const target = { index, anchor: doc => extractFootnote(doc, anchor) } const target = { index, anchor: doc => extractFootnote(doc, anchor) }
this.#showFragment(book, target, href) return this.#showFragment(book, target, href)
}) })
} }
} }