@hyebook/vue3-adapter 2.2.6 → 2.3.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/core/src/index.d.ts +1 -0
- package/dist/core/src/index.d.ts.map +1 -1
- package/dist/core/src/index.js +1 -0
- package/dist/core/src/player/ebook-player.d.ts +65 -0
- package/dist/core/src/player/ebook-player.d.ts.map +1 -0
- package/dist/core/src/player/ebook-player.js +550 -0
- package/dist/core/src/player/engine.d.ts +43 -2
- package/dist/core/src/player/engine.d.ts.map +1 -1
- package/dist/core/src/player/engine.js +388 -8
- package/dist/core/src/types/player.d.ts +93 -4
- package/dist/core/src/types/player.d.ts.map +1 -1
- package/dist/core/src/workbench/editor-workbench.d.ts +33 -1
- package/dist/core/src/workbench/editor-workbench.d.ts.map +1 -1
- package/dist/core/src/workbench/editor-workbench.js +248 -102
- package/dist/vue3-adapter/src/index.d.ts +5 -1
- package/dist/vue3-adapter/src/index.d.ts.map +1 -1
- package/dist/vue3-adapter/src/index.js +5 -2
- package/package.json +3 -3
package/dist/core/src/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../core/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../core/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC"}
|
package/dist/core/src/index.js
CHANGED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { EbookDoc } from "../types/ebook";
|
|
2
|
+
import type { EBookPlayerHandle, EBookPlayerOptions, ReaderAnnotationChangeSource, ReaderAnnotationCreateEvent, ReaderAnnotationDeleteEvent, ReaderAnnotations, ReaderAnnotationsChangeEvent, ReaderAnnotationUpdateEvent, ReaderHighlight, ReaderNote } from "../types/player";
|
|
3
|
+
export declare class EBookPlayer implements EBookPlayerHandle {
|
|
4
|
+
private container;
|
|
5
|
+
private options;
|
|
6
|
+
private engine;
|
|
7
|
+
private root;
|
|
8
|
+
private content;
|
|
9
|
+
private selectionToolbar;
|
|
10
|
+
private highlightBtn;
|
|
11
|
+
private noteBtn;
|
|
12
|
+
private selectionOptions;
|
|
13
|
+
private selectionDraft;
|
|
14
|
+
private loaded;
|
|
15
|
+
private destroyed;
|
|
16
|
+
private readonly handleSelectionChangeBound;
|
|
17
|
+
private readonly handleDocumentPointerDownBound;
|
|
18
|
+
private readonly preserveSelectionOnToolbarMouseDownBound;
|
|
19
|
+
private readonly handleHighlightClickBound;
|
|
20
|
+
private readonly handleNoteClickBound;
|
|
21
|
+
constructor(container: HTMLElement, options: EBookPlayerOptions);
|
|
22
|
+
load(): Promise<EbookDoc>;
|
|
23
|
+
destroy(): void;
|
|
24
|
+
getDocument(): EbookDoc;
|
|
25
|
+
getAnnotations(): ReaderAnnotations;
|
|
26
|
+
getHighlights(): ReaderHighlight[];
|
|
27
|
+
getNotes(): ReaderNote[];
|
|
28
|
+
setAnnotations(annotations: ReaderAnnotations, source?: ReaderAnnotationChangeSource): Promise<ReaderAnnotations>;
|
|
29
|
+
reloadAnnotations(): Promise<ReaderAnnotations>;
|
|
30
|
+
createHighlight(highlight: Omit<ReaderHighlight, "id" | "createdAt" | "updatedAt" | "bookId">, source?: ReaderAnnotationChangeSource): Promise<ReaderHighlight>;
|
|
31
|
+
updateHighlight(highlightId: string, patch: Partial<Omit<ReaderHighlight, "id" | "bookId" | "createdAt">>, source?: ReaderAnnotationChangeSource): Promise<ReaderHighlight | null>;
|
|
32
|
+
deleteHighlight(highlightId: string, source?: ReaderAnnotationChangeSource): Promise<boolean>;
|
|
33
|
+
createNote(note: Omit<ReaderNote, "id" | "createdAt" | "updatedAt" | "bookId">, source?: ReaderAnnotationChangeSource): Promise<ReaderNote>;
|
|
34
|
+
updateNote(noteId: string, patch: Partial<Omit<ReaderNote, "id" | "bookId" | "createdAt">>, source?: ReaderAnnotationChangeSource): Promise<ReaderNote | null>;
|
|
35
|
+
deleteNote(noteId: string, source?: ReaderAnnotationChangeSource): Promise<boolean>;
|
|
36
|
+
onAnnotationCreate(listener: (event: ReaderAnnotationCreateEvent) => void): () => void;
|
|
37
|
+
onAnnotationUpdate(listener: (event: ReaderAnnotationUpdateEvent) => void): () => void;
|
|
38
|
+
onAnnotationDelete(listener: (event: ReaderAnnotationDeleteEvent) => void): () => void;
|
|
39
|
+
onAnnotationsChange(listener: (event: ReaderAnnotationsChangeEvent) => void): () => void;
|
|
40
|
+
private renderDocument;
|
|
41
|
+
private renderPage;
|
|
42
|
+
private renderTextBlock;
|
|
43
|
+
private renderNonTextBlock;
|
|
44
|
+
private getTextBlockContent;
|
|
45
|
+
private createAnnotationBuckets;
|
|
46
|
+
private toBlockKey;
|
|
47
|
+
private applyHighlightMarks;
|
|
48
|
+
private applyNoteMarks;
|
|
49
|
+
private wrapRangeWithMark;
|
|
50
|
+
private createRangeFromOffsets;
|
|
51
|
+
private normalizeOffsetRange;
|
|
52
|
+
private handleSelectionChange;
|
|
53
|
+
private resolveTextContainer;
|
|
54
|
+
private getSelectionOffsets;
|
|
55
|
+
private hideSelectionToolbar;
|
|
56
|
+
private clearSelection;
|
|
57
|
+
private handleDocumentPointerDown;
|
|
58
|
+
private preserveSelectionOnToolbarMouseDown;
|
|
59
|
+
private handleHighlightClick;
|
|
60
|
+
private handleNoteClick;
|
|
61
|
+
private resolveWidth;
|
|
62
|
+
private resolveSelectionToolbarOptions;
|
|
63
|
+
private ensureLoaded;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=ebook-player.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ebook-player.d.ts","sourceRoot":"","sources":["../../../../../core/src/player/ebook-player.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
import { PlayerEngine } from "./engine";
|
|
2
|
+
const EBOOK_PLAYER_STYLE_ID = "hy-ebook-lite-player-style";
|
|
3
|
+
const EBOOK_PLAYER_STYLE_VERSION = "0.1.0";
|
|
4
|
+
const EBOOK_PLAYER_CSS = `
|
|
5
|
+
.hyepl-root{position:relative;max-width:100%;margin:0 auto;color:#0f172a;font-size:14px;line-height:1.7}
|
|
6
|
+
.hyepl-content{position:relative}
|
|
7
|
+
.hyepl-page{margin:0 0 16px}
|
|
8
|
+
.hyepl-page:last-child{margin-bottom:0}
|
|
9
|
+
.hyepl-block{margin:0 0 10px;white-space:pre-wrap;word-break:break-word}
|
|
10
|
+
.hyepl-block:last-child{margin-bottom:0}
|
|
11
|
+
.hyepl-block-muted{color:#64748b}
|
|
12
|
+
.hyepl-highlight{background:var(--hyepl-highlight-color,#fff59d);color:inherit;padding:0 .08em;border-radius:.2em}
|
|
13
|
+
.hyepl-note{background:rgba(14,116,144,.14);border-bottom:2px solid #0e7490;color:inherit;padding:0 .04em;border-radius:.2em}
|
|
14
|
+
.hyepl-selection-toolbar{position:fixed;z-index:10060;display:none;align-items:center;gap:8px;padding:6px 8px;border-radius:10px;border:1px solid #0f172a;background:rgba(15,23,42,.96);box-shadow:0 10px 24px rgba(15,23,42,.22);transform:translate(-50%,-100%)}
|
|
15
|
+
.hyepl-selection-toolbar.show{display:inline-flex}
|
|
16
|
+
.hyepl-toolbar-btn{height:30px;padding:0 10px;border:1px solid #334155;border-radius:8px;background:#1e293b;color:#f8fafc;cursor:pointer;font-size:12px}
|
|
17
|
+
`;
|
|
18
|
+
export class EBookPlayer {
|
|
19
|
+
constructor(container, options) {
|
|
20
|
+
this.selectionDraft = null;
|
|
21
|
+
this.loaded = false;
|
|
22
|
+
this.destroyed = false;
|
|
23
|
+
this.handleSelectionChangeBound = () => {
|
|
24
|
+
this.handleSelectionChange();
|
|
25
|
+
};
|
|
26
|
+
this.handleDocumentPointerDownBound = (event) => {
|
|
27
|
+
this.handleDocumentPointerDown(event);
|
|
28
|
+
};
|
|
29
|
+
this.preserveSelectionOnToolbarMouseDownBound = (event) => {
|
|
30
|
+
this.preserveSelectionOnToolbarMouseDown(event);
|
|
31
|
+
};
|
|
32
|
+
this.handleHighlightClickBound = () => {
|
|
33
|
+
void this.handleHighlightClick();
|
|
34
|
+
};
|
|
35
|
+
this.handleNoteClickBound = () => {
|
|
36
|
+
void this.handleNoteClick();
|
|
37
|
+
};
|
|
38
|
+
this.container = container;
|
|
39
|
+
this.options = options;
|
|
40
|
+
this.engine = new PlayerEngine(options.provider, options.engineOptions);
|
|
41
|
+
this.selectionOptions = this.resolveSelectionToolbarOptions(options.selectionToolbar);
|
|
42
|
+
ensureEBookPlayerStyles();
|
|
43
|
+
this.root = document.createElement("section");
|
|
44
|
+
this.root.className = "hyepl-root";
|
|
45
|
+
this.root.style.width = `${this.resolveWidth(options.width)}px`;
|
|
46
|
+
this.content = document.createElement("div");
|
|
47
|
+
this.content.className = "hyepl-content";
|
|
48
|
+
this.selectionToolbar = document.createElement("div");
|
|
49
|
+
this.selectionToolbar.className = "hyepl-selection-toolbar";
|
|
50
|
+
this.highlightBtn = document.createElement("button");
|
|
51
|
+
this.highlightBtn.type = "button";
|
|
52
|
+
this.highlightBtn.className = "hyepl-toolbar-btn";
|
|
53
|
+
this.highlightBtn.textContent = this.selectionOptions.highlightButtonText;
|
|
54
|
+
this.highlightBtn.setAttribute("aria-label", this.selectionOptions.highlightButtonText);
|
|
55
|
+
this.noteBtn = document.createElement("button");
|
|
56
|
+
this.noteBtn.type = "button";
|
|
57
|
+
this.noteBtn.className = "hyepl-toolbar-btn";
|
|
58
|
+
this.noteBtn.textContent = this.selectionOptions.noteButtonText;
|
|
59
|
+
this.noteBtn.setAttribute("aria-label", this.selectionOptions.noteButtonText);
|
|
60
|
+
this.selectionToolbar.append(this.highlightBtn, this.noteBtn);
|
|
61
|
+
this.root.append(this.content, this.selectionToolbar);
|
|
62
|
+
this.container.innerHTML = "";
|
|
63
|
+
this.container.append(this.root);
|
|
64
|
+
this.highlightBtn.addEventListener("mousedown", this.preserveSelectionOnToolbarMouseDownBound);
|
|
65
|
+
this.noteBtn.addEventListener("mousedown", this.preserveSelectionOnToolbarMouseDownBound);
|
|
66
|
+
this.highlightBtn.addEventListener("click", this.handleHighlightClickBound);
|
|
67
|
+
this.noteBtn.addEventListener("click", this.handleNoteClickBound);
|
|
68
|
+
document.addEventListener("selectionchange", this.handleSelectionChangeBound);
|
|
69
|
+
document.addEventListener("pointerdown", this.handleDocumentPointerDownBound);
|
|
70
|
+
}
|
|
71
|
+
async load() {
|
|
72
|
+
const doc = await this.engine.load();
|
|
73
|
+
this.loaded = true;
|
|
74
|
+
if (this.options.initialAnnotations) {
|
|
75
|
+
await this.engine.setAnnotations(this.options.initialAnnotations, "api");
|
|
76
|
+
}
|
|
77
|
+
this.renderDocument();
|
|
78
|
+
return doc;
|
|
79
|
+
}
|
|
80
|
+
destroy() {
|
|
81
|
+
if (this.destroyed) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.destroyed = true;
|
|
85
|
+
this.hideSelectionToolbar();
|
|
86
|
+
document.removeEventListener("selectionchange", this.handleSelectionChangeBound);
|
|
87
|
+
document.removeEventListener("pointerdown", this.handleDocumentPointerDownBound);
|
|
88
|
+
this.highlightBtn.removeEventListener("mousedown", this.preserveSelectionOnToolbarMouseDownBound);
|
|
89
|
+
this.noteBtn.removeEventListener("mousedown", this.preserveSelectionOnToolbarMouseDownBound);
|
|
90
|
+
this.highlightBtn.removeEventListener("click", this.handleHighlightClickBound);
|
|
91
|
+
this.noteBtn.removeEventListener("click", this.handleNoteClickBound);
|
|
92
|
+
if (this.root.parentNode === this.container) {
|
|
93
|
+
this.container.removeChild(this.root);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
getDocument() {
|
|
97
|
+
return this.engine.getDocument();
|
|
98
|
+
}
|
|
99
|
+
getAnnotations() {
|
|
100
|
+
return this.engine.getAnnotations();
|
|
101
|
+
}
|
|
102
|
+
getHighlights() {
|
|
103
|
+
return this.engine.getHighlights();
|
|
104
|
+
}
|
|
105
|
+
getNotes() {
|
|
106
|
+
return this.engine.getNotes();
|
|
107
|
+
}
|
|
108
|
+
async setAnnotations(annotations, source = "api") {
|
|
109
|
+
this.ensureLoaded();
|
|
110
|
+
const all = await this.engine.setAnnotations(annotations, source);
|
|
111
|
+
this.renderDocument();
|
|
112
|
+
return all;
|
|
113
|
+
}
|
|
114
|
+
async reloadAnnotations() {
|
|
115
|
+
this.ensureLoaded();
|
|
116
|
+
const all = await this.engine.reloadAnnotations();
|
|
117
|
+
this.renderDocument();
|
|
118
|
+
return all;
|
|
119
|
+
}
|
|
120
|
+
async createHighlight(highlight, source = "api") {
|
|
121
|
+
this.ensureLoaded();
|
|
122
|
+
const created = await this.engine.createHighlight(highlight, source);
|
|
123
|
+
this.renderDocument();
|
|
124
|
+
return created;
|
|
125
|
+
}
|
|
126
|
+
async updateHighlight(highlightId, patch, source = "api") {
|
|
127
|
+
this.ensureLoaded();
|
|
128
|
+
const updated = await this.engine.updateHighlight(highlightId, patch, source);
|
|
129
|
+
this.renderDocument();
|
|
130
|
+
return updated;
|
|
131
|
+
}
|
|
132
|
+
async deleteHighlight(highlightId, source = "api") {
|
|
133
|
+
this.ensureLoaded();
|
|
134
|
+
const deleted = await this.engine.deleteHighlight(highlightId, source);
|
|
135
|
+
this.renderDocument();
|
|
136
|
+
return deleted;
|
|
137
|
+
}
|
|
138
|
+
async createNote(note, source = "api") {
|
|
139
|
+
this.ensureLoaded();
|
|
140
|
+
const created = await this.engine.createNote(note, source);
|
|
141
|
+
this.renderDocument();
|
|
142
|
+
return created;
|
|
143
|
+
}
|
|
144
|
+
async updateNote(noteId, patch, source = "api") {
|
|
145
|
+
this.ensureLoaded();
|
|
146
|
+
const updated = await this.engine.updateNote(noteId, patch, source);
|
|
147
|
+
this.renderDocument();
|
|
148
|
+
return updated;
|
|
149
|
+
}
|
|
150
|
+
async deleteNote(noteId, source = "api") {
|
|
151
|
+
this.ensureLoaded();
|
|
152
|
+
const deleted = await this.engine.deleteNote(noteId, source);
|
|
153
|
+
this.renderDocument();
|
|
154
|
+
return deleted;
|
|
155
|
+
}
|
|
156
|
+
onAnnotationCreate(listener) {
|
|
157
|
+
return this.engine.onAnnotationCreate(listener);
|
|
158
|
+
}
|
|
159
|
+
onAnnotationUpdate(listener) {
|
|
160
|
+
return this.engine.onAnnotationUpdate(listener);
|
|
161
|
+
}
|
|
162
|
+
onAnnotationDelete(listener) {
|
|
163
|
+
return this.engine.onAnnotationDelete(listener);
|
|
164
|
+
}
|
|
165
|
+
onAnnotationsChange(listener) {
|
|
166
|
+
return this.engine.onAnnotationsChange(listener);
|
|
167
|
+
}
|
|
168
|
+
renderDocument() {
|
|
169
|
+
if (!this.loaded || this.destroyed) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const doc = this.engine.getDocument();
|
|
173
|
+
const buckets = this.createAnnotationBuckets(this.engine.getAnnotations());
|
|
174
|
+
const fragment = document.createDocumentFragment();
|
|
175
|
+
doc.pages.forEach((page) => {
|
|
176
|
+
fragment.append(this.renderPage(page, buckets));
|
|
177
|
+
});
|
|
178
|
+
this.content.innerHTML = "";
|
|
179
|
+
this.content.append(fragment);
|
|
180
|
+
this.hideSelectionToolbar();
|
|
181
|
+
}
|
|
182
|
+
renderPage(page, buckets) {
|
|
183
|
+
const pageSection = document.createElement("section");
|
|
184
|
+
pageSection.className = "hyepl-page";
|
|
185
|
+
pageSection.dataset.pageId = page.id;
|
|
186
|
+
page.blocks.forEach((block) => {
|
|
187
|
+
if (block.type === "text") {
|
|
188
|
+
pageSection.append(this.renderTextBlock(page.id, block, buckets));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
pageSection.append(this.renderNonTextBlock(block));
|
|
192
|
+
});
|
|
193
|
+
return pageSection;
|
|
194
|
+
}
|
|
195
|
+
renderTextBlock(pageId, block, buckets) {
|
|
196
|
+
const blockTextNode = document.createElement("div");
|
|
197
|
+
blockTextNode.className = "hyepl-block hyepl-block-text";
|
|
198
|
+
blockTextNode.dataset.pageId = pageId;
|
|
199
|
+
blockTextNode.dataset.blockId = block.id;
|
|
200
|
+
blockTextNode.textContent = this.getTextBlockContent(block);
|
|
201
|
+
const blockKey = this.toBlockKey(pageId, block.id);
|
|
202
|
+
const highlights = buckets.highlightsByBlock.get(blockKey) || [];
|
|
203
|
+
const notes = buckets.notesByBlock.get(blockKey) || [];
|
|
204
|
+
this.applyHighlightMarks(blockTextNode, highlights);
|
|
205
|
+
this.applyNoteMarks(blockTextNode, notes);
|
|
206
|
+
return blockTextNode;
|
|
207
|
+
}
|
|
208
|
+
renderNonTextBlock(block) {
|
|
209
|
+
const node = document.createElement("div");
|
|
210
|
+
node.className = "hyepl-block hyepl-block-muted";
|
|
211
|
+
if (block.type === "image") {
|
|
212
|
+
node.textContent = "[图片内容]";
|
|
213
|
+
return node;
|
|
214
|
+
}
|
|
215
|
+
if (block.type === "video") {
|
|
216
|
+
node.textContent = "[视频内容]";
|
|
217
|
+
return node;
|
|
218
|
+
}
|
|
219
|
+
if (block.type === "table") {
|
|
220
|
+
node.textContent = "[表格内容]";
|
|
221
|
+
return node;
|
|
222
|
+
}
|
|
223
|
+
node.textContent = "[内容块]";
|
|
224
|
+
return node;
|
|
225
|
+
}
|
|
226
|
+
getTextBlockContent(block) {
|
|
227
|
+
return block.content.paragraphs
|
|
228
|
+
.map((paragraph) => {
|
|
229
|
+
return paragraph.runs.map((run) => run.text || "").join("");
|
|
230
|
+
})
|
|
231
|
+
.join("\n");
|
|
232
|
+
}
|
|
233
|
+
createAnnotationBuckets(annotations) {
|
|
234
|
+
const highlightsByBlock = new Map();
|
|
235
|
+
const notesByBlock = new Map();
|
|
236
|
+
annotations.highlights.forEach((item) => {
|
|
237
|
+
const key = this.toBlockKey(item.pageId, item.blockId);
|
|
238
|
+
const current = highlightsByBlock.get(key) || [];
|
|
239
|
+
current.push(item);
|
|
240
|
+
highlightsByBlock.set(key, current);
|
|
241
|
+
});
|
|
242
|
+
annotations.notes.forEach((item) => {
|
|
243
|
+
const key = this.toBlockKey(item.pageId, item.blockId);
|
|
244
|
+
const current = notesByBlock.get(key) || [];
|
|
245
|
+
current.push(item);
|
|
246
|
+
notesByBlock.set(key, current);
|
|
247
|
+
});
|
|
248
|
+
return { highlightsByBlock, notesByBlock };
|
|
249
|
+
}
|
|
250
|
+
toBlockKey(pageId, blockId) {
|
|
251
|
+
return `${pageId}::${blockId}`;
|
|
252
|
+
}
|
|
253
|
+
applyHighlightMarks(container, highlights) {
|
|
254
|
+
[...highlights]
|
|
255
|
+
.filter((item) => Number.isFinite(item.range.start) && Number.isFinite(item.range.end))
|
|
256
|
+
.sort((a, b) => b.range.start - a.range.start)
|
|
257
|
+
.forEach((item) => {
|
|
258
|
+
const range = this.createRangeFromOffsets(container, item.range.start, item.range.end);
|
|
259
|
+
if (!range || range.collapsed) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const mark = document.createElement("mark");
|
|
263
|
+
mark.className = "hyepl-highlight";
|
|
264
|
+
if (item.color) {
|
|
265
|
+
mark.style.setProperty("--hyepl-highlight-color", item.color);
|
|
266
|
+
}
|
|
267
|
+
this.wrapRangeWithMark(range, mark);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
applyNoteMarks(container, notes) {
|
|
271
|
+
[...notes]
|
|
272
|
+
.filter((item) => !!item.range)
|
|
273
|
+
.sort((a, b) => {
|
|
274
|
+
const startA = a.range?.start || 0;
|
|
275
|
+
const startB = b.range?.start || 0;
|
|
276
|
+
return startB - startA;
|
|
277
|
+
})
|
|
278
|
+
.forEach((item) => {
|
|
279
|
+
if (!item.range) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const range = this.createRangeFromOffsets(container, item.range.start, item.range.end);
|
|
283
|
+
if (!range || range.collapsed) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const mark = document.createElement("mark");
|
|
287
|
+
mark.className = "hyepl-note";
|
|
288
|
+
mark.dataset.noteId = item.id;
|
|
289
|
+
this.wrapRangeWithMark(range, mark);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
wrapRangeWithMark(range, mark) {
|
|
293
|
+
try {
|
|
294
|
+
range.surroundContents(mark);
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
const fragment = range.extractContents();
|
|
298
|
+
mark.append(fragment);
|
|
299
|
+
range.insertNode(mark);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
createRangeFromOffsets(containerNode, start, end) {
|
|
303
|
+
const normalized = this.normalizeOffsetRange(start, end);
|
|
304
|
+
if (!normalized) {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
const range = document.createRange();
|
|
308
|
+
const walker = document.createTreeWalker(containerNode, NodeFilter.SHOW_TEXT);
|
|
309
|
+
let cursor = 0;
|
|
310
|
+
let startNode = null;
|
|
311
|
+
let endNode = null;
|
|
312
|
+
let startOffset = 0;
|
|
313
|
+
let endOffset = 0;
|
|
314
|
+
let lastTextNode = null;
|
|
315
|
+
while (walker.nextNode()) {
|
|
316
|
+
const node = walker.currentNode;
|
|
317
|
+
const length = node.textContent?.length || 0;
|
|
318
|
+
lastTextNode = node;
|
|
319
|
+
const nextCursor = cursor + length;
|
|
320
|
+
if (!startNode &&
|
|
321
|
+
normalized.start >= cursor &&
|
|
322
|
+
normalized.start < nextCursor) {
|
|
323
|
+
startNode = node;
|
|
324
|
+
startOffset = normalized.start - cursor;
|
|
325
|
+
}
|
|
326
|
+
if (!endNode && normalized.end > cursor && normalized.end <= nextCursor) {
|
|
327
|
+
endNode = node;
|
|
328
|
+
endOffset = normalized.end - cursor;
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
cursor = nextCursor;
|
|
332
|
+
}
|
|
333
|
+
if (!startNode && lastTextNode && normalized.start === cursor) {
|
|
334
|
+
startNode = lastTextNode;
|
|
335
|
+
startOffset = lastTextNode.textContent?.length || 0;
|
|
336
|
+
}
|
|
337
|
+
if (!endNode && lastTextNode && normalized.end === cursor) {
|
|
338
|
+
endNode = lastTextNode;
|
|
339
|
+
endOffset = lastTextNode.textContent?.length || 0;
|
|
340
|
+
}
|
|
341
|
+
if (!startNode || !endNode) {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
range.setStart(startNode, startOffset);
|
|
345
|
+
range.setEnd(endNode, endOffset);
|
|
346
|
+
return range;
|
|
347
|
+
}
|
|
348
|
+
normalizeOffsetRange(start, end) {
|
|
349
|
+
const safeStart = Math.max(0, Math.floor(start || 0));
|
|
350
|
+
const safeEnd = Math.max(0, Math.floor(end || 0));
|
|
351
|
+
if (safeStart === safeEnd) {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
return safeStart < safeEnd
|
|
355
|
+
? { start: safeStart, end: safeEnd }
|
|
356
|
+
: { start: safeEnd, end: safeStart };
|
|
357
|
+
}
|
|
358
|
+
handleSelectionChange() {
|
|
359
|
+
if (!this.selectionOptions.enabled || !this.loaded || this.destroyed) {
|
|
360
|
+
this.hideSelectionToolbar();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
const selection = window.getSelection();
|
|
364
|
+
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
|
|
365
|
+
this.hideSelectionToolbar();
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const range = selection.getRangeAt(0);
|
|
369
|
+
const startContainer = this.resolveTextContainer(range.startContainer);
|
|
370
|
+
const endContainer = this.resolveTextContainer(range.endContainer);
|
|
371
|
+
if (!startContainer || !endContainer || startContainer !== endContainer) {
|
|
372
|
+
this.hideSelectionToolbar();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const offsets = this.getSelectionOffsets(startContainer, selection);
|
|
376
|
+
if (!offsets) {
|
|
377
|
+
this.hideSelectionToolbar();
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const pageId = startContainer.dataset.pageId;
|
|
381
|
+
const blockId = startContainer.dataset.blockId;
|
|
382
|
+
if (!pageId || !blockId) {
|
|
383
|
+
this.hideSelectionToolbar();
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const rect = range.getBoundingClientRect();
|
|
387
|
+
if (!rect.width && !rect.height) {
|
|
388
|
+
this.hideSelectionToolbar();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
this.selectionDraft = {
|
|
392
|
+
pageId,
|
|
393
|
+
blockId,
|
|
394
|
+
range: offsets,
|
|
395
|
+
text: selection.toString().trim(),
|
|
396
|
+
};
|
|
397
|
+
this.selectionToolbar.style.left = `${Math.round(rect.left + rect.width / 2)}px`;
|
|
398
|
+
this.selectionToolbar.style.top = `${Math.round(rect.top - 8)}px`;
|
|
399
|
+
this.selectionToolbar.classList.add("show");
|
|
400
|
+
}
|
|
401
|
+
resolveTextContainer(node) {
|
|
402
|
+
if (!node) {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
const baseElement = node.nodeType === Node.ELEMENT_NODE
|
|
406
|
+
? node
|
|
407
|
+
: node.parentElement;
|
|
408
|
+
if (!baseElement) {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
const textContainer = baseElement.closest(".hyepl-block-text");
|
|
412
|
+
if (!textContainer || !this.content.contains(textContainer)) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
return textContainer;
|
|
416
|
+
}
|
|
417
|
+
getSelectionOffsets(container, selection) {
|
|
418
|
+
if (selection.rangeCount === 0) {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
const range = selection.getRangeAt(0);
|
|
422
|
+
const startRange = document.createRange();
|
|
423
|
+
startRange.selectNodeContents(container);
|
|
424
|
+
startRange.setEnd(range.startContainer, range.startOffset);
|
|
425
|
+
const endRange = document.createRange();
|
|
426
|
+
endRange.selectNodeContents(container);
|
|
427
|
+
endRange.setEnd(range.endContainer, range.endOffset);
|
|
428
|
+
const start = startRange.toString().length;
|
|
429
|
+
const end = endRange.toString().length;
|
|
430
|
+
return this.normalizeOffsetRange(start, end);
|
|
431
|
+
}
|
|
432
|
+
hideSelectionToolbar() {
|
|
433
|
+
this.selectionToolbar.classList.remove("show");
|
|
434
|
+
this.selectionDraft = null;
|
|
435
|
+
}
|
|
436
|
+
clearSelection() {
|
|
437
|
+
window.getSelection()?.removeAllRanges();
|
|
438
|
+
}
|
|
439
|
+
handleDocumentPointerDown(event) {
|
|
440
|
+
const target = event.target;
|
|
441
|
+
if (!target) {
|
|
442
|
+
this.hideSelectionToolbar();
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (this.selectionToolbar.contains(target)) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (!this.root.contains(target)) {
|
|
449
|
+
this.hideSelectionToolbar();
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
preserveSelectionOnToolbarMouseDown(event) {
|
|
453
|
+
event.preventDefault();
|
|
454
|
+
event.stopPropagation();
|
|
455
|
+
}
|
|
456
|
+
async handleHighlightClick() {
|
|
457
|
+
if (!this.selectionDraft) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const payload = {
|
|
461
|
+
pageId: this.selectionDraft.pageId,
|
|
462
|
+
blockId: this.selectionDraft.blockId,
|
|
463
|
+
range: this.selectionDraft.range,
|
|
464
|
+
...(this.selectionDraft.text
|
|
465
|
+
? { selectedText: this.selectionDraft.text }
|
|
466
|
+
: {}),
|
|
467
|
+
...(this.selectionOptions.highlightColor
|
|
468
|
+
? { color: this.selectionOptions.highlightColor }
|
|
469
|
+
: {}),
|
|
470
|
+
};
|
|
471
|
+
await this.createHighlight(payload, "user");
|
|
472
|
+
this.clearSelection();
|
|
473
|
+
this.hideSelectionToolbar();
|
|
474
|
+
}
|
|
475
|
+
async handleNoteClick() {
|
|
476
|
+
if (!this.selectionDraft) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
const defaultText = this.selectionDraft.text || "";
|
|
480
|
+
const noteText = window.prompt("请输入笔记内容", defaultText);
|
|
481
|
+
const content = noteText?.trim();
|
|
482
|
+
if (!content) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const payload = {
|
|
486
|
+
pageId: this.selectionDraft.pageId,
|
|
487
|
+
blockId: this.selectionDraft.blockId,
|
|
488
|
+
range: this.selectionDraft.range,
|
|
489
|
+
content,
|
|
490
|
+
...(this.selectionDraft.text
|
|
491
|
+
? { selectedText: this.selectionDraft.text }
|
|
492
|
+
: {}),
|
|
493
|
+
...(this.selectionOptions.noteColor
|
|
494
|
+
? { color: this.selectionOptions.noteColor }
|
|
495
|
+
: {}),
|
|
496
|
+
};
|
|
497
|
+
await this.createNote(payload, "user");
|
|
498
|
+
this.clearSelection();
|
|
499
|
+
this.hideSelectionToolbar();
|
|
500
|
+
}
|
|
501
|
+
resolveWidth(width) {
|
|
502
|
+
if (!Number.isFinite(width)) {
|
|
503
|
+
return 840;
|
|
504
|
+
}
|
|
505
|
+
return Math.max(320, Math.round(width));
|
|
506
|
+
}
|
|
507
|
+
resolveSelectionToolbarOptions(options) {
|
|
508
|
+
const highlightButtonText = typeof options?.highlightButtonText === "string" &&
|
|
509
|
+
options.highlightButtonText.trim()
|
|
510
|
+
? options.highlightButtonText.trim()
|
|
511
|
+
: "高亮";
|
|
512
|
+
const noteButtonText = typeof options?.noteButtonText === "string" &&
|
|
513
|
+
options.noteButtonText.trim()
|
|
514
|
+
? options.noteButtonText.trim()
|
|
515
|
+
: "笔记";
|
|
516
|
+
return {
|
|
517
|
+
enabled: options?.enabled !== false,
|
|
518
|
+
highlightButtonText,
|
|
519
|
+
noteButtonText,
|
|
520
|
+
highlightColor: typeof options?.highlightColor === "string" &&
|
|
521
|
+
options.highlightColor.trim()
|
|
522
|
+
? options.highlightColor.trim()
|
|
523
|
+
: "#fff59d",
|
|
524
|
+
noteColor: typeof options?.noteColor === "string" && options.noteColor.trim()
|
|
525
|
+
? options.noteColor.trim()
|
|
526
|
+
: "#0e7490",
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
ensureLoaded() {
|
|
530
|
+
if (!this.loaded) {
|
|
531
|
+
throw new Error("Document is not loaded. Call load() first.");
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function ensureEBookPlayerStyles() {
|
|
536
|
+
const existing = document.getElementById(EBOOK_PLAYER_STYLE_ID);
|
|
537
|
+
if (existing instanceof HTMLStyleElement) {
|
|
538
|
+
if (existing.dataset.hyeplVersion !== EBOOK_PLAYER_STYLE_VERSION ||
|
|
539
|
+
existing.textContent !== EBOOK_PLAYER_CSS) {
|
|
540
|
+
existing.textContent = EBOOK_PLAYER_CSS;
|
|
541
|
+
existing.dataset.hyeplVersion = EBOOK_PLAYER_STYLE_VERSION;
|
|
542
|
+
}
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const style = document.createElement("style");
|
|
546
|
+
style.id = EBOOK_PLAYER_STYLE_ID;
|
|
547
|
+
style.dataset.hyeplVersion = EBOOK_PLAYER_STYLE_VERSION;
|
|
548
|
+
style.textContent = EBOOK_PLAYER_CSS;
|
|
549
|
+
document.head.append(style);
|
|
550
|
+
}
|