diff --git a/reader.html b/reader.html
index 8820ea3..69e5634 100644
--- a/reader.html
+++ b/reader.html
@@ -237,6 +237,20 @@ body {
background-repeat: no-repeat;
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Ccircle%20cx%3D%2212%22%20cy%3D%2212%22%20r%3D%223%22%2F%3E%3C%2Fsvg%3E');
}
+.popover {
+ background: Canvas;
+ color: CanvasText;
+ border-radius: 6px;
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, .2), 0 0 16px rgba(0, 0, 0, .1), 0 0 32px rgba(0, 0, 0, .1);
+}
+.popover-arrow-down {
+ fill: Canvas;
+ filter: drop-shadow(0 -1px 0 rgba(0, 0, 0, .2));
+}
+.popover-arrow-up {
+ fill: Canvas;
+ filter: drop-shadow(0 1px 0 rgba(0, 0, 0, .2));
+}
diff --git a/reader.js b/reader.js
index 86ee8b2..59d07ee 100644
--- a/reader.js
+++ b/reader.js
@@ -2,6 +2,7 @@
import { View } from './view.js'
import { createTOCView } from './ui/tree.js'
import { createMenu } from './ui/menu.js'
+import { createPopover } from './ui/popover.js'
const { ZipReader, BlobReader, TextWriter, BlobWriter } = zip
zip.configure({ useWebWorkers: false })
@@ -119,6 +120,30 @@ const emit = obj => {
if (tocItem?.href) setCurrentHref?.(tocItem.href)
break
}
+ case 'reference': {
+ const { content, pos: { point, dir } } = obj
+ const iframe = document.createElement('iframe')
+ iframe.sandbox = 'allow-same-origin'
+ iframe.srcdoc = content
+ iframe.onload = () => {
+ const doc = iframe.contentDocument
+ doc.documentElement.style.colorScheme = 'light dark'
+ doc.body.style.margin = '18px'
+ }
+ Object.assign(iframe.style, {
+ border: '0',
+ width: '100%',
+ height: '100%',
+ })
+ const { popover, arrow, overlay } = createPopover(300, 250, point, dir)
+ overlay.style.zIndex = 3
+ popover.style.zIndex = 3
+ arrow.style.zIndex = 3
+ popover.append(iframe)
+ document.body.append(overlay)
+ document.body.append(popover)
+ document.body.append(arrow)
+ }
}
}
diff --git a/ui/popover.js b/ui/popover.js
new file mode 100644
index 0000000..5b99618
--- /dev/null
+++ b/ui/popover.js
@@ -0,0 +1,61 @@
+const clamp = (min, max, x) => Math.min(max, Math.max(min, x))
+
+const arrowHeight = 10
+const arrowWidth = 20
+const radius = 6
+
+const createSVGElement = tag =>
+ document.createElementNS('http://www.w3.org/2000/svg', tag)
+
+const createArrow = down => {
+ const h = arrowHeight + 1
+ const svg = createSVGElement('svg')
+ svg.setAttribute('width', arrowWidth)
+ svg.setAttribute('height', arrowHeight)
+ const polygon = createSVGElement('polygon')
+ polygon.setAttribute('points', down
+ ? `0 ${h}, ${arrowWidth / 2} 0, ${arrowWidth} ${h}`
+ : `0 0, ${arrowWidth / 2} ${h}, ${arrowWidth} 0`)
+ svg.classList.add(down ? 'popover-arrow-down' : 'popover-arrow-up')
+ svg.append(polygon)
+ return svg
+}
+
+export const createPopover = (width, height, { x, y }, dir) => {
+ const down = dir === 'down'
+ const fullHeight = height + arrowHeight
+ const overlay = document.createElement('div')
+ Object.assign(overlay.style, {
+ position: 'absolute',
+ left: '0',
+ top: '0',
+ width: '100vw',
+ height: '100vh',
+ })
+ const arrow = createArrow(down)
+ Object.assign(arrow.style, {
+ position: 'absolute',
+ left: `${clamp(radius, window.innerWidth - arrowWidth - radius,
+ x - arrowWidth / 2)}px`,
+ top: `${clamp(0, window.innerHeight - arrowHeight,
+ down ? y : y - arrowHeight)}px`,
+ })
+ const popover = document.createElement('div')
+ popover.classList.add('popover')
+ Object.assign(popover.style, {
+ position: 'absolute',
+ boxSizing: 'border-box',
+ overflow: 'hidden',
+ left: `${clamp(0, window.innerWidth - width, x - width / 2)}px`,
+ top: `${clamp(0, window.innerHeight - fullHeight,
+ down ? y + arrowHeight : y - fullHeight)}px`,
+ width: `${width}px`,
+ height: `${height}px`,
+ })
+ overlay.addEventListener('click', () => {
+ overlay.parentNode.removeChild(overlay)
+ arrow.parentNode.removeChild(arrow)
+ popover.parentNode.removeChild(popover)
+ })
+ return { popover, arrow, overlay }
+}