@somecat/epub-reader 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core.cjs +72 -9
- package/dist/core.cjs.map +1 -1
- package/dist/core.js +72 -9
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +72 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +72 -9
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +157 -88
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +157 -88
- package/dist/react.js.map +1 -1
- package/dist/vue.cjs +0 -0
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.js +0 -0
- package/dist/vue.js.map +1 -1
- package/package.json +1 -1
- package/style.css +42 -3
package/dist/core.cjs
CHANGED
|
@@ -3,33 +3,52 @@
|
|
|
3
3
|
require('foliate-js/view.js');
|
|
4
4
|
|
|
5
5
|
// src/core/reader.ts
|
|
6
|
-
var getContentCSS = (fontSize, isDark, lineHeight, letterSpacing, extraCSS) =>
|
|
6
|
+
var getContentCSS = (fontSize, isDark, lineHeight, letterSpacing, extraCSS) => {
|
|
7
|
+
const scale = fontSize / 100;
|
|
8
|
+
return `
|
|
7
9
|
@namespace epub "http://www.idpf.org/2007/ops";
|
|
8
|
-
|
|
10
|
+
:root:root {
|
|
9
11
|
color-scheme: ${isDark ? "dark" : "light"} !important;
|
|
10
12
|
}
|
|
11
|
-
body {
|
|
13
|
+
:root:root body {
|
|
12
14
|
background-color: transparent !important;
|
|
13
15
|
color: ${isDark ? "#e0e0e0" : "black"} !important;
|
|
14
16
|
font-size: ${fontSize}% !important;
|
|
15
17
|
line-height: ${lineHeight} !important;
|
|
16
18
|
letter-spacing: ${letterSpacing}em !important;
|
|
19
|
+
-webkit-text-size-adjust: 100% !important;
|
|
20
|
+
text-size-adjust: 100% !important;
|
|
17
21
|
}
|
|
18
|
-
|
|
22
|
+
|
|
23
|
+
:root:root body :is(p, li, blockquote) {
|
|
19
24
|
line-height: inherit !important;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
:root:root body p {
|
|
20
28
|
margin-bottom: 1em;
|
|
21
29
|
}
|
|
22
|
-
|
|
30
|
+
|
|
31
|
+
:root:root body a {
|
|
23
32
|
color: ${isDark ? "#64b5f6" : "#2563eb"} !important;
|
|
24
33
|
}
|
|
25
|
-
|
|
34
|
+
|
|
35
|
+
:root:root body img {
|
|
26
36
|
max-width: 100%;
|
|
27
37
|
height: auto;
|
|
28
38
|
object-fit: contain;
|
|
29
39
|
${isDark ? "filter: brightness(0.8) contrast(1.2);" : ""}
|
|
30
40
|
}
|
|
41
|
+
|
|
42
|
+
@supports (zoom: 1) {
|
|
43
|
+
:root:root body[data-epub-reader-force-zoom='true'] {
|
|
44
|
+
zoom: ${scale};
|
|
45
|
+
font-size: 100% !important;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
31
49
|
${extraCSS ?? ""}
|
|
32
50
|
`;
|
|
51
|
+
};
|
|
33
52
|
function createEBookReader(container, options = {}) {
|
|
34
53
|
if (!container) throw new Error("container is required");
|
|
35
54
|
if (!customElements.get("foliate-view")) throw new Error("foliate-view is not defined");
|
|
@@ -53,6 +72,8 @@ function createEBookReader(container, options = {}) {
|
|
|
53
72
|
let lineHeight = initialLineHeight;
|
|
54
73
|
let letterSpacing = initialLetterSpacing;
|
|
55
74
|
let searchToken = 0;
|
|
75
|
+
let activeDoc = null;
|
|
76
|
+
let forceZoomEnabled = false;
|
|
56
77
|
container.innerHTML = "";
|
|
57
78
|
const viewer = document.createElement("foliate-view");
|
|
58
79
|
viewer.style.display = "block";
|
|
@@ -60,21 +81,59 @@ function createEBookReader(container, options = {}) {
|
|
|
60
81
|
viewer.style.height = "100%";
|
|
61
82
|
viewer.setAttribute("margin", "48");
|
|
62
83
|
viewer.setAttribute("gap", "0.07");
|
|
63
|
-
const
|
|
84
|
+
const pickSampleEl = (doc) => {
|
|
85
|
+
const candidates = doc.querySelectorAll("p, li, blockquote, span, div");
|
|
86
|
+
for (const el of candidates) {
|
|
87
|
+
const text = el.textContent?.trim();
|
|
88
|
+
if (!text) continue;
|
|
89
|
+
if (text.length < 24) continue;
|
|
90
|
+
return el;
|
|
91
|
+
}
|
|
92
|
+
return doc.body;
|
|
93
|
+
};
|
|
94
|
+
const readFontSizePx = (doc) => {
|
|
95
|
+
const el = pickSampleEl(doc);
|
|
96
|
+
if (!el) return null;
|
|
97
|
+
const px = Number.parseFloat(getComputedStyle(el).fontSize);
|
|
98
|
+
return Number.isFinite(px) ? px : null;
|
|
99
|
+
};
|
|
100
|
+
const applyForceZoomIfNeeded = () => {
|
|
101
|
+
if (!forceZoomEnabled) return;
|
|
102
|
+
if (!activeDoc?.body) return;
|
|
103
|
+
activeDoc.body.setAttribute("data-epub-reader-force-zoom", "true");
|
|
104
|
+
};
|
|
105
|
+
const applyStyles = (check) => {
|
|
64
106
|
if (destroyed) return;
|
|
65
107
|
if (!viewer.renderer?.setStyles) return;
|
|
108
|
+
applyForceZoomIfNeeded();
|
|
66
109
|
viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS));
|
|
67
110
|
requestAnimationFrame(() => {
|
|
68
111
|
setTimeout(() => {
|
|
69
112
|
if (destroyed) return;
|
|
70
113
|
viewer.renderer?.render?.();
|
|
71
114
|
viewer.renderer?.expand?.();
|
|
115
|
+
if (!check) return;
|
|
116
|
+
if (forceZoomEnabled) return;
|
|
117
|
+
if (!activeDoc) return;
|
|
118
|
+
const { beforePx, beforeFontSize, afterFontSize } = check;
|
|
119
|
+
const afterPx = readFontSizePx(activeDoc);
|
|
120
|
+
if (beforePx == null || afterPx == null) return;
|
|
121
|
+
const isMeaningfulChange = Math.abs(afterFontSize - beforeFontSize) >= 10;
|
|
122
|
+
const isIneffective = Math.abs(afterPx - beforePx) < 0.5;
|
|
123
|
+
if (isMeaningfulChange && isIneffective) {
|
|
124
|
+
forceZoomEnabled = true;
|
|
125
|
+
applyForceZoomIfNeeded();
|
|
126
|
+
viewer.renderer?.setStyles?.(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS));
|
|
127
|
+
viewer.renderer?.render?.();
|
|
128
|
+
viewer.renderer?.expand?.();
|
|
129
|
+
}
|
|
72
130
|
}, 50);
|
|
73
131
|
});
|
|
74
132
|
};
|
|
75
133
|
const handleLoad = (e) => {
|
|
76
|
-
applyStyles();
|
|
77
134
|
const detail = e.detail;
|
|
135
|
+
activeDoc = detail?.doc ?? null;
|
|
136
|
+
applyStyles();
|
|
78
137
|
if (detail?.doc) onContentLoad?.(detail.doc);
|
|
79
138
|
};
|
|
80
139
|
const handleRelocate = (e) => {
|
|
@@ -91,6 +150,8 @@ function createEBookReader(container, options = {}) {
|
|
|
91
150
|
try {
|
|
92
151
|
viewer.clearSearch?.();
|
|
93
152
|
searchToken++;
|
|
153
|
+
activeDoc = null;
|
|
154
|
+
forceZoomEnabled = false;
|
|
94
155
|
await viewer.open?.(file);
|
|
95
156
|
const nextToc = viewer.book?.toc ?? [];
|
|
96
157
|
toc = nextToc;
|
|
@@ -136,8 +197,10 @@ function createEBookReader(container, options = {}) {
|
|
|
136
197
|
},
|
|
137
198
|
setFontSize(nextFontSize) {
|
|
138
199
|
const safe = Math.min(300, Math.max(50, nextFontSize));
|
|
200
|
+
const beforeFontSize = fontSize;
|
|
201
|
+
const beforePx = activeDoc && !forceZoomEnabled ? readFontSizePx(activeDoc) : null;
|
|
139
202
|
fontSize = safe;
|
|
140
|
-
applyStyles();
|
|
203
|
+
applyStyles({ beforePx, beforeFontSize, afterFontSize: safe });
|
|
141
204
|
},
|
|
142
205
|
setLineHeight(nextLineHeight) {
|
|
143
206
|
const safe = Math.min(3, Math.max(1, nextLineHeight));
|
package/dist/core.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/reader.ts"],"names":[],"mappings":"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","file":"core.cjs","sourcesContent":["import 'foliate-js/view.js'\r\nimport type { EBookReaderHandle, EBookReaderOptions, ProgressInfo, SearchOptions, SearchResult, TocItem } from './types.js'\r\n\r\ntype FoliateViewElement = HTMLElement & {\r\n book?: { toc?: TocItem[] }\r\n renderer?: {\r\n prevSection?: () => void\r\n nextSection?: () => void\r\n setStyles?: (css: string) => void\r\n render?: () => void\r\n expand?: () => void\r\n }\r\n open?: (file: File) => Promise<void>\r\n init?: (options?: unknown) => Promise<void>\r\n goLeft?: () => void\r\n goRight?: () => void\r\n goTo?: (target: string) => Promise<void> | void\r\n goToFraction?: (fraction: number) => Promise<void> | void\r\n search?: (options: unknown) => AsyncIterable<unknown>\r\n clearSearch?: () => void\r\n}\r\n\r\nconst getContentCSS = (fontSize: number, isDark: boolean, lineHeight: number, letterSpacing: number, extraCSS?: string) => `\n@namespace epub \"http://www.idpf.org/2007/ops\";\r\nhtml {\r\n color-scheme: ${isDark ? 'dark' : 'light'} !important;\r\n}\r\nbody {\r\n background-color: transparent !important;\r\n color: ${isDark ? '#e0e0e0' : 'black'} !important;\r\n font-size: ${fontSize}% !important;\r\n line-height: ${lineHeight} !important;\n letter-spacing: ${letterSpacing}em !important;\n}\r\np {\r\n line-height: inherit !important;\n margin-bottom: 1em;\r\n}\r\na {\r\n color: ${isDark ? '#64b5f6' : '#2563eb'} !important;\r\n}\r\nimg {\r\n max-width: 100%;\r\n height: auto;\r\n object-fit: contain;\r\n ${isDark ? 'filter: brightness(0.8) contrast(1.2);' : ''}\r\n}\r\n${extraCSS ?? ''}\r\n`\r\n\r\nexport function createEBookReader(container: HTMLElement, options: EBookReaderOptions = {}): EBookReaderHandle {\r\n if (!container) throw new Error('container is required')\r\n if (!customElements.get('foliate-view')) throw new Error('foliate-view is not defined')\r\n\r\n const {\r\n darkMode: initialDarkMode = false,\r\n fontSize: initialFontSize = 100,\r\n lineHeight: initialLineHeight = 1.6,\n letterSpacing: initialLetterSpacing = 0,\n extraContentCSS,\r\n onReady,\r\n onError,\r\n onProgress,\r\n onToc,\r\n onSearchProgress,\r\n onContentLoad,\r\n } = options\r\n\r\n let destroyed = false\r\n let toc: TocItem[] = []\r\n let fontSize = initialFontSize\r\n let darkMode = initialDarkMode\r\n let lineHeight = initialLineHeight\n let letterSpacing = initialLetterSpacing\n let searchToken = 0\r\n\r\n container.innerHTML = ''\r\n\r\n const viewer = document.createElement('foliate-view') as FoliateViewElement\r\n viewer.style.display = 'block'\r\n viewer.style.width = '100%'\r\n viewer.style.height = '100%'\r\n viewer.setAttribute('margin', '48')\r\n viewer.setAttribute('gap', '0.07')\r\n\r\n const applyStyles = () => {\r\n if (destroyed) return\r\n if (!viewer.renderer?.setStyles) return\r\n viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS))\n requestAnimationFrame(() => {\r\n setTimeout(() => {\r\n if (destroyed) return\r\n viewer.renderer?.render?.()\r\n viewer.renderer?.expand?.()\r\n }, 50)\r\n })\r\n }\r\n\r\n const handleLoad = (e: Event) => {\r\n applyStyles()\r\n const detail = (e as CustomEvent).detail as { doc?: Document } | undefined\r\n if (detail?.doc) onContentLoad?.(detail.doc)\r\n }\r\n\r\n const handleRelocate = (e: Event) => {\r\n const detail = (e as CustomEvent).detail as ProgressInfo\r\n onProgress?.(detail)\r\n }\r\n\r\n viewer.addEventListener('load', handleLoad as EventListener)\r\n viewer.addEventListener('relocate', handleRelocate as EventListener)\r\n\r\n container.appendChild(viewer)\r\n\r\n const handle: EBookReaderHandle = {\r\n async open(file) {\r\n if (destroyed) return\r\n if (!file) return\r\n\r\n try {\r\n viewer.clearSearch?.()\r\n searchToken++\r\n\r\n await viewer.open?.(file)\r\n\r\n const nextToc = viewer.book?.toc ?? []\r\n toc = nextToc\r\n onToc?.(toc)\r\n\r\n await viewer.init?.({ showTextStart: true })\r\n applyStyles()\r\n } catch (error) {\r\n onError?.(error)\r\n throw error\r\n }\r\n },\r\n destroy() {\r\n if (destroyed) return\r\n destroyed = true\r\n searchToken++\r\n viewer.removeEventListener('load', handleLoad)\r\n viewer.removeEventListener('relocate', handleRelocate as EventListener)\r\n container.innerHTML = ''\r\n },\r\n prevPage() {\r\n viewer.goLeft?.()\r\n },\r\n nextPage() {\r\n viewer.goRight?.()\r\n },\r\n prevSection() {\r\n viewer.renderer?.prevSection?.()\r\n },\r\n nextSection() {\r\n viewer.renderer?.nextSection?.()\r\n },\r\n goTo(target) {\r\n if (!target) return\r\n viewer.goTo?.(target)\r\n },\r\n goToFraction(fraction) {\r\n const safe = Math.min(1, Math.max(0, fraction))\r\n viewer.goToFraction?.(safe)\r\n },\r\n setDarkMode(nextDarkMode) {\r\n darkMode = nextDarkMode\r\n applyStyles()\r\n },\r\n setFontSize(nextFontSize) {\r\n const safe = Math.min(300, Math.max(50, nextFontSize))\r\n fontSize = safe\r\n applyStyles()\r\n },\r\n setLineHeight(nextLineHeight) {\n const safe = Math.min(3, Math.max(1, nextLineHeight))\n lineHeight = safe\n applyStyles()\n },\n setLetterSpacing(nextLetterSpacing) {\n const safe = Math.min(0.3, Math.max(0, nextLetterSpacing))\n letterSpacing = safe\n applyStyles()\n },\n async search(query, opts: SearchOptions = {}) {\r\n const normalized = query.trim()\r\n if (!normalized) {\r\n viewer.clearSearch?.()\r\n return []\r\n }\r\n\r\n const token = ++searchToken\r\n const results: SearchResult[] = []\r\n\r\n try {\r\n for await (const item of viewer.search?.({\r\n query: normalized,\r\n matchCase: Boolean(opts.matchCase),\r\n matchWholeWords: Boolean(opts.wholeWords),\r\n matchDiacritics: Boolean(opts.matchDiacritics),\r\n }) ?? []) {\r\n if (destroyed || token !== searchToken) return results\r\n\r\n if (item === 'done') {\r\n onSearchProgress?.({ done: true, progress: 1 })\r\n break\r\n }\r\n\r\n if (typeof item === 'object' && item && 'progress' in (item as any)) {\r\n const progress = (item as any).progress\r\n if (typeof progress === 'number') onSearchProgress?.({ progress })\r\n continue\r\n }\r\n\r\n const anyItem = item as any\r\n if (anyItem?.subitems?.length) {\r\n for (const sub of anyItem.subitems) {\r\n results.push({\r\n label: anyItem.label,\r\n cfi: sub?.cfi,\r\n excerpt: sub?.excerpt,\r\n title: sub?.title,\r\n })\r\n }\r\n } else if (anyItem?.cfi) {\r\n results.push({\r\n cfi: anyItem.cfi,\r\n excerpt: anyItem.excerpt,\r\n title: anyItem.title,\r\n })\r\n }\r\n }\r\n } catch (error) {\r\n onError?.(error)\r\n throw error\r\n }\r\n\r\n return results\r\n },\r\n cancelSearch() {\r\n searchToken++\r\n },\r\n clearSearch() {\r\n viewer.clearSearch?.()\r\n },\r\n getToc() {\r\n return toc\r\n },\r\n }\r\n\r\n onReady?.(handle)\r\n return handle\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/reader.ts"],"names":[],"mappings":"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","file":"core.cjs","sourcesContent":["import 'foliate-js/view.js'\r\nimport type { EBookReaderHandle, EBookReaderOptions, ProgressInfo, SearchOptions, SearchResult, TocItem } from './types.js'\r\n\r\ntype FoliateViewElement = HTMLElement & {\r\n book?: { toc?: TocItem[] }\r\n renderer?: {\r\n prevSection?: () => void\r\n nextSection?: () => void\r\n setStyles?: (css: string) => void\r\n render?: () => void\r\n expand?: () => void\r\n }\r\n open?: (file: File) => Promise<void>\r\n init?: (options?: unknown) => Promise<void>\r\n goLeft?: () => void\r\n goRight?: () => void\r\n goTo?: (target: string) => Promise<void> | void\r\n goToFraction?: (fraction: number) => Promise<void> | void\r\n search?: (options: unknown) => AsyncIterable<unknown>\r\n clearSearch?: () => void\r\n}\r\n\r\nconst getContentCSS = (fontSize: number, isDark: boolean, lineHeight: number, letterSpacing: number, extraCSS?: string) => {\r\n const scale = fontSize / 100\r\n\r\n return `\r\n@namespace epub \"http://www.idpf.org/2007/ops\";\r\n:root:root {\r\n color-scheme: ${isDark ? 'dark' : 'light'} !important;\r\n}\r\n:root:root body {\r\n background-color: transparent !important;\r\n color: ${isDark ? '#e0e0e0' : 'black'} !important;\r\n font-size: ${fontSize}% !important;\r\n line-height: ${lineHeight} !important;\r\n letter-spacing: ${letterSpacing}em !important;\r\n -webkit-text-size-adjust: 100% !important;\r\n text-size-adjust: 100% !important;\r\n}\r\n\r\n:root:root body :is(p, li, blockquote) {\r\n line-height: inherit !important;\r\n}\r\n\r\n:root:root body p {\r\n margin-bottom: 1em;\r\n}\r\n\r\n:root:root body a {\r\n color: ${isDark ? '#64b5f6' : '#2563eb'} !important;\r\n}\r\n\r\n:root:root body img {\r\n max-width: 100%;\r\n height: auto;\r\n object-fit: contain;\r\n ${isDark ? 'filter: brightness(0.8) contrast(1.2);' : ''}\r\n}\r\n\r\n@supports (zoom: 1) {\r\n :root:root body[data-epub-reader-force-zoom='true'] {\r\n zoom: ${scale};\r\n font-size: 100% !important;\r\n }\r\n}\r\n\r\n${extraCSS ?? ''}\r\n`\r\n}\r\n\r\nexport function createEBookReader(container: HTMLElement, options: EBookReaderOptions = {}): EBookReaderHandle {\r\n if (!container) throw new Error('container is required')\r\n if (!customElements.get('foliate-view')) throw new Error('foliate-view is not defined')\r\n\r\n const {\r\n darkMode: initialDarkMode = false,\r\n fontSize: initialFontSize = 100,\r\n lineHeight: initialLineHeight = 1.6,\r\n letterSpacing: initialLetterSpacing = 0,\r\n extraContentCSS,\r\n onReady,\r\n onError,\r\n onProgress,\r\n onToc,\r\n onSearchProgress,\r\n onContentLoad,\r\n } = options\r\n\r\n let destroyed = false\r\n let toc: TocItem[] = []\r\n let fontSize = initialFontSize\r\n let darkMode = initialDarkMode\r\n let lineHeight = initialLineHeight\r\n let letterSpacing = initialLetterSpacing\r\n let searchToken = 0\r\n let activeDoc: Document | null = null\r\n let forceZoomEnabled = false\r\n\r\n container.innerHTML = ''\r\n\r\n const viewer = document.createElement('foliate-view') as FoliateViewElement\r\n viewer.style.display = 'block'\r\n viewer.style.width = '100%'\r\n viewer.style.height = '100%'\r\n viewer.setAttribute('margin', '48')\r\n viewer.setAttribute('gap', '0.07')\r\n\r\n const pickSampleEl = (doc: Document) => {\r\n const candidates = doc.querySelectorAll('p, li, blockquote, span, div')\r\n for (const el of candidates) {\r\n const text = el.textContent?.trim()\r\n if (!text) continue\r\n if (text.length < 24) continue\r\n return el as HTMLElement\r\n }\r\n return doc.body\r\n }\r\n\r\n const readFontSizePx = (doc: Document) => {\r\n const el = pickSampleEl(doc)\r\n if (!el) return null\r\n const px = Number.parseFloat(getComputedStyle(el).fontSize)\r\n return Number.isFinite(px) ? px : null\r\n }\r\n\r\n const applyForceZoomIfNeeded = () => {\r\n if (!forceZoomEnabled) return\r\n if (!activeDoc?.body) return\r\n activeDoc.body.setAttribute('data-epub-reader-force-zoom', 'true')\r\n }\r\n\r\n const applyStyles = (check?: { beforePx: number | null; beforeFontSize: number; afterFontSize: number }) => {\r\n if (destroyed) return\r\n if (!viewer.renderer?.setStyles) return\r\n\r\n applyForceZoomIfNeeded()\r\n\r\n viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS))\r\n requestAnimationFrame(() => {\r\n setTimeout(() => {\r\n if (destroyed) return\r\n viewer.renderer?.render?.()\r\n viewer.renderer?.expand?.()\r\n\r\n if (!check) return\r\n if (forceZoomEnabled) return\r\n if (!activeDoc) return\r\n\r\n const { beforePx, beforeFontSize, afterFontSize } = check\r\n const afterPx = readFontSizePx(activeDoc)\r\n if (beforePx == null || afterPx == null) return\r\n\r\n const isMeaningfulChange = Math.abs(afterFontSize - beforeFontSize) >= 10\r\n const isIneffective = Math.abs(afterPx - beforePx) < 0.5\r\n if (isMeaningfulChange && isIneffective) {\r\n forceZoomEnabled = true\r\n applyForceZoomIfNeeded()\r\n viewer.renderer?.setStyles?.(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS))\r\n viewer.renderer?.render?.()\r\n viewer.renderer?.expand?.()\r\n }\r\n }, 50)\r\n })\r\n }\r\n\r\n const handleLoad = (e: Event) => {\r\n const detail = (e as CustomEvent).detail as { doc?: Document } | undefined\r\n activeDoc = detail?.doc ?? null\r\n applyStyles()\r\n if (detail?.doc) onContentLoad?.(detail.doc)\r\n }\r\n\r\n const handleRelocate = (e: Event) => {\r\n const detail = (e as CustomEvent).detail as ProgressInfo\r\n onProgress?.(detail)\r\n }\r\n\r\n viewer.addEventListener('load', handleLoad as EventListener)\r\n viewer.addEventListener('relocate', handleRelocate as EventListener)\r\n\r\n container.appendChild(viewer)\r\n\r\n const handle: EBookReaderHandle = {\r\n async open(file) {\r\n if (destroyed) return\r\n if (!file) return\r\n\r\n try {\r\n viewer.clearSearch?.()\r\n searchToken++\r\n activeDoc = null\r\n forceZoomEnabled = false\r\n\r\n await viewer.open?.(file)\r\n\r\n const nextToc = viewer.book?.toc ?? []\r\n toc = nextToc\r\n onToc?.(toc)\r\n\r\n await viewer.init?.({ showTextStart: true })\r\n applyStyles()\r\n } catch (error) {\r\n onError?.(error)\r\n throw error\r\n }\r\n },\r\n destroy() {\r\n if (destroyed) return\r\n destroyed = true\r\n searchToken++\r\n viewer.removeEventListener('load', handleLoad)\r\n viewer.removeEventListener('relocate', handleRelocate as EventListener)\r\n container.innerHTML = ''\r\n },\r\n prevPage() {\r\n viewer.goLeft?.()\r\n },\r\n nextPage() {\r\n viewer.goRight?.()\r\n },\r\n prevSection() {\r\n viewer.renderer?.prevSection?.()\r\n },\r\n nextSection() {\r\n viewer.renderer?.nextSection?.()\r\n },\r\n goTo(target) {\r\n if (!target) return\r\n viewer.goTo?.(target)\r\n },\r\n goToFraction(fraction) {\r\n const safe = Math.min(1, Math.max(0, fraction))\r\n viewer.goToFraction?.(safe)\r\n },\r\n setDarkMode(nextDarkMode) {\r\n darkMode = nextDarkMode\r\n applyStyles()\r\n },\r\n setFontSize(nextFontSize) {\r\n const safe = Math.min(300, Math.max(50, nextFontSize))\r\n const beforeFontSize = fontSize\r\n const beforePx = activeDoc && !forceZoomEnabled ? readFontSizePx(activeDoc) : null\r\n fontSize = safe\r\n applyStyles({ beforePx, beforeFontSize, afterFontSize: safe })\r\n },\r\n setLineHeight(nextLineHeight) {\r\n const safe = Math.min(3, Math.max(1, nextLineHeight))\r\n lineHeight = safe\r\n applyStyles()\r\n },\r\n setLetterSpacing(nextLetterSpacing) {\r\n const safe = Math.min(0.3, Math.max(0, nextLetterSpacing))\r\n letterSpacing = safe\r\n applyStyles()\r\n },\r\n async search(query, opts: SearchOptions = {}) {\r\n const normalized = query.trim()\r\n if (!normalized) {\r\n viewer.clearSearch?.()\r\n return []\r\n }\r\n\r\n const token = ++searchToken\r\n const results: SearchResult[] = []\r\n\r\n try {\r\n for await (const item of viewer.search?.({\r\n query: normalized,\r\n matchCase: Boolean(opts.matchCase),\r\n matchWholeWords: Boolean(opts.wholeWords),\r\n matchDiacritics: Boolean(opts.matchDiacritics),\r\n }) ?? []) {\r\n if (destroyed || token !== searchToken) return results\r\n\r\n if (item === 'done') {\r\n onSearchProgress?.({ done: true, progress: 1 })\r\n break\r\n }\r\n\r\n if (typeof item === 'object' && item && 'progress' in (item as any)) {\r\n const progress = (item as any).progress\r\n if (typeof progress === 'number') onSearchProgress?.({ progress })\r\n continue\r\n }\r\n\r\n const anyItem = item as any\r\n if (anyItem?.subitems?.length) {\r\n for (const sub of anyItem.subitems) {\r\n results.push({\r\n label: anyItem.label,\r\n cfi: sub?.cfi,\r\n excerpt: sub?.excerpt,\r\n title: sub?.title,\r\n })\r\n }\r\n } else if (anyItem?.cfi) {\r\n results.push({\r\n cfi: anyItem.cfi,\r\n excerpt: anyItem.excerpt,\r\n title: anyItem.title,\r\n })\r\n }\r\n }\r\n } catch (error) {\r\n onError?.(error)\r\n throw error\r\n }\r\n\r\n return results\r\n },\r\n cancelSearch() {\r\n searchToken++\r\n },\r\n clearSearch() {\r\n viewer.clearSearch?.()\r\n },\r\n getToc() {\r\n return toc\r\n },\r\n }\r\n\r\n onReady?.(handle)\r\n return handle\r\n}\r\n"]}
|
package/dist/core.js
CHANGED
|
@@ -1,33 +1,52 @@
|
|
|
1
1
|
import 'foliate-js/view.js';
|
|
2
2
|
|
|
3
3
|
// src/core/reader.ts
|
|
4
|
-
var getContentCSS = (fontSize, isDark, lineHeight, letterSpacing, extraCSS) =>
|
|
4
|
+
var getContentCSS = (fontSize, isDark, lineHeight, letterSpacing, extraCSS) => {
|
|
5
|
+
const scale = fontSize / 100;
|
|
6
|
+
return `
|
|
5
7
|
@namespace epub "http://www.idpf.org/2007/ops";
|
|
6
|
-
|
|
8
|
+
:root:root {
|
|
7
9
|
color-scheme: ${isDark ? "dark" : "light"} !important;
|
|
8
10
|
}
|
|
9
|
-
body {
|
|
11
|
+
:root:root body {
|
|
10
12
|
background-color: transparent !important;
|
|
11
13
|
color: ${isDark ? "#e0e0e0" : "black"} !important;
|
|
12
14
|
font-size: ${fontSize}% !important;
|
|
13
15
|
line-height: ${lineHeight} !important;
|
|
14
16
|
letter-spacing: ${letterSpacing}em !important;
|
|
17
|
+
-webkit-text-size-adjust: 100% !important;
|
|
18
|
+
text-size-adjust: 100% !important;
|
|
15
19
|
}
|
|
16
|
-
|
|
20
|
+
|
|
21
|
+
:root:root body :is(p, li, blockquote) {
|
|
17
22
|
line-height: inherit !important;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
:root:root body p {
|
|
18
26
|
margin-bottom: 1em;
|
|
19
27
|
}
|
|
20
|
-
|
|
28
|
+
|
|
29
|
+
:root:root body a {
|
|
21
30
|
color: ${isDark ? "#64b5f6" : "#2563eb"} !important;
|
|
22
31
|
}
|
|
23
|
-
|
|
32
|
+
|
|
33
|
+
:root:root body img {
|
|
24
34
|
max-width: 100%;
|
|
25
35
|
height: auto;
|
|
26
36
|
object-fit: contain;
|
|
27
37
|
${isDark ? "filter: brightness(0.8) contrast(1.2);" : ""}
|
|
28
38
|
}
|
|
39
|
+
|
|
40
|
+
@supports (zoom: 1) {
|
|
41
|
+
:root:root body[data-epub-reader-force-zoom='true'] {
|
|
42
|
+
zoom: ${scale};
|
|
43
|
+
font-size: 100% !important;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
29
47
|
${extraCSS ?? ""}
|
|
30
48
|
`;
|
|
49
|
+
};
|
|
31
50
|
function createEBookReader(container, options = {}) {
|
|
32
51
|
if (!container) throw new Error("container is required");
|
|
33
52
|
if (!customElements.get("foliate-view")) throw new Error("foliate-view is not defined");
|
|
@@ -51,6 +70,8 @@ function createEBookReader(container, options = {}) {
|
|
|
51
70
|
let lineHeight = initialLineHeight;
|
|
52
71
|
let letterSpacing = initialLetterSpacing;
|
|
53
72
|
let searchToken = 0;
|
|
73
|
+
let activeDoc = null;
|
|
74
|
+
let forceZoomEnabled = false;
|
|
54
75
|
container.innerHTML = "";
|
|
55
76
|
const viewer = document.createElement("foliate-view");
|
|
56
77
|
viewer.style.display = "block";
|
|
@@ -58,21 +79,59 @@ function createEBookReader(container, options = {}) {
|
|
|
58
79
|
viewer.style.height = "100%";
|
|
59
80
|
viewer.setAttribute("margin", "48");
|
|
60
81
|
viewer.setAttribute("gap", "0.07");
|
|
61
|
-
const
|
|
82
|
+
const pickSampleEl = (doc) => {
|
|
83
|
+
const candidates = doc.querySelectorAll("p, li, blockquote, span, div");
|
|
84
|
+
for (const el of candidates) {
|
|
85
|
+
const text = el.textContent?.trim();
|
|
86
|
+
if (!text) continue;
|
|
87
|
+
if (text.length < 24) continue;
|
|
88
|
+
return el;
|
|
89
|
+
}
|
|
90
|
+
return doc.body;
|
|
91
|
+
};
|
|
92
|
+
const readFontSizePx = (doc) => {
|
|
93
|
+
const el = pickSampleEl(doc);
|
|
94
|
+
if (!el) return null;
|
|
95
|
+
const px = Number.parseFloat(getComputedStyle(el).fontSize);
|
|
96
|
+
return Number.isFinite(px) ? px : null;
|
|
97
|
+
};
|
|
98
|
+
const applyForceZoomIfNeeded = () => {
|
|
99
|
+
if (!forceZoomEnabled) return;
|
|
100
|
+
if (!activeDoc?.body) return;
|
|
101
|
+
activeDoc.body.setAttribute("data-epub-reader-force-zoom", "true");
|
|
102
|
+
};
|
|
103
|
+
const applyStyles = (check) => {
|
|
62
104
|
if (destroyed) return;
|
|
63
105
|
if (!viewer.renderer?.setStyles) return;
|
|
106
|
+
applyForceZoomIfNeeded();
|
|
64
107
|
viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS));
|
|
65
108
|
requestAnimationFrame(() => {
|
|
66
109
|
setTimeout(() => {
|
|
67
110
|
if (destroyed) return;
|
|
68
111
|
viewer.renderer?.render?.();
|
|
69
112
|
viewer.renderer?.expand?.();
|
|
113
|
+
if (!check) return;
|
|
114
|
+
if (forceZoomEnabled) return;
|
|
115
|
+
if (!activeDoc) return;
|
|
116
|
+
const { beforePx, beforeFontSize, afterFontSize } = check;
|
|
117
|
+
const afterPx = readFontSizePx(activeDoc);
|
|
118
|
+
if (beforePx == null || afterPx == null) return;
|
|
119
|
+
const isMeaningfulChange = Math.abs(afterFontSize - beforeFontSize) >= 10;
|
|
120
|
+
const isIneffective = Math.abs(afterPx - beforePx) < 0.5;
|
|
121
|
+
if (isMeaningfulChange && isIneffective) {
|
|
122
|
+
forceZoomEnabled = true;
|
|
123
|
+
applyForceZoomIfNeeded();
|
|
124
|
+
viewer.renderer?.setStyles?.(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS));
|
|
125
|
+
viewer.renderer?.render?.();
|
|
126
|
+
viewer.renderer?.expand?.();
|
|
127
|
+
}
|
|
70
128
|
}, 50);
|
|
71
129
|
});
|
|
72
130
|
};
|
|
73
131
|
const handleLoad = (e) => {
|
|
74
|
-
applyStyles();
|
|
75
132
|
const detail = e.detail;
|
|
133
|
+
activeDoc = detail?.doc ?? null;
|
|
134
|
+
applyStyles();
|
|
76
135
|
if (detail?.doc) onContentLoad?.(detail.doc);
|
|
77
136
|
};
|
|
78
137
|
const handleRelocate = (e) => {
|
|
@@ -89,6 +148,8 @@ function createEBookReader(container, options = {}) {
|
|
|
89
148
|
try {
|
|
90
149
|
viewer.clearSearch?.();
|
|
91
150
|
searchToken++;
|
|
151
|
+
activeDoc = null;
|
|
152
|
+
forceZoomEnabled = false;
|
|
92
153
|
await viewer.open?.(file);
|
|
93
154
|
const nextToc = viewer.book?.toc ?? [];
|
|
94
155
|
toc = nextToc;
|
|
@@ -134,8 +195,10 @@ function createEBookReader(container, options = {}) {
|
|
|
134
195
|
},
|
|
135
196
|
setFontSize(nextFontSize) {
|
|
136
197
|
const safe = Math.min(300, Math.max(50, nextFontSize));
|
|
198
|
+
const beforeFontSize = fontSize;
|
|
199
|
+
const beforePx = activeDoc && !forceZoomEnabled ? readFontSizePx(activeDoc) : null;
|
|
137
200
|
fontSize = safe;
|
|
138
|
-
applyStyles();
|
|
201
|
+
applyStyles({ beforePx, beforeFontSize, afterFontSize: safe });
|
|
139
202
|
},
|
|
140
203
|
setLineHeight(nextLineHeight) {
|
|
141
204
|
const safe = Math.min(3, Math.max(1, nextLineHeight));
|
package/dist/core.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/reader.ts"],"names":[],"mappings":"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","file":"core.js","sourcesContent":["import 'foliate-js/view.js'\r\nimport type { EBookReaderHandle, EBookReaderOptions, ProgressInfo, SearchOptions, SearchResult, TocItem } from './types.js'\r\n\r\ntype FoliateViewElement = HTMLElement & {\r\n book?: { toc?: TocItem[] }\r\n renderer?: {\r\n prevSection?: () => void\r\n nextSection?: () => void\r\n setStyles?: (css: string) => void\r\n render?: () => void\r\n expand?: () => void\r\n }\r\n open?: (file: File) => Promise<void>\r\n init?: (options?: unknown) => Promise<void>\r\n goLeft?: () => void\r\n goRight?: () => void\r\n goTo?: (target: string) => Promise<void> | void\r\n goToFraction?: (fraction: number) => Promise<void> | void\r\n search?: (options: unknown) => AsyncIterable<unknown>\r\n clearSearch?: () => void\r\n}\r\n\r\nconst getContentCSS = (fontSize: number, isDark: boolean, lineHeight: number, letterSpacing: number, extraCSS?: string) => `\n@namespace epub \"http://www.idpf.org/2007/ops\";\r\nhtml {\r\n color-scheme: ${isDark ? 'dark' : 'light'} !important;\r\n}\r\nbody {\r\n background-color: transparent !important;\r\n color: ${isDark ? '#e0e0e0' : 'black'} !important;\r\n font-size: ${fontSize}% !important;\r\n line-height: ${lineHeight} !important;\n letter-spacing: ${letterSpacing}em !important;\n}\r\np {\r\n line-height: inherit !important;\n margin-bottom: 1em;\r\n}\r\na {\r\n color: ${isDark ? '#64b5f6' : '#2563eb'} !important;\r\n}\r\nimg {\r\n max-width: 100%;\r\n height: auto;\r\n object-fit: contain;\r\n ${isDark ? 'filter: brightness(0.8) contrast(1.2);' : ''}\r\n}\r\n${extraCSS ?? ''}\r\n`\r\n\r\nexport function createEBookReader(container: HTMLElement, options: EBookReaderOptions = {}): EBookReaderHandle {\r\n if (!container) throw new Error('container is required')\r\n if (!customElements.get('foliate-view')) throw new Error('foliate-view is not defined')\r\n\r\n const {\r\n darkMode: initialDarkMode = false,\r\n fontSize: initialFontSize = 100,\r\n lineHeight: initialLineHeight = 1.6,\n letterSpacing: initialLetterSpacing = 0,\n extraContentCSS,\r\n onReady,\r\n onError,\r\n onProgress,\r\n onToc,\r\n onSearchProgress,\r\n onContentLoad,\r\n } = options\r\n\r\n let destroyed = false\r\n let toc: TocItem[] = []\r\n let fontSize = initialFontSize\r\n let darkMode = initialDarkMode\r\n let lineHeight = initialLineHeight\n let letterSpacing = initialLetterSpacing\n let searchToken = 0\r\n\r\n container.innerHTML = ''\r\n\r\n const viewer = document.createElement('foliate-view') as FoliateViewElement\r\n viewer.style.display = 'block'\r\n viewer.style.width = '100%'\r\n viewer.style.height = '100%'\r\n viewer.setAttribute('margin', '48')\r\n viewer.setAttribute('gap', '0.07')\r\n\r\n const applyStyles = () => {\r\n if (destroyed) return\r\n if (!viewer.renderer?.setStyles) return\r\n viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS))\n requestAnimationFrame(() => {\r\n setTimeout(() => {\r\n if (destroyed) return\r\n viewer.renderer?.render?.()\r\n viewer.renderer?.expand?.()\r\n }, 50)\r\n })\r\n }\r\n\r\n const handleLoad = (e: Event) => {\r\n applyStyles()\r\n const detail = (e as CustomEvent).detail as { doc?: Document } | undefined\r\n if (detail?.doc) onContentLoad?.(detail.doc)\r\n }\r\n\r\n const handleRelocate = (e: Event) => {\r\n const detail = (e as CustomEvent).detail as ProgressInfo\r\n onProgress?.(detail)\r\n }\r\n\r\n viewer.addEventListener('load', handleLoad as EventListener)\r\n viewer.addEventListener('relocate', handleRelocate as EventListener)\r\n\r\n container.appendChild(viewer)\r\n\r\n const handle: EBookReaderHandle = {\r\n async open(file) {\r\n if (destroyed) return\r\n if (!file) return\r\n\r\n try {\r\n viewer.clearSearch?.()\r\n searchToken++\r\n\r\n await viewer.open?.(file)\r\n\r\n const nextToc = viewer.book?.toc ?? []\r\n toc = nextToc\r\n onToc?.(toc)\r\n\r\n await viewer.init?.({ showTextStart: true })\r\n applyStyles()\r\n } catch (error) {\r\n onError?.(error)\r\n throw error\r\n }\r\n },\r\n destroy() {\r\n if (destroyed) return\r\n destroyed = true\r\n searchToken++\r\n viewer.removeEventListener('load', handleLoad)\r\n viewer.removeEventListener('relocate', handleRelocate as EventListener)\r\n container.innerHTML = ''\r\n },\r\n prevPage() {\r\n viewer.goLeft?.()\r\n },\r\n nextPage() {\r\n viewer.goRight?.()\r\n },\r\n prevSection() {\r\n viewer.renderer?.prevSection?.()\r\n },\r\n nextSection() {\r\n viewer.renderer?.nextSection?.()\r\n },\r\n goTo(target) {\r\n if (!target) return\r\n viewer.goTo?.(target)\r\n },\r\n goToFraction(fraction) {\r\n const safe = Math.min(1, Math.max(0, fraction))\r\n viewer.goToFraction?.(safe)\r\n },\r\n setDarkMode(nextDarkMode) {\r\n darkMode = nextDarkMode\r\n applyStyles()\r\n },\r\n setFontSize(nextFontSize) {\r\n const safe = Math.min(300, Math.max(50, nextFontSize))\r\n fontSize = safe\r\n applyStyles()\r\n },\r\n setLineHeight(nextLineHeight) {\n const safe = Math.min(3, Math.max(1, nextLineHeight))\n lineHeight = safe\n applyStyles()\n },\n setLetterSpacing(nextLetterSpacing) {\n const safe = Math.min(0.3, Math.max(0, nextLetterSpacing))\n letterSpacing = safe\n applyStyles()\n },\n async search(query, opts: SearchOptions = {}) {\r\n const normalized = query.trim()\r\n if (!normalized) {\r\n viewer.clearSearch?.()\r\n return []\r\n }\r\n\r\n const token = ++searchToken\r\n const results: SearchResult[] = []\r\n\r\n try {\r\n for await (const item of viewer.search?.({\r\n query: normalized,\r\n matchCase: Boolean(opts.matchCase),\r\n matchWholeWords: Boolean(opts.wholeWords),\r\n matchDiacritics: Boolean(opts.matchDiacritics),\r\n }) ?? []) {\r\n if (destroyed || token !== searchToken) return results\r\n\r\n if (item === 'done') {\r\n onSearchProgress?.({ done: true, progress: 1 })\r\n break\r\n }\r\n\r\n if (typeof item === 'object' && item && 'progress' in (item as any)) {\r\n const progress = (item as any).progress\r\n if (typeof progress === 'number') onSearchProgress?.({ progress })\r\n continue\r\n }\r\n\r\n const anyItem = item as any\r\n if (anyItem?.subitems?.length) {\r\n for (const sub of anyItem.subitems) {\r\n results.push({\r\n label: anyItem.label,\r\n cfi: sub?.cfi,\r\n excerpt: sub?.excerpt,\r\n title: sub?.title,\r\n })\r\n }\r\n } else if (anyItem?.cfi) {\r\n results.push({\r\n cfi: anyItem.cfi,\r\n excerpt: anyItem.excerpt,\r\n title: anyItem.title,\r\n })\r\n }\r\n }\r\n } catch (error) {\r\n onError?.(error)\r\n throw error\r\n }\r\n\r\n return results\r\n },\r\n cancelSearch() {\r\n searchToken++\r\n },\r\n clearSearch() {\r\n viewer.clearSearch?.()\r\n },\r\n getToc() {\r\n return toc\r\n },\r\n }\r\n\r\n onReady?.(handle)\r\n return handle\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/reader.ts"],"names":[],"mappings":"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","file":"core.js","sourcesContent":["import 'foliate-js/view.js'\r\nimport type { EBookReaderHandle, EBookReaderOptions, ProgressInfo, SearchOptions, SearchResult, TocItem } from './types.js'\r\n\r\ntype FoliateViewElement = HTMLElement & {\r\n book?: { toc?: TocItem[] }\r\n renderer?: {\r\n prevSection?: () => void\r\n nextSection?: () => void\r\n setStyles?: (css: string) => void\r\n render?: () => void\r\n expand?: () => void\r\n }\r\n open?: (file: File) => Promise<void>\r\n init?: (options?: unknown) => Promise<void>\r\n goLeft?: () => void\r\n goRight?: () => void\r\n goTo?: (target: string) => Promise<void> | void\r\n goToFraction?: (fraction: number) => Promise<void> | void\r\n search?: (options: unknown) => AsyncIterable<unknown>\r\n clearSearch?: () => void\r\n}\r\n\r\nconst getContentCSS = (fontSize: number, isDark: boolean, lineHeight: number, letterSpacing: number, extraCSS?: string) => {\r\n const scale = fontSize / 100\r\n\r\n return `\r\n@namespace epub \"http://www.idpf.org/2007/ops\";\r\n:root:root {\r\n color-scheme: ${isDark ? 'dark' : 'light'} !important;\r\n}\r\n:root:root body {\r\n background-color: transparent !important;\r\n color: ${isDark ? '#e0e0e0' : 'black'} !important;\r\n font-size: ${fontSize}% !important;\r\n line-height: ${lineHeight} !important;\r\n letter-spacing: ${letterSpacing}em !important;\r\n -webkit-text-size-adjust: 100% !important;\r\n text-size-adjust: 100% !important;\r\n}\r\n\r\n:root:root body :is(p, li, blockquote) {\r\n line-height: inherit !important;\r\n}\r\n\r\n:root:root body p {\r\n margin-bottom: 1em;\r\n}\r\n\r\n:root:root body a {\r\n color: ${isDark ? '#64b5f6' : '#2563eb'} !important;\r\n}\r\n\r\n:root:root body img {\r\n max-width: 100%;\r\n height: auto;\r\n object-fit: contain;\r\n ${isDark ? 'filter: brightness(0.8) contrast(1.2);' : ''}\r\n}\r\n\r\n@supports (zoom: 1) {\r\n :root:root body[data-epub-reader-force-zoom='true'] {\r\n zoom: ${scale};\r\n font-size: 100% !important;\r\n }\r\n}\r\n\r\n${extraCSS ?? ''}\r\n`\r\n}\r\n\r\nexport function createEBookReader(container: HTMLElement, options: EBookReaderOptions = {}): EBookReaderHandle {\r\n if (!container) throw new Error('container is required')\r\n if (!customElements.get('foliate-view')) throw new Error('foliate-view is not defined')\r\n\r\n const {\r\n darkMode: initialDarkMode = false,\r\n fontSize: initialFontSize = 100,\r\n lineHeight: initialLineHeight = 1.6,\r\n letterSpacing: initialLetterSpacing = 0,\r\n extraContentCSS,\r\n onReady,\r\n onError,\r\n onProgress,\r\n onToc,\r\n onSearchProgress,\r\n onContentLoad,\r\n } = options\r\n\r\n let destroyed = false\r\n let toc: TocItem[] = []\r\n let fontSize = initialFontSize\r\n let darkMode = initialDarkMode\r\n let lineHeight = initialLineHeight\r\n let letterSpacing = initialLetterSpacing\r\n let searchToken = 0\r\n let activeDoc: Document | null = null\r\n let forceZoomEnabled = false\r\n\r\n container.innerHTML = ''\r\n\r\n const viewer = document.createElement('foliate-view') as FoliateViewElement\r\n viewer.style.display = 'block'\r\n viewer.style.width = '100%'\r\n viewer.style.height = '100%'\r\n viewer.setAttribute('margin', '48')\r\n viewer.setAttribute('gap', '0.07')\r\n\r\n const pickSampleEl = (doc: Document) => {\r\n const candidates = doc.querySelectorAll('p, li, blockquote, span, div')\r\n for (const el of candidates) {\r\n const text = el.textContent?.trim()\r\n if (!text) continue\r\n if (text.length < 24) continue\r\n return el as HTMLElement\r\n }\r\n return doc.body\r\n }\r\n\r\n const readFontSizePx = (doc: Document) => {\r\n const el = pickSampleEl(doc)\r\n if (!el) return null\r\n const px = Number.parseFloat(getComputedStyle(el).fontSize)\r\n return Number.isFinite(px) ? px : null\r\n }\r\n\r\n const applyForceZoomIfNeeded = () => {\r\n if (!forceZoomEnabled) return\r\n if (!activeDoc?.body) return\r\n activeDoc.body.setAttribute('data-epub-reader-force-zoom', 'true')\r\n }\r\n\r\n const applyStyles = (check?: { beforePx: number | null; beforeFontSize: number; afterFontSize: number }) => {\r\n if (destroyed) return\r\n if (!viewer.renderer?.setStyles) return\r\n\r\n applyForceZoomIfNeeded()\r\n\r\n viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS))\r\n requestAnimationFrame(() => {\r\n setTimeout(() => {\r\n if (destroyed) return\r\n viewer.renderer?.render?.()\r\n viewer.renderer?.expand?.()\r\n\r\n if (!check) return\r\n if (forceZoomEnabled) return\r\n if (!activeDoc) return\r\n\r\n const { beforePx, beforeFontSize, afterFontSize } = check\r\n const afterPx = readFontSizePx(activeDoc)\r\n if (beforePx == null || afterPx == null) return\r\n\r\n const isMeaningfulChange = Math.abs(afterFontSize - beforeFontSize) >= 10\r\n const isIneffective = Math.abs(afterPx - beforePx) < 0.5\r\n if (isMeaningfulChange && isIneffective) {\r\n forceZoomEnabled = true\r\n applyForceZoomIfNeeded()\r\n viewer.renderer?.setStyles?.(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS))\r\n viewer.renderer?.render?.()\r\n viewer.renderer?.expand?.()\r\n }\r\n }, 50)\r\n })\r\n }\r\n\r\n const handleLoad = (e: Event) => {\r\n const detail = (e as CustomEvent).detail as { doc?: Document } | undefined\r\n activeDoc = detail?.doc ?? null\r\n applyStyles()\r\n if (detail?.doc) onContentLoad?.(detail.doc)\r\n }\r\n\r\n const handleRelocate = (e: Event) => {\r\n const detail = (e as CustomEvent).detail as ProgressInfo\r\n onProgress?.(detail)\r\n }\r\n\r\n viewer.addEventListener('load', handleLoad as EventListener)\r\n viewer.addEventListener('relocate', handleRelocate as EventListener)\r\n\r\n container.appendChild(viewer)\r\n\r\n const handle: EBookReaderHandle = {\r\n async open(file) {\r\n if (destroyed) return\r\n if (!file) return\r\n\r\n try {\r\n viewer.clearSearch?.()\r\n searchToken++\r\n activeDoc = null\r\n forceZoomEnabled = false\r\n\r\n await viewer.open?.(file)\r\n\r\n const nextToc = viewer.book?.toc ?? []\r\n toc = nextToc\r\n onToc?.(toc)\r\n\r\n await viewer.init?.({ showTextStart: true })\r\n applyStyles()\r\n } catch (error) {\r\n onError?.(error)\r\n throw error\r\n }\r\n },\r\n destroy() {\r\n if (destroyed) return\r\n destroyed = true\r\n searchToken++\r\n viewer.removeEventListener('load', handleLoad)\r\n viewer.removeEventListener('relocate', handleRelocate as EventListener)\r\n container.innerHTML = ''\r\n },\r\n prevPage() {\r\n viewer.goLeft?.()\r\n },\r\n nextPage() {\r\n viewer.goRight?.()\r\n },\r\n prevSection() {\r\n viewer.renderer?.prevSection?.()\r\n },\r\n nextSection() {\r\n viewer.renderer?.nextSection?.()\r\n },\r\n goTo(target) {\r\n if (!target) return\r\n viewer.goTo?.(target)\r\n },\r\n goToFraction(fraction) {\r\n const safe = Math.min(1, Math.max(0, fraction))\r\n viewer.goToFraction?.(safe)\r\n },\r\n setDarkMode(nextDarkMode) {\r\n darkMode = nextDarkMode\r\n applyStyles()\r\n },\r\n setFontSize(nextFontSize) {\r\n const safe = Math.min(300, Math.max(50, nextFontSize))\r\n const beforeFontSize = fontSize\r\n const beforePx = activeDoc && !forceZoomEnabled ? readFontSizePx(activeDoc) : null\r\n fontSize = safe\r\n applyStyles({ beforePx, beforeFontSize, afterFontSize: safe })\r\n },\r\n setLineHeight(nextLineHeight) {\r\n const safe = Math.min(3, Math.max(1, nextLineHeight))\r\n lineHeight = safe\r\n applyStyles()\r\n },\r\n setLetterSpacing(nextLetterSpacing) {\r\n const safe = Math.min(0.3, Math.max(0, nextLetterSpacing))\r\n letterSpacing = safe\r\n applyStyles()\r\n },\r\n async search(query, opts: SearchOptions = {}) {\r\n const normalized = query.trim()\r\n if (!normalized) {\r\n viewer.clearSearch?.()\r\n return []\r\n }\r\n\r\n const token = ++searchToken\r\n const results: SearchResult[] = []\r\n\r\n try {\r\n for await (const item of viewer.search?.({\r\n query: normalized,\r\n matchCase: Boolean(opts.matchCase),\r\n matchWholeWords: Boolean(opts.wholeWords),\r\n matchDiacritics: Boolean(opts.matchDiacritics),\r\n }) ?? []) {\r\n if (destroyed || token !== searchToken) return results\r\n\r\n if (item === 'done') {\r\n onSearchProgress?.({ done: true, progress: 1 })\r\n break\r\n }\r\n\r\n if (typeof item === 'object' && item && 'progress' in (item as any)) {\r\n const progress = (item as any).progress\r\n if (typeof progress === 'number') onSearchProgress?.({ progress })\r\n continue\r\n }\r\n\r\n const anyItem = item as any\r\n if (anyItem?.subitems?.length) {\r\n for (const sub of anyItem.subitems) {\r\n results.push({\r\n label: anyItem.label,\r\n cfi: sub?.cfi,\r\n excerpt: sub?.excerpt,\r\n title: sub?.title,\r\n })\r\n }\r\n } else if (anyItem?.cfi) {\r\n results.push({\r\n cfi: anyItem.cfi,\r\n excerpt: anyItem.excerpt,\r\n title: anyItem.title,\r\n })\r\n }\r\n }\r\n } catch (error) {\r\n onError?.(error)\r\n throw error\r\n }\r\n\r\n return results\r\n },\r\n cancelSearch() {\r\n searchToken++\r\n },\r\n clearSearch() {\r\n viewer.clearSearch?.()\r\n },\r\n getToc() {\r\n return toc\r\n },\r\n }\r\n\r\n onReady?.(handle)\r\n return handle\r\n}\r\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -3,33 +3,52 @@
|
|
|
3
3
|
require('foliate-js/view.js');
|
|
4
4
|
|
|
5
5
|
// src/core/reader.ts
|
|
6
|
-
var getContentCSS = (fontSize, isDark, lineHeight, letterSpacing, extraCSS) =>
|
|
6
|
+
var getContentCSS = (fontSize, isDark, lineHeight, letterSpacing, extraCSS) => {
|
|
7
|
+
const scale = fontSize / 100;
|
|
8
|
+
return `
|
|
7
9
|
@namespace epub "http://www.idpf.org/2007/ops";
|
|
8
|
-
|
|
10
|
+
:root:root {
|
|
9
11
|
color-scheme: ${isDark ? "dark" : "light"} !important;
|
|
10
12
|
}
|
|
11
|
-
body {
|
|
13
|
+
:root:root body {
|
|
12
14
|
background-color: transparent !important;
|
|
13
15
|
color: ${isDark ? "#e0e0e0" : "black"} !important;
|
|
14
16
|
font-size: ${fontSize}% !important;
|
|
15
17
|
line-height: ${lineHeight} !important;
|
|
16
18
|
letter-spacing: ${letterSpacing}em !important;
|
|
19
|
+
-webkit-text-size-adjust: 100% !important;
|
|
20
|
+
text-size-adjust: 100% !important;
|
|
17
21
|
}
|
|
18
|
-
|
|
22
|
+
|
|
23
|
+
:root:root body :is(p, li, blockquote) {
|
|
19
24
|
line-height: inherit !important;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
:root:root body p {
|
|
20
28
|
margin-bottom: 1em;
|
|
21
29
|
}
|
|
22
|
-
|
|
30
|
+
|
|
31
|
+
:root:root body a {
|
|
23
32
|
color: ${isDark ? "#64b5f6" : "#2563eb"} !important;
|
|
24
33
|
}
|
|
25
|
-
|
|
34
|
+
|
|
35
|
+
:root:root body img {
|
|
26
36
|
max-width: 100%;
|
|
27
37
|
height: auto;
|
|
28
38
|
object-fit: contain;
|
|
29
39
|
${isDark ? "filter: brightness(0.8) contrast(1.2);" : ""}
|
|
30
40
|
}
|
|
41
|
+
|
|
42
|
+
@supports (zoom: 1) {
|
|
43
|
+
:root:root body[data-epub-reader-force-zoom='true'] {
|
|
44
|
+
zoom: ${scale};
|
|
45
|
+
font-size: 100% !important;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
31
49
|
${extraCSS ?? ""}
|
|
32
50
|
`;
|
|
51
|
+
};
|
|
33
52
|
function createEBookReader(container, options = {}) {
|
|
34
53
|
if (!container) throw new Error("container is required");
|
|
35
54
|
if (!customElements.get("foliate-view")) throw new Error("foliate-view is not defined");
|
|
@@ -53,6 +72,8 @@ function createEBookReader(container, options = {}) {
|
|
|
53
72
|
let lineHeight = initialLineHeight;
|
|
54
73
|
let letterSpacing = initialLetterSpacing;
|
|
55
74
|
let searchToken = 0;
|
|
75
|
+
let activeDoc = null;
|
|
76
|
+
let forceZoomEnabled = false;
|
|
56
77
|
container.innerHTML = "";
|
|
57
78
|
const viewer = document.createElement("foliate-view");
|
|
58
79
|
viewer.style.display = "block";
|
|
@@ -60,21 +81,59 @@ function createEBookReader(container, options = {}) {
|
|
|
60
81
|
viewer.style.height = "100%";
|
|
61
82
|
viewer.setAttribute("margin", "48");
|
|
62
83
|
viewer.setAttribute("gap", "0.07");
|
|
63
|
-
const
|
|
84
|
+
const pickSampleEl = (doc) => {
|
|
85
|
+
const candidates = doc.querySelectorAll("p, li, blockquote, span, div");
|
|
86
|
+
for (const el of candidates) {
|
|
87
|
+
const text = el.textContent?.trim();
|
|
88
|
+
if (!text) continue;
|
|
89
|
+
if (text.length < 24) continue;
|
|
90
|
+
return el;
|
|
91
|
+
}
|
|
92
|
+
return doc.body;
|
|
93
|
+
};
|
|
94
|
+
const readFontSizePx = (doc) => {
|
|
95
|
+
const el = pickSampleEl(doc);
|
|
96
|
+
if (!el) return null;
|
|
97
|
+
const px = Number.parseFloat(getComputedStyle(el).fontSize);
|
|
98
|
+
return Number.isFinite(px) ? px : null;
|
|
99
|
+
};
|
|
100
|
+
const applyForceZoomIfNeeded = () => {
|
|
101
|
+
if (!forceZoomEnabled) return;
|
|
102
|
+
if (!activeDoc?.body) return;
|
|
103
|
+
activeDoc.body.setAttribute("data-epub-reader-force-zoom", "true");
|
|
104
|
+
};
|
|
105
|
+
const applyStyles = (check) => {
|
|
64
106
|
if (destroyed) return;
|
|
65
107
|
if (!viewer.renderer?.setStyles) return;
|
|
108
|
+
applyForceZoomIfNeeded();
|
|
66
109
|
viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS));
|
|
67
110
|
requestAnimationFrame(() => {
|
|
68
111
|
setTimeout(() => {
|
|
69
112
|
if (destroyed) return;
|
|
70
113
|
viewer.renderer?.render?.();
|
|
71
114
|
viewer.renderer?.expand?.();
|
|
115
|
+
if (!check) return;
|
|
116
|
+
if (forceZoomEnabled) return;
|
|
117
|
+
if (!activeDoc) return;
|
|
118
|
+
const { beforePx, beforeFontSize, afterFontSize } = check;
|
|
119
|
+
const afterPx = readFontSizePx(activeDoc);
|
|
120
|
+
if (beforePx == null || afterPx == null) return;
|
|
121
|
+
const isMeaningfulChange = Math.abs(afterFontSize - beforeFontSize) >= 10;
|
|
122
|
+
const isIneffective = Math.abs(afterPx - beforePx) < 0.5;
|
|
123
|
+
if (isMeaningfulChange && isIneffective) {
|
|
124
|
+
forceZoomEnabled = true;
|
|
125
|
+
applyForceZoomIfNeeded();
|
|
126
|
+
viewer.renderer?.setStyles?.(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS));
|
|
127
|
+
viewer.renderer?.render?.();
|
|
128
|
+
viewer.renderer?.expand?.();
|
|
129
|
+
}
|
|
72
130
|
}, 50);
|
|
73
131
|
});
|
|
74
132
|
};
|
|
75
133
|
const handleLoad = (e) => {
|
|
76
|
-
applyStyles();
|
|
77
134
|
const detail = e.detail;
|
|
135
|
+
activeDoc = detail?.doc ?? null;
|
|
136
|
+
applyStyles();
|
|
78
137
|
if (detail?.doc) onContentLoad?.(detail.doc);
|
|
79
138
|
};
|
|
80
139
|
const handleRelocate = (e) => {
|
|
@@ -91,6 +150,8 @@ function createEBookReader(container, options = {}) {
|
|
|
91
150
|
try {
|
|
92
151
|
viewer.clearSearch?.();
|
|
93
152
|
searchToken++;
|
|
153
|
+
activeDoc = null;
|
|
154
|
+
forceZoomEnabled = false;
|
|
94
155
|
await viewer.open?.(file);
|
|
95
156
|
const nextToc = viewer.book?.toc ?? [];
|
|
96
157
|
toc = nextToc;
|
|
@@ -136,8 +197,10 @@ function createEBookReader(container, options = {}) {
|
|
|
136
197
|
},
|
|
137
198
|
setFontSize(nextFontSize) {
|
|
138
199
|
const safe = Math.min(300, Math.max(50, nextFontSize));
|
|
200
|
+
const beforeFontSize = fontSize;
|
|
201
|
+
const beforePx = activeDoc && !forceZoomEnabled ? readFontSizePx(activeDoc) : null;
|
|
139
202
|
fontSize = safe;
|
|
140
|
-
applyStyles();
|
|
203
|
+
applyStyles({ beforePx, beforeFontSize, afterFontSize: safe });
|
|
141
204
|
},
|
|
142
205
|
setLineHeight(nextLineHeight) {
|
|
143
206
|
const safe = Math.min(3, Math.max(1, nextLineHeight));
|