@hyebook/vue3-adapter 2.3.9 → 2.3.11
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/src/player/ebook-player.d.ts +33 -0
- package/dist/core/src/player/ebook-player.d.ts.map +1 -1
- package/dist/core/src/player/ebook-player.js +402 -50
- package/dist/core/src/player/engine.d.ts +2 -1
- package/dist/core/src/player/engine.d.ts.map +1 -1
- package/dist/core/src/player/engine.js +29 -0
- package/dist/core/src/types/player.d.ts +5 -0
- package/dist/core/src/types/player.d.ts.map +1 -1
- package/dist/core/src/workbench/editor-workbench.d.ts +4 -0
- package/dist/core/src/workbench/editor-workbench.d.ts.map +1 -1
- package/dist/core/src/workbench/editor-workbench.js +376 -86
- package/package.json +1 -1
|
@@ -1,13 +1,40 @@
|
|
|
1
1
|
import { PlayerEngine } from "./engine";
|
|
2
2
|
const EBOOK_PLAYER_STYLE_ID = "hy-ebook-lite-player-style";
|
|
3
|
-
const EBOOK_PLAYER_STYLE_VERSION = "0.2.
|
|
3
|
+
const EBOOK_PLAYER_STYLE_VERSION = "0.2.2";
|
|
4
|
+
const JUMP_SCROLL_FOCUS_DELAY_MS = 360;
|
|
5
|
+
const JUMP_FOCUS_ANIMATION_MS = 560;
|
|
6
|
+
const BOOKMARK_JUMP_REVEAL_TOP_OFFSET_PX = 28;
|
|
4
7
|
const HIGHLIGHT_ICON_SVG = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 22V12.5H6V8.5H18V12.5H21V22H3Z" stroke="#333333" stroke-width="2" stroke-linejoin="round"/><path d="M8.5 8.5V4L15.5 2V8.5" stroke="#333333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
|
5
8
|
const NOTE_ICON_SVG = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M15.4998 4.49951L19.4998 8.4995" stroke="#333333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M3.99977 15.9995L17.9997 2L21.9998 5.9995L7.99975 19.9995L2.99976 20.9995L3.99977 15.9995Z" stroke="#333333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M4.49976 15.9995L7.99975 19.4995" stroke="#333333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M6.49976 17.4995L17.4998 6.4995" stroke="#333333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
|
6
9
|
const BOOKMARK_ICON_SVG = `<svg width="30" height="40" viewBox="0 0 30 40" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0 3C0 1.34315 1.34315 0 3 0H27C28.6569 0 30 1.34315 30 3V38.1989C30 38.9837 29.1374 39.4627 28.4713 39.0477L15 30.6562L1.52873 39.0477C0.862627 39.4627 0 38.9837 0 38.1989L0 3Z" fill="#4FCEBB"/></svg>`;
|
|
10
|
+
const BACK_TO_TOP_ICON_SVG = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 18V6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M7 11L12 6L17 11" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
|
7
11
|
const EBOOK_PLAYER_CSS = `
|
|
8
12
|
.hyepl-root{position:relative;max-width:100%;margin:0 auto;color:#0f172a;font-size:14px;line-height:1.7}
|
|
9
13
|
.hyepl-root ::selection{background:#7DE2D3}
|
|
10
14
|
.hyepl-root ::-moz-selection{background:#7DE2D3}
|
|
15
|
+
.hyepl-load-warning{position:absolute;top:12px;right:12px;z-index:10080;max-width:min(420px,calc(100% - 24px));display:none;border:1px solid #f5c2c7;background:#fff5f5;color:#7f1d1d;border-radius:10px;box-shadow:0 10px 24px rgba(15,23,42,.12);padding:8px 10px}
|
|
16
|
+
.hyepl-load-warning.show{display:block}
|
|
17
|
+
.hyepl-load-warning-header{display:flex;align-items:center;gap:8px}
|
|
18
|
+
.hyepl-load-warning-title{font-size:12px;font-weight:600;line-height:1.4}
|
|
19
|
+
.hyepl-load-warning-actions{margin-left:auto;display:inline-flex;align-items:center;gap:6px}
|
|
20
|
+
.hyepl-load-warning-btn{border:1px solid #f1b3b3;background:#fff;color:#7f1d1d;border-radius:6px;height:24px;padding:0 8px;font-size:12px;line-height:1;cursor:pointer}
|
|
21
|
+
.hyepl-load-warning-close{width:24px;padding:0}
|
|
22
|
+
.hyepl-load-warning-message{margin:8px 0 0;font-size:12px;line-height:1.5;color:#7f1d1d;white-space:pre-wrap;word-break:break-word;display:none;max-height:180px;overflow:auto}
|
|
23
|
+
.hyepl-load-warning.expanded .hyepl-load-warning-message{display:block}
|
|
24
|
+
.hyepl-input-backdrop{position:fixed;inset:0;display:none;align-items:center;justify-content:center;background:rgba(15,23,42,.45);z-index:10090;padding:16px}
|
|
25
|
+
.hyepl-input-backdrop.show{display:flex}
|
|
26
|
+
.hyepl-input-dialog{width:min(520px,100%);background:#fff;border:1px solid #cbd5e1;border-radius:12px;padding:14px;display:grid;gap:10px;box-shadow:0 16px 34px rgba(15,23,42,.28)}
|
|
27
|
+
.hyepl-input-title{margin:0;font-size:14px;font-weight:600;color:#0f172a}
|
|
28
|
+
.hyepl-input-textarea{width:100%;min-height:120px;resize:vertical;border:1px solid #94a3b8;border-radius:8px;padding:8px 10px;background:#fff;color:#0f172a;box-sizing:border-box;font-family:inherit;font-size:14px;line-height:1.5}
|
|
29
|
+
.hyepl-input-error{min-height:18px;margin:0;color:#b91c1c;font-size:12px}
|
|
30
|
+
.hyepl-input-actions{display:flex;justify-content:flex-end;gap:8px}
|
|
31
|
+
.hyepl-input-btn{height:34px;padding:0 12px;border-radius:8px;border:1px solid #94a3b8;background:#fff;color:#0f172a;cursor:pointer}
|
|
32
|
+
.hyepl-input-btn.primary{border-color:#0b7285;background:#0b7285;color:#fff}
|
|
33
|
+
.hyepl-back-to-top{position:absolute;right:16px;bottom:16px;z-index:10060;width:52px;height:52px;border:1px solid #d5dde8;border-radius:999px;background:#ffffff;color:#0f172a;box-shadow:0 10px 28px rgba(15,23,42,.2);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:background .2s ease,color .2s ease,border-color .2s ease}
|
|
34
|
+
.hyepl-back-to-top svg{width:22px;height:22px}
|
|
35
|
+
.hyepl-back-to-top:hover{background:#0f172a;color:#ffffff;border-color:#0f172a}
|
|
36
|
+
.hyepl-back-to-top:focus-visible{outline:2px solid #38bdf8;outline-offset:2px}
|
|
37
|
+
.hyepl-back-to-top.hidden{display:none}
|
|
11
38
|
.hyepl-content{position:relative}
|
|
12
39
|
.hyepl-page{margin:0 0 16px}
|
|
13
40
|
.hyepl-page:last-child{margin-bottom:0}
|
|
@@ -16,6 +43,7 @@ const EBOOK_PLAYER_CSS = `
|
|
|
16
43
|
.hyepl-block-text{position:relative;padding-right:18px;white-space:normal}
|
|
17
44
|
.hyepl-block-text p,.hyepl-block-text h1,.hyepl-block-text h2,.hyepl-block-text h3,.hyepl-block-text h4,.hyepl-block-text blockquote{margin:0 0 .75em;line-height:1.7}
|
|
18
45
|
.hyepl-block-text p:last-child,.hyepl-block-text h1:last-child,.hyepl-block-text h2:last-child,.hyepl-block-text h3:last-child,.hyepl-block-text h4:last-child,.hyepl-block-text blockquote:last-child{margin-bottom:0}
|
|
46
|
+
.hyepl-block-text em,.hyepl-block-text i,.hyepl-block-table em,.hyepl-block-table i{font-style:italic}
|
|
19
47
|
.hyepl-block-text h1{font-size:24px;line-height:1.3}
|
|
20
48
|
.hyepl-block-text h2{font-size:20px;line-height:1.35}
|
|
21
49
|
.hyepl-block-text h3{font-size:16px;line-height:1.45}
|
|
@@ -48,10 +76,31 @@ const EBOOK_PLAYER_CSS = `
|
|
|
48
76
|
.hyepl-toolbar-icon{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px}
|
|
49
77
|
.hyepl-toolbar-icon svg{display:block;width:24px;height:24px}
|
|
50
78
|
.hyepl-toolbar-text{font-size:14px;font-weight:700;color:#111827}
|
|
79
|
+
|
|
80
|
+
.hyepl-jump-focus{animation:hyepl-jump-focus 560ms ease-out}
|
|
81
|
+
.hyepl-jump-focus-mark{animation:hyepl-jump-focus-mark 560ms ease-out}
|
|
82
|
+
|
|
83
|
+
@keyframes hyepl-jump-focus{
|
|
84
|
+
0%{filter:none;box-shadow:0 0 0 0 rgba(125,226,211,0)}
|
|
85
|
+
35%{filter:brightness(1.08);box-shadow:0 0 0 4px rgba(125,226,211,.24)}
|
|
86
|
+
100%{filter:none;box-shadow:0 0 0 0 rgba(125,226,211,0)}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@keyframes hyepl-jump-focus-mark{
|
|
90
|
+
0%{opacity:1;filter:none}
|
|
91
|
+
35%{opacity:.72;filter:brightness(1.2)}
|
|
92
|
+
100%{opacity:1;filter:none}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@media (prefers-reduced-motion:reduce){
|
|
96
|
+
.hyepl-jump-focus,.hyepl-jump-focus-mark{animation:none}
|
|
97
|
+
}
|
|
51
98
|
`;
|
|
52
99
|
export class EBookPlayer {
|
|
53
100
|
constructor(container, options) {
|
|
54
101
|
this.runtimeDocument = null;
|
|
102
|
+
this.pendingTextInputResolver = null;
|
|
103
|
+
this.pendingTextInputMaxLength = 0;
|
|
55
104
|
this.selectionDraft = null;
|
|
56
105
|
this.loaded = false;
|
|
57
106
|
this.destroyed = false;
|
|
@@ -60,6 +109,7 @@ export class EBookPlayer {
|
|
|
60
109
|
};
|
|
61
110
|
this.handleSelectionRepositionBound = () => {
|
|
62
111
|
this.updateSelectionToolbarPosition();
|
|
112
|
+
this.updateBackToTopButtonPosition();
|
|
63
113
|
};
|
|
64
114
|
this.handleDocumentPointerDownBound = (event) => {
|
|
65
115
|
this.handleDocumentPointerDown(event);
|
|
@@ -73,6 +123,37 @@ export class EBookPlayer {
|
|
|
73
123
|
this.handleNoteClickBound = () => {
|
|
74
124
|
void this.handleNoteClick();
|
|
75
125
|
};
|
|
126
|
+
this.handleLoadWarningToggleBound = () => {
|
|
127
|
+
this.toggleLoadWarning();
|
|
128
|
+
};
|
|
129
|
+
this.handleLoadWarningCloseBound = () => {
|
|
130
|
+
this.hideLoadWarning();
|
|
131
|
+
};
|
|
132
|
+
this.handleTextInputCancelBound = () => {
|
|
133
|
+
this.finishTextInput(null);
|
|
134
|
+
};
|
|
135
|
+
this.handleTextInputConfirmBound = () => {
|
|
136
|
+
this.confirmTextInput();
|
|
137
|
+
};
|
|
138
|
+
this.handleTextInputBackdropPointerDownBound = (event) => {
|
|
139
|
+
if (event.target === this.textInputBackdrop) {
|
|
140
|
+
this.finishTextInput(null);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
this.handleTextInputKeyDownBound = (event) => {
|
|
144
|
+
if (event.key === "Escape") {
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
this.finishTextInput(null);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
|
|
150
|
+
event.preventDefault();
|
|
151
|
+
this.confirmTextInput();
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
this.handleBackToTopClickBound = () => {
|
|
155
|
+
this.scrollToStoredPosition(0, "smooth");
|
|
156
|
+
};
|
|
76
157
|
this.container = container;
|
|
77
158
|
this.options = options;
|
|
78
159
|
const runtimeProvider = createRuntimeProvider(options.provider, () => this.runtimeDocument);
|
|
@@ -87,6 +168,60 @@ export class EBookPlayer {
|
|
|
87
168
|
this.content.className = "hyepl-content";
|
|
88
169
|
this.selectionToolbar = document.createElement("div");
|
|
89
170
|
this.selectionToolbar.className = "hyepl-selection-toolbar";
|
|
171
|
+
this.loadWarning = document.createElement("div");
|
|
172
|
+
this.loadWarning.className = "hyepl-load-warning";
|
|
173
|
+
const loadWarningHeader = document.createElement("div");
|
|
174
|
+
loadWarningHeader.className = "hyepl-load-warning-header";
|
|
175
|
+
const loadWarningTitle = document.createElement("span");
|
|
176
|
+
loadWarningTitle.className = "hyepl-load-warning-title";
|
|
177
|
+
loadWarningTitle.textContent = "加载数据异常";
|
|
178
|
+
const loadWarningActions = document.createElement("div");
|
|
179
|
+
loadWarningActions.className = "hyepl-load-warning-actions";
|
|
180
|
+
this.loadWarningToggleBtn = document.createElement("button");
|
|
181
|
+
this.loadWarningToggleBtn.type = "button";
|
|
182
|
+
this.loadWarningToggleBtn.className = "hyepl-load-warning-btn";
|
|
183
|
+
this.loadWarningCloseBtn = document.createElement("button");
|
|
184
|
+
this.loadWarningCloseBtn.type = "button";
|
|
185
|
+
this.loadWarningCloseBtn.className =
|
|
186
|
+
"hyepl-load-warning-btn hyepl-load-warning-close";
|
|
187
|
+
this.loadWarningCloseBtn.textContent = "×";
|
|
188
|
+
this.loadWarningCloseBtn.title = "关闭";
|
|
189
|
+
this.loadWarningCloseBtn.setAttribute("aria-label", "关闭加载异常提示");
|
|
190
|
+
this.loadWarningMessage = document.createElement("pre");
|
|
191
|
+
this.loadWarningMessage.className = "hyepl-load-warning-message";
|
|
192
|
+
loadWarningActions.append(this.loadWarningToggleBtn, this.loadWarningCloseBtn);
|
|
193
|
+
loadWarningHeader.append(loadWarningTitle, loadWarningActions);
|
|
194
|
+
this.loadWarning.append(loadWarningHeader, this.loadWarningMessage);
|
|
195
|
+
this.setLoadWarningExpanded(false);
|
|
196
|
+
this.textInputBackdrop = document.createElement("div");
|
|
197
|
+
this.textInputBackdrop.className = "hyepl-input-backdrop";
|
|
198
|
+
const textInputDialog = document.createElement("div");
|
|
199
|
+
textInputDialog.className = "hyepl-input-dialog";
|
|
200
|
+
this.textInputTitle = document.createElement("h4");
|
|
201
|
+
this.textInputTitle.className = "hyepl-input-title";
|
|
202
|
+
this.textInputTextarea = document.createElement("textarea");
|
|
203
|
+
this.textInputTextarea.className = "hyepl-input-textarea";
|
|
204
|
+
this.textInputError = document.createElement("p");
|
|
205
|
+
this.textInputError.className = "hyepl-input-error";
|
|
206
|
+
const textInputActions = document.createElement("div");
|
|
207
|
+
textInputActions.className = "hyepl-input-actions";
|
|
208
|
+
this.textInputCancelBtn = document.createElement("button");
|
|
209
|
+
this.textInputCancelBtn.type = "button";
|
|
210
|
+
this.textInputCancelBtn.className = "hyepl-input-btn";
|
|
211
|
+
this.textInputCancelBtn.textContent = "取消";
|
|
212
|
+
this.textInputConfirmBtn = document.createElement("button");
|
|
213
|
+
this.textInputConfirmBtn.type = "button";
|
|
214
|
+
this.textInputConfirmBtn.className = "hyepl-input-btn primary";
|
|
215
|
+
this.textInputConfirmBtn.textContent = "确认";
|
|
216
|
+
textInputActions.append(this.textInputCancelBtn, this.textInputConfirmBtn);
|
|
217
|
+
textInputDialog.append(this.textInputTitle, this.textInputTextarea, this.textInputError, textInputActions);
|
|
218
|
+
this.textInputBackdrop.append(textInputDialog);
|
|
219
|
+
this.backToTopBtn = document.createElement("button");
|
|
220
|
+
this.backToTopBtn.type = "button";
|
|
221
|
+
this.backToTopBtn.className = "hyepl-back-to-top";
|
|
222
|
+
this.backToTopBtn.innerHTML = BACK_TO_TOP_ICON_SVG;
|
|
223
|
+
this.backToTopBtn.title = "回到顶部";
|
|
224
|
+
this.backToTopBtn.setAttribute("aria-label", "回到顶部");
|
|
90
225
|
this.highlightBtn = document.createElement("button");
|
|
91
226
|
this.highlightBtn.type = "button";
|
|
92
227
|
this.highlightBtn.className = "hyepl-toolbar-btn";
|
|
@@ -101,32 +236,51 @@ export class EBookPlayer {
|
|
|
101
236
|
this.setToolbarButtonContent(this.noteBtn, NOTE_ICON_SVG, this.selectionOptions.noteButtonText);
|
|
102
237
|
this.noteBtn.setAttribute("aria-label", this.selectionOptions.noteButtonText);
|
|
103
238
|
this.selectionToolbar.append(this.highlightBtn, this.toolbarDivider, this.noteBtn);
|
|
104
|
-
this.root.append(this.content, this.selectionToolbar);
|
|
239
|
+
this.root.append(this.content, this.selectionToolbar, this.loadWarning, this.textInputBackdrop);
|
|
105
240
|
this.container.innerHTML = "";
|
|
106
241
|
this.container.append(this.root);
|
|
242
|
+
if (!this.container.style.position) {
|
|
243
|
+
this.container.style.position = "relative";
|
|
244
|
+
}
|
|
245
|
+
this.container.append(this.backToTopBtn);
|
|
107
246
|
this.highlightBtn.addEventListener("mousedown", this.preserveSelectionOnToolbarMouseDownBound);
|
|
108
247
|
this.noteBtn.addEventListener("mousedown", this.preserveSelectionOnToolbarMouseDownBound);
|
|
109
248
|
this.highlightBtn.addEventListener("click", this.handleHighlightClickBound);
|
|
110
249
|
this.noteBtn.addEventListener("click", this.handleNoteClickBound);
|
|
250
|
+
this.loadWarningToggleBtn.addEventListener("click", this.handleLoadWarningToggleBound);
|
|
251
|
+
this.loadWarningCloseBtn.addEventListener("click", this.handleLoadWarningCloseBound);
|
|
252
|
+
this.textInputCancelBtn.addEventListener("click", this.handleTextInputCancelBound);
|
|
253
|
+
this.textInputConfirmBtn.addEventListener("click", this.handleTextInputConfirmBound);
|
|
254
|
+
this.textInputBackdrop.addEventListener("pointerdown", this.handleTextInputBackdropPointerDownBound);
|
|
255
|
+
this.textInputTextarea.addEventListener("keydown", this.handleTextInputKeyDownBound);
|
|
256
|
+
this.backToTopBtn.addEventListener("click", this.handleBackToTopClickBound);
|
|
111
257
|
document.addEventListener("selectionchange", this.handleSelectionChangeBound);
|
|
112
258
|
document.addEventListener("pointerdown", this.handleDocumentPointerDownBound);
|
|
113
259
|
window.addEventListener("scroll", this.handleSelectionRepositionBound, true);
|
|
114
260
|
window.addEventListener("resize", this.handleSelectionRepositionBound);
|
|
261
|
+
this.updateBackToTopButtonPosition();
|
|
115
262
|
}
|
|
116
263
|
async load(doc) {
|
|
117
264
|
if (doc) {
|
|
118
265
|
this.runtimeDocument = cloneEbookDoc(doc);
|
|
119
266
|
}
|
|
120
|
-
|
|
121
|
-
|
|
267
|
+
try {
|
|
268
|
+
if (!this.options.provider && !this.runtimeDocument) {
|
|
269
|
+
throw new Error("Provider is not configured. Call load(doc) when using EBookPlayer without provider.");
|
|
270
|
+
}
|
|
271
|
+
const loadedDoc = await this.engine.load();
|
|
272
|
+
this.loaded = true;
|
|
273
|
+
if (this.options.initialAnnotations) {
|
|
274
|
+
await this.engine.setAnnotations(this.options.initialAnnotations, "api");
|
|
275
|
+
}
|
|
276
|
+
this.renderDocument();
|
|
277
|
+
this.hideLoadWarning();
|
|
278
|
+
return loadedDoc;
|
|
122
279
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
await this.engine.setAnnotations(this.options.initialAnnotations, "api");
|
|
280
|
+
catch (error) {
|
|
281
|
+
this.showLoadWarning(error);
|
|
282
|
+
throw error;
|
|
127
283
|
}
|
|
128
|
-
this.renderDocument();
|
|
129
|
-
return loadedDoc;
|
|
130
284
|
}
|
|
131
285
|
destroy() {
|
|
132
286
|
if (this.destroyed) {
|
|
@@ -142,6 +296,19 @@ export class EBookPlayer {
|
|
|
142
296
|
this.noteBtn.removeEventListener("mousedown", this.preserveSelectionOnToolbarMouseDownBound);
|
|
143
297
|
this.highlightBtn.removeEventListener("click", this.handleHighlightClickBound);
|
|
144
298
|
this.noteBtn.removeEventListener("click", this.handleNoteClickBound);
|
|
299
|
+
this.loadWarningToggleBtn.removeEventListener("click", this.handleLoadWarningToggleBound);
|
|
300
|
+
this.loadWarningCloseBtn.removeEventListener("click", this.handleLoadWarningCloseBound);
|
|
301
|
+
this.textInputCancelBtn.removeEventListener("click", this.handleTextInputCancelBound);
|
|
302
|
+
this.textInputConfirmBtn.removeEventListener("click", this.handleTextInputConfirmBound);
|
|
303
|
+
this.textInputBackdrop.removeEventListener("pointerdown", this.handleTextInputBackdropPointerDownBound);
|
|
304
|
+
this.textInputTextarea.removeEventListener("keydown", this.handleTextInputKeyDownBound);
|
|
305
|
+
this.backToTopBtn.removeEventListener("click", this.handleBackToTopClickBound);
|
|
306
|
+
if (this.pendingTextInputResolver) {
|
|
307
|
+
this.finishTextInput(null);
|
|
308
|
+
}
|
|
309
|
+
if (this.backToTopBtn.parentNode === this.container) {
|
|
310
|
+
this.container.removeChild(this.backToTopBtn);
|
|
311
|
+
}
|
|
145
312
|
if (this.root.parentNode === this.container) {
|
|
146
313
|
this.container.removeChild(this.root);
|
|
147
314
|
}
|
|
@@ -226,7 +393,7 @@ export class EBookPlayer {
|
|
|
226
393
|
throw new Error("Bookmark content is required when prompt is disabled.");
|
|
227
394
|
}
|
|
228
395
|
else {
|
|
229
|
-
content = this.promptTextWithLimit("请输入书签内容", current?.content || "", this.inputLimits.bookmarkMaxLength);
|
|
396
|
+
content = await this.promptTextWithLimit("请输入书签内容", current?.content || "", this.inputLimits.bookmarkMaxLength);
|
|
230
397
|
}
|
|
231
398
|
if (content === null) {
|
|
232
399
|
return null;
|
|
@@ -236,6 +403,31 @@ export class EBookPlayer {
|
|
|
236
403
|
this.renderDocument();
|
|
237
404
|
return bookmark;
|
|
238
405
|
}
|
|
406
|
+
async updateBookmark(bookmarkId, options = {}, source = "api") {
|
|
407
|
+
this.ensureLoaded();
|
|
408
|
+
const current = this.engine
|
|
409
|
+
.getBookmarks()
|
|
410
|
+
.find((item) => item.id === bookmarkId);
|
|
411
|
+
if (!current) {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
let content = null;
|
|
415
|
+
if (typeof options.content === "string" && options.content.trim()) {
|
|
416
|
+
content = this.normalizeBookmarkContent(options.content);
|
|
417
|
+
}
|
|
418
|
+
else if (options.prompt === false) {
|
|
419
|
+
throw new Error("Bookmark content is required when prompt is disabled.");
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
content = await this.promptTextWithLimit("请输入书签内容", current.content || "", this.inputLimits.bookmarkMaxLength);
|
|
423
|
+
}
|
|
424
|
+
if (content === null) {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
const updated = await this.engine.updateBookmark(bookmarkId, { content }, source);
|
|
428
|
+
this.renderDocument();
|
|
429
|
+
return updated;
|
|
430
|
+
}
|
|
239
431
|
async deleteBookmark(bookmarkId, source = "api") {
|
|
240
432
|
this.ensureLoaded();
|
|
241
433
|
const deleted = await this.engine.deleteBookmark(bookmarkId, source);
|
|
@@ -254,7 +446,13 @@ export class EBookPlayer {
|
|
|
254
446
|
if (pageIndex === null) {
|
|
255
447
|
return null;
|
|
256
448
|
}
|
|
257
|
-
this.scrollToBlock(target.pageId, target.blockId);
|
|
449
|
+
const blockNode = this.scrollToBlock(target.pageId, target.blockId);
|
|
450
|
+
const selector = `mark.hyepl-highlight[data-highlight-id="${this.escapeSelectorValue(highlightId)}"]`;
|
|
451
|
+
this.focusAfterJump({
|
|
452
|
+
selector,
|
|
453
|
+
focusClass: "hyepl-jump-focus-mark",
|
|
454
|
+
fallbackNode: blockNode,
|
|
455
|
+
});
|
|
258
456
|
return pageIndex;
|
|
259
457
|
}
|
|
260
458
|
goToNote(noteId) {
|
|
@@ -267,7 +465,13 @@ export class EBookPlayer {
|
|
|
267
465
|
if (pageIndex === null) {
|
|
268
466
|
return null;
|
|
269
467
|
}
|
|
270
|
-
this.scrollToBlock(target.pageId, target.blockId);
|
|
468
|
+
const blockNode = this.scrollToBlock(target.pageId, target.blockId);
|
|
469
|
+
const selector = `mark.hyepl-note[data-note-id="${this.escapeSelectorValue(noteId)}"]`;
|
|
470
|
+
this.focusAfterJump({
|
|
471
|
+
selector,
|
|
472
|
+
focusClass: "hyepl-jump-focus-mark",
|
|
473
|
+
fallbackNode: blockNode,
|
|
474
|
+
});
|
|
271
475
|
return pageIndex;
|
|
272
476
|
}
|
|
273
477
|
goToBookmark(bookmarkId) {
|
|
@@ -282,11 +486,30 @@ export class EBookPlayer {
|
|
|
282
486
|
if (pageIndex === null) {
|
|
283
487
|
return null;
|
|
284
488
|
}
|
|
489
|
+
const bookmarkSelector = `.hyepl-bookmark-flag[data-bookmark-id="${this.escapeSelectorValue(bookmarkId)}"]`;
|
|
490
|
+
const bookmarkNode = this.content.querySelector(bookmarkSelector);
|
|
491
|
+
const fallbackNode = this.findBlockNode(target.pageId, target.blockId);
|
|
285
492
|
if (Number.isFinite(target.scrollTop)) {
|
|
286
|
-
this.scrollToStoredPosition(target.scrollTop);
|
|
493
|
+
this.scrollToStoredPosition(target.scrollTop - BOOKMARK_JUMP_REVEAL_TOP_OFFSET_PX);
|
|
494
|
+
this.focusAfterJump({
|
|
495
|
+
selector: bookmarkSelector,
|
|
496
|
+
fallbackNode,
|
|
497
|
+
});
|
|
287
498
|
return pageIndex;
|
|
288
499
|
}
|
|
289
|
-
|
|
500
|
+
if (bookmarkNode) {
|
|
501
|
+
this.scrollToNodeWithTopOffset(bookmarkNode, BOOKMARK_JUMP_REVEAL_TOP_OFFSET_PX);
|
|
502
|
+
}
|
|
503
|
+
else if (fallbackNode) {
|
|
504
|
+
this.scrollToNodeWithTopOffset(fallbackNode, BOOKMARK_JUMP_REVEAL_TOP_OFFSET_PX);
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
this.scrollToBlock(target.pageId, target.blockId);
|
|
508
|
+
}
|
|
509
|
+
this.focusAfterJump({
|
|
510
|
+
selector: bookmarkSelector,
|
|
511
|
+
fallbackNode,
|
|
512
|
+
});
|
|
290
513
|
return pageIndex;
|
|
291
514
|
}
|
|
292
515
|
onAnnotationCreate(listener) {
|
|
@@ -681,6 +904,7 @@ export class EBookPlayer {
|
|
|
681
904
|
}
|
|
682
905
|
const mark = document.createElement("mark");
|
|
683
906
|
mark.className = "hyepl-highlight";
|
|
907
|
+
mark.dataset.highlightId = item.id;
|
|
684
908
|
if (item.color) {
|
|
685
909
|
mark.style.setProperty("--hyepl-highlight-color", item.color);
|
|
686
910
|
}
|
|
@@ -995,6 +1219,22 @@ export class EBookPlayer {
|
|
|
995
1219
|
this.selectionToolbar.classList.remove("show");
|
|
996
1220
|
this.selectionDraft = null;
|
|
997
1221
|
}
|
|
1222
|
+
showLoadWarning(error) {
|
|
1223
|
+
this.loadWarningMessage.textContent = resolveErrorMessage(error, "未知加载错误");
|
|
1224
|
+
this.loadWarning.classList.add("show");
|
|
1225
|
+
this.setLoadWarningExpanded(false);
|
|
1226
|
+
}
|
|
1227
|
+
hideLoadWarning() {
|
|
1228
|
+
this.loadWarning.classList.remove("show");
|
|
1229
|
+
this.setLoadWarningExpanded(false);
|
|
1230
|
+
}
|
|
1231
|
+
toggleLoadWarning() {
|
|
1232
|
+
this.setLoadWarningExpanded(!this.loadWarning.classList.contains("expanded"));
|
|
1233
|
+
}
|
|
1234
|
+
setLoadWarningExpanded(expanded) {
|
|
1235
|
+
this.loadWarning.classList.toggle("expanded", expanded);
|
|
1236
|
+
this.loadWarningToggleBtn.textContent = expanded ? "收起" : "详情";
|
|
1237
|
+
}
|
|
998
1238
|
clearSelection() {
|
|
999
1239
|
window.getSelection()?.removeAllRanges();
|
|
1000
1240
|
}
|
|
@@ -1059,29 +1299,30 @@ export class EBookPlayer {
|
|
|
1059
1299
|
this.hideSelectionToolbar();
|
|
1060
1300
|
}
|
|
1061
1301
|
async handleNoteClick() {
|
|
1062
|
-
|
|
1302
|
+
const selectionDraft = this.selectionDraft;
|
|
1303
|
+
if (!selectionDraft) {
|
|
1063
1304
|
return;
|
|
1064
1305
|
}
|
|
1065
1306
|
const defaultText = "";
|
|
1066
|
-
const content = this.promptTextWithLimit("请输入笔记内容", defaultText, this.inputLimits.noteMaxLength);
|
|
1307
|
+
const content = await this.promptTextWithLimit("请输入笔记内容", defaultText, this.inputLimits.noteMaxLength);
|
|
1067
1308
|
if (content === null) {
|
|
1068
1309
|
return;
|
|
1069
1310
|
}
|
|
1070
|
-
const segments =
|
|
1071
|
-
?
|
|
1311
|
+
const segments = selectionDraft.segments && selectionDraft.segments.length
|
|
1312
|
+
? selectionDraft.segments
|
|
1072
1313
|
: [
|
|
1073
1314
|
{
|
|
1074
|
-
pageId:
|
|
1075
|
-
blockId:
|
|
1076
|
-
range:
|
|
1077
|
-
text:
|
|
1315
|
+
pageId: selectionDraft.pageId,
|
|
1316
|
+
blockId: selectionDraft.blockId,
|
|
1317
|
+
range: selectionDraft.range,
|
|
1318
|
+
text: selectionDraft.text,
|
|
1078
1319
|
},
|
|
1079
1320
|
];
|
|
1080
1321
|
const firstSegment = segments[0];
|
|
1081
1322
|
if (!firstSegment) {
|
|
1082
1323
|
return;
|
|
1083
1324
|
}
|
|
1084
|
-
const mergedSelectedText =
|
|
1325
|
+
const mergedSelectedText = selectionDraft.text ||
|
|
1085
1326
|
segments
|
|
1086
1327
|
.map((segment) => segment.text)
|
|
1087
1328
|
.filter(Boolean)
|
|
@@ -1113,6 +1354,41 @@ export class EBookPlayer {
|
|
|
1113
1354
|
}
|
|
1114
1355
|
return Math.max(320, Math.round(width));
|
|
1115
1356
|
}
|
|
1357
|
+
updateBackToTopButtonPosition() {
|
|
1358
|
+
const hostRect = this.container.getBoundingClientRect();
|
|
1359
|
+
const hostWidth = this.container.clientWidth || hostRect.width;
|
|
1360
|
+
const hostHeight = this.container.clientHeight || hostRect.height;
|
|
1361
|
+
const buttonWidth = this.backToTopBtn.offsetWidth || 52;
|
|
1362
|
+
const buttonHeight = this.backToTopBtn.offsetHeight || 52;
|
|
1363
|
+
const margin = 16;
|
|
1364
|
+
if (hostWidth <= 0 || hostHeight <= 0) {
|
|
1365
|
+
this.backToTopBtn.classList.add("hidden");
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
const viewportLeft = 0;
|
|
1369
|
+
const viewportTop = 0;
|
|
1370
|
+
const viewportRight = window.innerWidth;
|
|
1371
|
+
const viewportBottom = window.innerHeight;
|
|
1372
|
+
const desiredViewportLeft = Math.min(hostRect.right - buttonWidth - margin, viewportRight - buttonWidth - margin);
|
|
1373
|
+
const desiredViewportTop = viewportBottom - buttonHeight - margin;
|
|
1374
|
+
const minViewportLeft = Math.max(viewportLeft + margin, hostRect.left + margin);
|
|
1375
|
+
const maxViewportLeft = Math.min(viewportRight - buttonWidth - margin, hostRect.right - buttonWidth - margin);
|
|
1376
|
+
const minViewportTop = Math.max(viewportTop + margin, hostRect.top + margin);
|
|
1377
|
+
const maxViewportTop = Math.min(viewportBottom - buttonHeight - margin, hostRect.bottom - buttonHeight - margin);
|
|
1378
|
+
if (maxViewportLeft < minViewportLeft || maxViewportTop < minViewportTop) {
|
|
1379
|
+
this.backToTopBtn.classList.add("hidden");
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
const clampedViewportLeft = Math.max(minViewportLeft, Math.min(desiredViewportLeft, maxViewportLeft));
|
|
1383
|
+
const clampedViewportTop = Math.max(minViewportTop, Math.min(desiredViewportTop, maxViewportTop));
|
|
1384
|
+
const localLeft = clampedViewportLeft - hostRect.left;
|
|
1385
|
+
const localTop = clampedViewportTop - hostRect.top;
|
|
1386
|
+
this.backToTopBtn.style.left = `${Math.round(localLeft)}px`;
|
|
1387
|
+
this.backToTopBtn.style.top = `${Math.round(localTop)}px`;
|
|
1388
|
+
this.backToTopBtn.style.right = "auto";
|
|
1389
|
+
this.backToTopBtn.style.bottom = "auto";
|
|
1390
|
+
this.backToTopBtn.classList.remove("hidden");
|
|
1391
|
+
}
|
|
1116
1392
|
resolveSelectionToolbarOptions(options) {
|
|
1117
1393
|
const highlightButtonText = typeof options?.highlightButtonText === "string" &&
|
|
1118
1394
|
options.highlightButtonText.trim()
|
|
@@ -1150,24 +1426,43 @@ export class EBookPlayer {
|
|
|
1150
1426
|
return Math.max(1, Math.floor(value));
|
|
1151
1427
|
}
|
|
1152
1428
|
promptTextWithLimit(title, defaultText, maxLength) {
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1429
|
+
if (this.pendingTextInputResolver) {
|
|
1430
|
+
this.finishTextInput(null);
|
|
1431
|
+
}
|
|
1432
|
+
this.pendingTextInputMaxLength = maxLength;
|
|
1433
|
+
this.textInputTitle.textContent = `${title}(最多${maxLength}字)`;
|
|
1434
|
+
this.textInputTextarea.value = defaultText;
|
|
1435
|
+
this.textInputError.textContent = "";
|
|
1436
|
+
this.textInputBackdrop.classList.add("show");
|
|
1437
|
+
window.setTimeout(() => {
|
|
1438
|
+
this.textInputTextarea.focus();
|
|
1439
|
+
this.textInputTextarea.setSelectionRange(this.textInputTextarea.value.length, this.textInputTextarea.value.length);
|
|
1440
|
+
}, 0);
|
|
1441
|
+
return new Promise((resolve) => {
|
|
1442
|
+
this.pendingTextInputResolver = resolve;
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
confirmTextInput() {
|
|
1446
|
+
const raw = this.textInputTextarea.value || "";
|
|
1447
|
+
const trimmed = raw.trim();
|
|
1448
|
+
if (!trimmed) {
|
|
1449
|
+
this.textInputError.textContent = "内容不能为空";
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
if (trimmed.length > this.pendingTextInputMaxLength) {
|
|
1453
|
+
this.textInputError.textContent = `最多输入${this.pendingTextInputMaxLength}字,当前${trimmed.length}字。`;
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
this.finishTextInput(trimmed);
|
|
1457
|
+
}
|
|
1458
|
+
finishTextInput(value) {
|
|
1459
|
+
const resolver = this.pendingTextInputResolver;
|
|
1460
|
+
this.pendingTextInputResolver = null;
|
|
1461
|
+
this.pendingTextInputMaxLength = 0;
|
|
1462
|
+
this.textInputBackdrop.classList.remove("show");
|
|
1463
|
+
this.textInputError.textContent = "";
|
|
1464
|
+
if (resolver) {
|
|
1465
|
+
resolver(value);
|
|
1171
1466
|
}
|
|
1172
1467
|
}
|
|
1173
1468
|
normalizeBookmarkContent(raw) {
|
|
@@ -1220,15 +1515,18 @@ export class EBookPlayer {
|
|
|
1220
1515
|
}
|
|
1221
1516
|
return { pageId, blockId };
|
|
1222
1517
|
}
|
|
1223
|
-
|
|
1518
|
+
findBlockNode(pageId, blockId) {
|
|
1224
1519
|
const page = this.escapeSelectorValue(pageId);
|
|
1225
1520
|
const block = this.escapeSelectorValue(blockId);
|
|
1226
|
-
|
|
1521
|
+
return this.content.querySelector(`[data-page-id="${page}"][data-block-id="${block}"]`);
|
|
1522
|
+
}
|
|
1523
|
+
scrollToBlock(pageId, blockId, behavior = "smooth") {
|
|
1524
|
+
const node = this.findBlockNode(pageId, blockId);
|
|
1227
1525
|
if (!node) {
|
|
1228
|
-
return
|
|
1526
|
+
return null;
|
|
1229
1527
|
}
|
|
1230
|
-
node.scrollIntoView({ block: "start" });
|
|
1231
|
-
return
|
|
1528
|
+
node.scrollIntoView({ block: "start", behavior });
|
|
1529
|
+
return node;
|
|
1232
1530
|
}
|
|
1233
1531
|
getCurrentScrollTop() {
|
|
1234
1532
|
const scrollHost = this.resolveScrollHost();
|
|
@@ -1237,14 +1535,59 @@ export class EBookPlayer {
|
|
|
1237
1535
|
}
|
|
1238
1536
|
return Math.max(0, Math.floor(scrollHost.scrollTop || 0));
|
|
1239
1537
|
}
|
|
1240
|
-
scrollToStoredPosition(scrollTop) {
|
|
1538
|
+
scrollToStoredPosition(scrollTop, behavior = "smooth") {
|
|
1241
1539
|
const safeTop = Math.max(0, Math.floor(scrollTop || 0));
|
|
1242
1540
|
const scrollHost = this.resolveScrollHost();
|
|
1243
1541
|
if (this.isWindowScrollHost(scrollHost)) {
|
|
1244
|
-
window.scrollTo({ top: safeTop, behavior
|
|
1542
|
+
window.scrollTo({ top: safeTop, behavior });
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
scrollHost.scrollTo({ top: safeTop, behavior });
|
|
1546
|
+
}
|
|
1547
|
+
scrollToNodeWithTopOffset(node, topOffset, behavior = "smooth") {
|
|
1548
|
+
const safeOffset = Math.max(0, Math.floor(topOffset || 0));
|
|
1549
|
+
const scrollHost = this.resolveScrollHost();
|
|
1550
|
+
if (this.isWindowScrollHost(scrollHost)) {
|
|
1551
|
+
const absoluteTop = node.getBoundingClientRect().top + window.scrollY;
|
|
1552
|
+
const targetTop = Math.max(0, Math.floor(absoluteTop - safeOffset));
|
|
1553
|
+
window.scrollTo({ top: targetTop, behavior });
|
|
1245
1554
|
return;
|
|
1246
1555
|
}
|
|
1247
|
-
scrollHost.
|
|
1556
|
+
const hostRect = scrollHost.getBoundingClientRect();
|
|
1557
|
+
const relativeTop = node.getBoundingClientRect().top - hostRect.top + scrollHost.scrollTop;
|
|
1558
|
+
const targetTop = Math.max(0, Math.floor(relativeTop - safeOffset));
|
|
1559
|
+
scrollHost.scrollTo({ top: targetTop, behavior });
|
|
1560
|
+
}
|
|
1561
|
+
focusAfterJump(options) {
|
|
1562
|
+
const { selector, focusClass = "hyepl-jump-focus", fallbackNode = null, } = options;
|
|
1563
|
+
this.deferFocus(() => {
|
|
1564
|
+
const matched = Array.from(this.content.querySelectorAll(selector));
|
|
1565
|
+
if (matched.length) {
|
|
1566
|
+
this.playFocusAnimation(matched, focusClass);
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
if (fallbackNode) {
|
|
1570
|
+
this.playFocusAnimation([fallbackNode], "hyepl-jump-focus");
|
|
1571
|
+
}
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
deferFocus(task) {
|
|
1575
|
+
window.setTimeout(task, JUMP_SCROLL_FOCUS_DELAY_MS);
|
|
1576
|
+
}
|
|
1577
|
+
playFocusAnimation(nodes, className = "hyepl-jump-focus") {
|
|
1578
|
+
const seen = new Set();
|
|
1579
|
+
for (const node of nodes) {
|
|
1580
|
+
if (!node || !this.content.contains(node) || seen.has(node)) {
|
|
1581
|
+
continue;
|
|
1582
|
+
}
|
|
1583
|
+
seen.add(node);
|
|
1584
|
+
node.classList.remove(className);
|
|
1585
|
+
void node.offsetWidth;
|
|
1586
|
+
node.classList.add(className);
|
|
1587
|
+
window.setTimeout(() => {
|
|
1588
|
+
node.classList.remove(className);
|
|
1589
|
+
}, JUMP_FOCUS_ANIMATION_MS + 40);
|
|
1590
|
+
}
|
|
1248
1591
|
}
|
|
1249
1592
|
resolveScrollHost() {
|
|
1250
1593
|
let current = this.container;
|
|
@@ -1397,3 +1740,12 @@ function escapeHtml(value) {
|
|
|
1397
1740
|
function escapeAttribute(value) {
|
|
1398
1741
|
return escapeHtml(value);
|
|
1399
1742
|
}
|
|
1743
|
+
function resolveErrorMessage(error, fallback = "未知错误") {
|
|
1744
|
+
if (error instanceof Error && error.message) {
|
|
1745
|
+
return error.message;
|
|
1746
|
+
}
|
|
1747
|
+
if (typeof error === "string" && error.trim()) {
|
|
1748
|
+
return error.trim();
|
|
1749
|
+
}
|
|
1750
|
+
return fallback;
|
|
1751
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EbookDoc } from "../types/ebook";
|
|
2
|
-
import type { ReaderAnnotationCreateEvent, ReaderAnnotationDeleteEvent, ReaderAnnotations, ReaderAnnotationsChangeEvent, ReaderAnnotationUpdateEvent, ReaderBookmark, ReaderHighlight, PlayerDataProvider, PlayerEngineOptions, PlayerFeatures, ReaderNote, ReaderProgress } from "../types/player";
|
|
2
|
+
import type { ReaderAnnotationCreateEvent, ReaderAnnotationDeleteEvent, ReaderAnnotations, ReaderAnnotationsChangeEvent, ReaderAnnotationUpdateEvent, ReaderBookmark, ReaderBookmarkPatch, ReaderHighlight, PlayerDataProvider, PlayerEngineOptions, PlayerFeatures, ReaderNote, ReaderProgress } from "../types/player";
|
|
3
3
|
export interface PlayerState {
|
|
4
4
|
currentPage: number;
|
|
5
5
|
zoom: number;
|
|
@@ -49,6 +49,7 @@ export declare class PlayerEngine {
|
|
|
49
49
|
updateNote(noteId: string, patch: Partial<Omit<ReaderNote, "id" | "bookId" | "createdAt">>, source?: "api" | "user"): Promise<ReaderNote | null>;
|
|
50
50
|
deleteNote(noteId: string, source?: "api" | "user"): Promise<boolean>;
|
|
51
51
|
setBookmark(pageId: string, blockId: string, content: string, source?: "api" | "user", scrollTop?: number): Promise<ReaderBookmark>;
|
|
52
|
+
updateBookmark(bookmarkId: string, patch: ReaderBookmarkPatch, source?: "api" | "user"): Promise<ReaderBookmark | null>;
|
|
52
53
|
deleteBookmark(bookmarkId: string, source?: "api" | "user"): Promise<boolean>;
|
|
53
54
|
onAnnotationCreate(listener: AnnotationCreateListener): () => void;
|
|
54
55
|
onAnnotationUpdate(listener: AnnotationUpdateListener): () => void;
|