@somecat/epub-reader 0.1.0

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/index.cjs ADDED
@@ -0,0 +1,201 @@
1
+ 'use strict';
2
+
3
+ require('foliate-js/view.js');
4
+
5
+ // src/core/reader.ts
6
+ var getContentCSS = (fontSize, isDark, extraCSS) => `
7
+ @namespace epub "http://www.idpf.org/2007/ops";
8
+ html {
9
+ color-scheme: ${isDark ? "dark" : "light"} !important;
10
+ }
11
+ body {
12
+ background-color: transparent !important;
13
+ color: ${isDark ? "#e0e0e0" : "black"} !important;
14
+ font-size: ${fontSize}% !important;
15
+ }
16
+ p {
17
+ line-height: 1.6;
18
+ margin-bottom: 1em;
19
+ }
20
+ a {
21
+ color: ${isDark ? "#64b5f6" : "#2563eb"} !important;
22
+ }
23
+ img {
24
+ max-width: 100%;
25
+ height: auto;
26
+ object-fit: contain;
27
+ ${isDark ? "filter: brightness(0.8) contrast(1.2);" : ""}
28
+ }
29
+ ${extraCSS ?? ""}
30
+ `;
31
+ function createEBookReader(container, options = {}) {
32
+ if (!container) throw new Error("container is required");
33
+ if (!customElements.get("foliate-view")) throw new Error("foliate-view is not defined");
34
+ const {
35
+ darkMode: initialDarkMode = false,
36
+ fontSize: initialFontSize = 100,
37
+ extraContentCSS,
38
+ onReady,
39
+ onError,
40
+ onProgress,
41
+ onToc,
42
+ onSearchProgress,
43
+ onContentLoad
44
+ } = options;
45
+ let destroyed = false;
46
+ let toc = [];
47
+ let fontSize = initialFontSize;
48
+ let darkMode = initialDarkMode;
49
+ let searchToken = 0;
50
+ container.innerHTML = "";
51
+ const viewer = document.createElement("foliate-view");
52
+ viewer.style.display = "block";
53
+ viewer.style.width = "100%";
54
+ viewer.style.height = "100%";
55
+ viewer.setAttribute("margin", "48");
56
+ viewer.setAttribute("gap", "0.07");
57
+ const applyStyles = () => {
58
+ if (destroyed) return;
59
+ if (!viewer.renderer?.setStyles) return;
60
+ viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, extraContentCSS));
61
+ requestAnimationFrame(() => {
62
+ setTimeout(() => {
63
+ if (destroyed) return;
64
+ viewer.renderer?.render?.();
65
+ viewer.renderer?.expand?.();
66
+ }, 50);
67
+ });
68
+ };
69
+ const handleLoad = (e) => {
70
+ applyStyles();
71
+ const detail = e.detail;
72
+ if (detail?.doc) onContentLoad?.(detail.doc);
73
+ };
74
+ const handleRelocate = (e) => {
75
+ const detail = e.detail;
76
+ onProgress?.(detail);
77
+ };
78
+ viewer.addEventListener("load", handleLoad);
79
+ viewer.addEventListener("relocate", handleRelocate);
80
+ container.appendChild(viewer);
81
+ const handle = {
82
+ async open(file) {
83
+ if (destroyed) return;
84
+ if (!file) return;
85
+ try {
86
+ viewer.clearSearch?.();
87
+ searchToken++;
88
+ await viewer.open?.(file);
89
+ const nextToc = viewer.book?.toc ?? [];
90
+ toc = nextToc;
91
+ onToc?.(toc);
92
+ await viewer.init?.({ showTextStart: true });
93
+ applyStyles();
94
+ } catch (error) {
95
+ onError?.(error);
96
+ throw error;
97
+ }
98
+ },
99
+ destroy() {
100
+ if (destroyed) return;
101
+ destroyed = true;
102
+ searchToken++;
103
+ viewer.removeEventListener("load", handleLoad);
104
+ viewer.removeEventListener("relocate", handleRelocate);
105
+ container.innerHTML = "";
106
+ },
107
+ prevPage() {
108
+ viewer.goLeft?.();
109
+ },
110
+ nextPage() {
111
+ viewer.goRight?.();
112
+ },
113
+ prevSection() {
114
+ viewer.renderer?.prevSection?.();
115
+ },
116
+ nextSection() {
117
+ viewer.renderer?.nextSection?.();
118
+ },
119
+ goTo(target) {
120
+ if (!target) return;
121
+ viewer.goTo?.(target);
122
+ },
123
+ goToFraction(fraction) {
124
+ const safe = Math.min(1, Math.max(0, fraction));
125
+ viewer.goToFraction?.(safe);
126
+ },
127
+ setDarkMode(nextDarkMode) {
128
+ darkMode = nextDarkMode;
129
+ applyStyles();
130
+ },
131
+ setFontSize(nextFontSize) {
132
+ const safe = Math.min(300, Math.max(50, nextFontSize));
133
+ fontSize = safe;
134
+ applyStyles();
135
+ },
136
+ async search(query, opts = {}) {
137
+ const normalized = query.trim();
138
+ if (!normalized) {
139
+ viewer.clearSearch?.();
140
+ return [];
141
+ }
142
+ const token = ++searchToken;
143
+ const results = [];
144
+ try {
145
+ for await (const item of viewer.search?.({
146
+ query: normalized,
147
+ matchCase: Boolean(opts.matchCase),
148
+ matchWholeWords: Boolean(opts.wholeWords),
149
+ matchDiacritics: Boolean(opts.matchDiacritics)
150
+ }) ?? []) {
151
+ if (destroyed || token !== searchToken) return results;
152
+ if (item === "done") {
153
+ onSearchProgress?.({ done: true, progress: 1 });
154
+ break;
155
+ }
156
+ if (typeof item === "object" && item && "progress" in item) {
157
+ const progress = item.progress;
158
+ if (typeof progress === "number") onSearchProgress?.({ progress });
159
+ continue;
160
+ }
161
+ const anyItem = item;
162
+ if (anyItem?.subitems?.length) {
163
+ for (const sub of anyItem.subitems) {
164
+ results.push({
165
+ label: anyItem.label,
166
+ cfi: sub?.cfi,
167
+ excerpt: sub?.excerpt,
168
+ title: sub?.title
169
+ });
170
+ }
171
+ } else if (anyItem?.cfi) {
172
+ results.push({
173
+ cfi: anyItem.cfi,
174
+ excerpt: anyItem.excerpt,
175
+ title: anyItem.title
176
+ });
177
+ }
178
+ }
179
+ } catch (error) {
180
+ onError?.(error);
181
+ throw error;
182
+ }
183
+ return results;
184
+ },
185
+ cancelSearch() {
186
+ searchToken++;
187
+ },
188
+ clearSearch() {
189
+ viewer.clearSearch?.();
190
+ },
191
+ getToc() {
192
+ return toc;
193
+ }
194
+ };
195
+ onReady?.(handle);
196
+ return handle;
197
+ }
198
+
199
+ exports.createEBookReader = createEBookReader;
200
+ //# sourceMappingURL=index.cjs.map
201
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/reader.ts"],"names":[],"mappings":"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","file":"index.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, extraCSS?: string) => `\r\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}\r\np {\r\n line-height: 1.6;\r\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 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 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, extraContentCSS))\r\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 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"]}
@@ -0,0 +1,2 @@
1
+ export { E as EBookReaderHandle, a as EBookReaderOptions, P as ProgressInfo, S as SearchOptions, b as SearchResult, T as TocItem } from './types-Dc66KVH2.cjs';
2
+ export { createEBookReader } from './core.cjs';
@@ -0,0 +1,2 @@
1
+ export { E as EBookReaderHandle, a as EBookReaderOptions, P as ProgressInfo, S as SearchOptions, b as SearchResult, T as TocItem } from './types-Dc66KVH2.js';
2
+ export { createEBookReader } from './core.js';
package/dist/index.js ADDED
@@ -0,0 +1,199 @@
1
+ import 'foliate-js/view.js';
2
+
3
+ // src/core/reader.ts
4
+ var getContentCSS = (fontSize, isDark, extraCSS) => `
5
+ @namespace epub "http://www.idpf.org/2007/ops";
6
+ html {
7
+ color-scheme: ${isDark ? "dark" : "light"} !important;
8
+ }
9
+ body {
10
+ background-color: transparent !important;
11
+ color: ${isDark ? "#e0e0e0" : "black"} !important;
12
+ font-size: ${fontSize}% !important;
13
+ }
14
+ p {
15
+ line-height: 1.6;
16
+ margin-bottom: 1em;
17
+ }
18
+ a {
19
+ color: ${isDark ? "#64b5f6" : "#2563eb"} !important;
20
+ }
21
+ img {
22
+ max-width: 100%;
23
+ height: auto;
24
+ object-fit: contain;
25
+ ${isDark ? "filter: brightness(0.8) contrast(1.2);" : ""}
26
+ }
27
+ ${extraCSS ?? ""}
28
+ `;
29
+ function createEBookReader(container, options = {}) {
30
+ if (!container) throw new Error("container is required");
31
+ if (!customElements.get("foliate-view")) throw new Error("foliate-view is not defined");
32
+ const {
33
+ darkMode: initialDarkMode = false,
34
+ fontSize: initialFontSize = 100,
35
+ extraContentCSS,
36
+ onReady,
37
+ onError,
38
+ onProgress,
39
+ onToc,
40
+ onSearchProgress,
41
+ onContentLoad
42
+ } = options;
43
+ let destroyed = false;
44
+ let toc = [];
45
+ let fontSize = initialFontSize;
46
+ let darkMode = initialDarkMode;
47
+ let searchToken = 0;
48
+ container.innerHTML = "";
49
+ const viewer = document.createElement("foliate-view");
50
+ viewer.style.display = "block";
51
+ viewer.style.width = "100%";
52
+ viewer.style.height = "100%";
53
+ viewer.setAttribute("margin", "48");
54
+ viewer.setAttribute("gap", "0.07");
55
+ const applyStyles = () => {
56
+ if (destroyed) return;
57
+ if (!viewer.renderer?.setStyles) return;
58
+ viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, extraContentCSS));
59
+ requestAnimationFrame(() => {
60
+ setTimeout(() => {
61
+ if (destroyed) return;
62
+ viewer.renderer?.render?.();
63
+ viewer.renderer?.expand?.();
64
+ }, 50);
65
+ });
66
+ };
67
+ const handleLoad = (e) => {
68
+ applyStyles();
69
+ const detail = e.detail;
70
+ if (detail?.doc) onContentLoad?.(detail.doc);
71
+ };
72
+ const handleRelocate = (e) => {
73
+ const detail = e.detail;
74
+ onProgress?.(detail);
75
+ };
76
+ viewer.addEventListener("load", handleLoad);
77
+ viewer.addEventListener("relocate", handleRelocate);
78
+ container.appendChild(viewer);
79
+ const handle = {
80
+ async open(file) {
81
+ if (destroyed) return;
82
+ if (!file) return;
83
+ try {
84
+ viewer.clearSearch?.();
85
+ searchToken++;
86
+ await viewer.open?.(file);
87
+ const nextToc = viewer.book?.toc ?? [];
88
+ toc = nextToc;
89
+ onToc?.(toc);
90
+ await viewer.init?.({ showTextStart: true });
91
+ applyStyles();
92
+ } catch (error) {
93
+ onError?.(error);
94
+ throw error;
95
+ }
96
+ },
97
+ destroy() {
98
+ if (destroyed) return;
99
+ destroyed = true;
100
+ searchToken++;
101
+ viewer.removeEventListener("load", handleLoad);
102
+ viewer.removeEventListener("relocate", handleRelocate);
103
+ container.innerHTML = "";
104
+ },
105
+ prevPage() {
106
+ viewer.goLeft?.();
107
+ },
108
+ nextPage() {
109
+ viewer.goRight?.();
110
+ },
111
+ prevSection() {
112
+ viewer.renderer?.prevSection?.();
113
+ },
114
+ nextSection() {
115
+ viewer.renderer?.nextSection?.();
116
+ },
117
+ goTo(target) {
118
+ if (!target) return;
119
+ viewer.goTo?.(target);
120
+ },
121
+ goToFraction(fraction) {
122
+ const safe = Math.min(1, Math.max(0, fraction));
123
+ viewer.goToFraction?.(safe);
124
+ },
125
+ setDarkMode(nextDarkMode) {
126
+ darkMode = nextDarkMode;
127
+ applyStyles();
128
+ },
129
+ setFontSize(nextFontSize) {
130
+ const safe = Math.min(300, Math.max(50, nextFontSize));
131
+ fontSize = safe;
132
+ applyStyles();
133
+ },
134
+ async search(query, opts = {}) {
135
+ const normalized = query.trim();
136
+ if (!normalized) {
137
+ viewer.clearSearch?.();
138
+ return [];
139
+ }
140
+ const token = ++searchToken;
141
+ const results = [];
142
+ try {
143
+ for await (const item of viewer.search?.({
144
+ query: normalized,
145
+ matchCase: Boolean(opts.matchCase),
146
+ matchWholeWords: Boolean(opts.wholeWords),
147
+ matchDiacritics: Boolean(opts.matchDiacritics)
148
+ }) ?? []) {
149
+ if (destroyed || token !== searchToken) return results;
150
+ if (item === "done") {
151
+ onSearchProgress?.({ done: true, progress: 1 });
152
+ break;
153
+ }
154
+ if (typeof item === "object" && item && "progress" in item) {
155
+ const progress = item.progress;
156
+ if (typeof progress === "number") onSearchProgress?.({ progress });
157
+ continue;
158
+ }
159
+ const anyItem = item;
160
+ if (anyItem?.subitems?.length) {
161
+ for (const sub of anyItem.subitems) {
162
+ results.push({
163
+ label: anyItem.label,
164
+ cfi: sub?.cfi,
165
+ excerpt: sub?.excerpt,
166
+ title: sub?.title
167
+ });
168
+ }
169
+ } else if (anyItem?.cfi) {
170
+ results.push({
171
+ cfi: anyItem.cfi,
172
+ excerpt: anyItem.excerpt,
173
+ title: anyItem.title
174
+ });
175
+ }
176
+ }
177
+ } catch (error) {
178
+ onError?.(error);
179
+ throw error;
180
+ }
181
+ return results;
182
+ },
183
+ cancelSearch() {
184
+ searchToken++;
185
+ },
186
+ clearSearch() {
187
+ viewer.clearSearch?.();
188
+ },
189
+ getToc() {
190
+ return toc;
191
+ }
192
+ };
193
+ onReady?.(handle);
194
+ return handle;
195
+ }
196
+
197
+ export { createEBookReader };
198
+ //# sourceMappingURL=index.js.map
199
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/reader.ts"],"names":[],"mappings":"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","file":"index.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, extraCSS?: string) => `\r\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}\r\np {\r\n line-height: 1.6;\r\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 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 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, extraContentCSS))\r\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 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"]}