@hyebook/vue3-adapter 2.3.2 → 2.3.4

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.
@@ -1,6 +1,6 @@
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.1.0";
3
+ const EBOOK_PLAYER_STYLE_VERSION = "0.2.0";
4
4
  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
5
  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
6
  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>`;
@@ -9,15 +9,33 @@ const EBOOK_PLAYER_CSS = `
9
9
  .hyepl-content{position:relative}
10
10
  .hyepl-page{margin:0 0 16px}
11
11
  .hyepl-page:last-child{margin-bottom:0}
12
- .hyepl-block{margin:0 0 10px;white-space:pre-wrap;word-break:break-word}
12
+ .hyepl-block{margin:0 0 10px;word-break:break-word}
13
13
  .hyepl-block:last-child{margin-bottom:0}
14
- .hyepl-block-text{position:relative;padding-right:18px}
14
+ .hyepl-block-text{position:relative;padding-right:18px;white-space:normal}
15
+ .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}
16
+ .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}
17
+ .hyepl-block-text h1{font-size:24px;line-height:1.3}
18
+ .hyepl-block-text h2{font-size:20px;line-height:1.35}
19
+ .hyepl-block-text h3{font-size:16px;line-height:1.45}
20
+ .hyepl-block-text h4{font-size:14px;line-height:1.5}
21
+ .hyepl-block-text blockquote{padding-left:12px;border-left:3px solid #cbd5e1;color:#475569}
22
+ .hyepl-block-text sup,.hyepl-block-text sub{font-size:.6em;line-height:0;position:relative;vertical-align:baseline}
23
+ .hyepl-block-text sup{top:-.5em}
24
+ .hyepl-block-text sub{top:.5em}
25
+ .hyepl-inline-media{display:inline-block;vertical-align:middle;max-width:100%}
26
+ .hyepl-inline-image{height:auto;border-radius:6px}
27
+ .hyepl-inline-video{border-radius:8px;background:#000}
28
+ .hyepl-block-image img,.hyepl-block-video video{display:block;max-width:100%;width:100%;height:auto;border-radius:8px}
29
+ .hyepl-block-image figcaption,.hyepl-block-video figcaption{margin-top:6px;font-size:12px;color:#64748b}
30
+ .hyepl-block-table{overflow:auto}
31
+ .hyepl-block-table table{width:100%;border-collapse:collapse;table-layout:fixed}
32
+ .hyepl-block-table th,.hyepl-block-table td{border:1px solid #cbd5e1;padding:6px 8px;vertical-align:top;min-width:68px}
15
33
  .hyepl-block-muted{color:#64748b}
16
34
  .hyepl-highlight{background:var(--hyepl-highlight-color,#fff59d);color:inherit;padding:0 .08em;border-radius:.2em}
17
35
  .hyepl-note{background:rgba(14,116,144,.14);border-bottom:2px solid #0e7490;color:inherit;padding:0 .04em;border-radius:.2em}
18
36
  .hyepl-bookmark-flag{position:absolute;top:4px;right:0;display:inline-flex;align-items:center;justify-content:center;width:12px;height:16px;pointer-events:none;user-select:none}
19
37
  .hyepl-bookmark-flag svg{display:block;width:12px;height:16px}
20
- .hyepl-selection-toolbar{position:fixed;z-index:10060;display:none;align-items:stretch;gap:0;padding:8px 10px;border-radius:14px;border:1px solid #e5e7eb;background:#ffffff;box-shadow:0 10px 24px rgba(15,23,42,.14);transform:translate(-50%,-100%);overflow:visible}
38
+ .hyepl-selection-toolbar{position:fixed;z-index:10070;display:none;align-items:stretch;gap:0;padding:8px 10px;border-radius:14px;border:1px solid #e5e7eb;background:#ffffff;box-shadow:0 10px 24px rgba(15,23,42,.14);transform:translate(-50%,-100%);overflow:visible}
21
39
  .hyepl-selection-toolbar.show{display:flex}
22
40
  .hyepl-selection-toolbar:after{content:"";position:absolute;bottom:-10px;left:50%;transform:translateX(-50%);border-width:10px 10px 0 10px;border-style:solid;border-color:#ffffff transparent transparent transparent;filter:drop-shadow(0 1px 0 #e5e7eb);pointer-events:none}
23
41
 
@@ -248,7 +266,7 @@ export class EBookPlayer {
248
266
  blockTextNode.className = "hyepl-block hyepl-block-text";
249
267
  blockTextNode.dataset.pageId = pageId;
250
268
  blockTextNode.dataset.blockId = block.id;
251
- blockTextNode.textContent = this.getTextBlockContent(block);
269
+ blockTextNode.innerHTML = this.textBlockToHTML(block);
252
270
  const blockKey = this.toBlockKey(pageId, block.id);
253
271
  const highlights = buckets.highlightsByBlock.get(blockKey) || [];
254
272
  const notes = buckets.notesByBlock.get(blockKey) || [];
@@ -261,20 +279,17 @@ export class EBookPlayer {
261
279
  return blockTextNode;
262
280
  }
263
281
  renderNonTextBlock(block) {
264
- const node = document.createElement("div");
265
- node.className = "hyepl-block hyepl-block-muted";
266
282
  if (block.type === "image") {
267
- node.textContent = "[图片内容]";
268
- return node;
283
+ return this.renderImageBlock(block);
269
284
  }
270
285
  if (block.type === "video") {
271
- node.textContent = "[视频内容]";
272
- return node;
286
+ return this.renderVideoBlock(block);
273
287
  }
274
288
  if (block.type === "table") {
275
- node.textContent = "[表格内容]";
276
- return node;
289
+ return this.renderTableBlock(block);
277
290
  }
291
+ const node = document.createElement("div");
292
+ node.className = "hyepl-block hyepl-block-muted";
278
293
  node.textContent = "[内容块]";
279
294
  return node;
280
295
  }
@@ -285,21 +300,248 @@ export class EBookPlayer {
285
300
  })
286
301
  .join("\n");
287
302
  }
303
+ textBlockToHTML(block) {
304
+ const paragraphs = block.content?.paragraphs ?? [];
305
+ if (!paragraphs.length) {
306
+ return "<p><br></p>";
307
+ }
308
+ return paragraphs
309
+ .map((paragraph) => {
310
+ const tag = this.paragraphTypeToTag(paragraph.type);
311
+ const inlineById = Object.fromEntries((paragraph.inlineElements ?? []).map((item) => [item.id, item]));
312
+ const styleTokens = [
313
+ `text-align:${paragraph.align || "left"}`,
314
+ paragraph.lineHeight ? `line-height:${paragraph.lineHeight}` : "",
315
+ paragraph.spacingBefore !== undefined
316
+ ? `margin-top:${paragraph.spacingBefore}px`
317
+ : "",
318
+ paragraph.spacingAfter !== undefined
319
+ ? `margin-bottom:${paragraph.spacingAfter}px`
320
+ : "",
321
+ paragraph.indent !== undefined
322
+ ? `text-indent:${paragraph.indent}em`
323
+ : "",
324
+ ].filter(Boolean);
325
+ const body = paragraph.runs
326
+ ?.map((run) => this.runToHTML(run, inlineById))
327
+ .join("") || "<br>";
328
+ const styleAttr = styleTokens.length
329
+ ? ` style="${escapeAttribute(styleTokens.join(";"))}"`
330
+ : "";
331
+ return `<${tag}${styleAttr}>${body}</${tag}>`;
332
+ })
333
+ .join("");
334
+ }
335
+ paragraphTypeToTag(type) {
336
+ if (type === "h1" || type === "h2" || type === "h3" || type === "h4") {
337
+ return type;
338
+ }
339
+ if (type === "quote") {
340
+ return "blockquote";
341
+ }
342
+ return "p";
343
+ }
344
+ runToHTML(run, inlineById) {
345
+ const inlineElement = run.inlineRef
346
+ ? inlineById?.[run.inlineRef]
347
+ : undefined;
348
+ if (run.inlineRef && inlineElement) {
349
+ return this.inlineElementToHTML(inlineElement);
350
+ }
351
+ let text = escapeHtml(run.text || "").replace(/\n/g, "<br>");
352
+ const marks = run.marks || {};
353
+ const styleTokens = [];
354
+ if (marks.fontSize) {
355
+ styleTokens.push(`font-size:${marks.fontSize}px`);
356
+ }
357
+ if (marks.fontFamily) {
358
+ styleTokens.push(`font-family:${marks.fontFamily}`);
359
+ }
360
+ if (marks.color) {
361
+ styleTokens.push(`color:${marks.color}`);
362
+ }
363
+ if (marks.backgroundColor) {
364
+ styleTokens.push(`background-color:${marks.backgroundColor}`);
365
+ }
366
+ if (styleTokens.length) {
367
+ text = `<span style="${escapeAttribute(styleTokens.join(";"))}">${text}</span>`;
368
+ }
369
+ if (marks.bold) {
370
+ text = `<strong>${text}</strong>`;
371
+ }
372
+ if (marks.italic) {
373
+ text = `<em>${text}</em>`;
374
+ }
375
+ if (marks.underline) {
376
+ text = `<u>${text}</u>`;
377
+ }
378
+ if (marks.strike) {
379
+ text = `<s>${text}</s>`;
380
+ }
381
+ if (marks.sup) {
382
+ text = `<sup>${text}</sup>`;
383
+ }
384
+ if (marks.sub) {
385
+ text = `<sub>${text}</sub>`;
386
+ }
387
+ return text || "<br>";
388
+ }
389
+ inlineElementToHTML(element) {
390
+ if (element.type === "image") {
391
+ const sizeStyle = [
392
+ element.width !== undefined ? `width:${element.width}px` : "",
393
+ element.height !== undefined ? `height:${element.height}px` : "",
394
+ ...(element.align
395
+ ? this.inlineImageAlignStyleParts(element.align)
396
+ : []),
397
+ ]
398
+ .filter(Boolean)
399
+ .join(";");
400
+ return `<img class="hyepl-inline-media hyepl-inline-image" src="${escapeAttribute(element.src)}" alt="${escapeAttribute(element.alt || "")}" style="${escapeAttribute(sizeStyle)}" />`;
401
+ }
402
+ if (element.type === "video") {
403
+ const sizeStyle = [
404
+ element.width !== undefined ? `width:${element.width}px` : "",
405
+ element.height !== undefined ? `height:${element.height}px` : "",
406
+ ]
407
+ .filter(Boolean)
408
+ .join(";");
409
+ const posterAttr = element.poster
410
+ ? ` poster="${escapeAttribute(element.poster)}"`
411
+ : "";
412
+ return `<video class="hyepl-inline-media hyepl-inline-video" src="${escapeAttribute(element.src)}" controls${posterAttr} style="${escapeAttribute(sizeStyle)}"></video>`;
413
+ }
414
+ return "";
415
+ }
416
+ inlineImageAlignStyleParts(align) {
417
+ if (align === "center") {
418
+ return ["display:block", "margin-left:auto", "margin-right:auto"];
419
+ }
420
+ if (align === "right") {
421
+ return ["display:block", "margin-left:auto", "margin-right:0"];
422
+ }
423
+ if (align === "left") {
424
+ return ["display:block", "margin-left:0", "margin-right:auto"];
425
+ }
426
+ return [];
427
+ }
428
+ renderImageBlock(block) {
429
+ const figure = document.createElement("figure");
430
+ figure.className = "hyepl-block hyepl-block-image";
431
+ const image = document.createElement("img");
432
+ image.src = block.src;
433
+ image.alt = block.alt || "";
434
+ image.style.objectFit = block.fit || "contain";
435
+ figure.append(image);
436
+ if (block.caption) {
437
+ const caption = document.createElement("figcaption");
438
+ caption.textContent = block.caption;
439
+ figure.append(caption);
440
+ }
441
+ return figure;
442
+ }
443
+ renderVideoBlock(block) {
444
+ const figure = document.createElement("figure");
445
+ figure.className = "hyepl-block hyepl-block-video";
446
+ const video = document.createElement("video");
447
+ video.src = block.src;
448
+ video.controls = block.controls !== false;
449
+ video.autoplay = Boolean(block.autoplay);
450
+ video.muted = Boolean(block.muted);
451
+ video.loop = Boolean(block.loop);
452
+ if (block.poster) {
453
+ video.poster = block.poster;
454
+ }
455
+ figure.append(video);
456
+ return figure;
457
+ }
458
+ renderTableBlock(block) {
459
+ const wrapper = document.createElement("div");
460
+ wrapper.className = "hyepl-block hyepl-block-table";
461
+ const table = document.createElement("table");
462
+ if (block.style) {
463
+ table.setAttribute("style", block.style);
464
+ }
465
+ const tbody = document.createElement("tbody");
466
+ block.rows.forEach((row) => {
467
+ const tr = document.createElement("tr");
468
+ (row.cells || []).forEach((cell) => {
469
+ const node = document.createElement(cell.isHeader ? "th" : "td");
470
+ const rowSpan = Math.max(1, Math.round(cell.rowSpan || 1));
471
+ const colSpan = Math.max(1, Math.round(cell.colSpan || 1));
472
+ if (rowSpan > 1) {
473
+ node.rowSpan = rowSpan;
474
+ }
475
+ if (colSpan > 1) {
476
+ node.colSpan = colSpan;
477
+ }
478
+ if (cell.style) {
479
+ node.setAttribute("style", cell.style);
480
+ }
481
+ if (cell.textStyle?.color) {
482
+ node.style.color = cell.textStyle.color;
483
+ }
484
+ if (cell.textStyle?.fontFamily) {
485
+ node.style.fontFamily = cell.textStyle.fontFamily;
486
+ }
487
+ if (cell.textStyle?.fontSize !== undefined) {
488
+ node.style.fontSize = `${Math.round(cell.textStyle.fontSize)}px`;
489
+ }
490
+ if (cell.html) {
491
+ node.innerHTML = cell.html;
492
+ }
493
+ else {
494
+ node.textContent = cell.text || "";
495
+ }
496
+ if (!node.textContent && !node.children.length) {
497
+ node.innerHTML = "<br>";
498
+ }
499
+ tr.append(node);
500
+ });
501
+ if (!tr.children.length) {
502
+ const td = document.createElement("td");
503
+ td.innerHTML = "<br>";
504
+ tr.append(td);
505
+ }
506
+ tbody.append(tr);
507
+ });
508
+ if (!tbody.children.length) {
509
+ const tr = document.createElement("tr");
510
+ const td = document.createElement("td");
511
+ td.innerHTML = "<br>";
512
+ tr.append(td);
513
+ tbody.append(tr);
514
+ }
515
+ table.append(tbody);
516
+ wrapper.append(table);
517
+ return wrapper;
518
+ }
288
519
  createAnnotationBuckets(annotations) {
289
520
  const highlightsByBlock = new Map();
290
521
  const notesByBlock = new Map();
291
522
  const bookmarksByBlock = new Map();
292
523
  annotations.highlights.forEach((item) => {
293
- const key = this.toBlockKey(item.pageId, item.blockId);
294
- const current = highlightsByBlock.get(key) || [];
295
- current.push(item);
296
- highlightsByBlock.set(key, current);
524
+ this.resolveAnnotationSegments(item.pageId, item.blockId, item.range, item.segments).forEach((segment) => {
525
+ const key = this.toBlockKey(segment.pageId, segment.blockId);
526
+ const current = highlightsByBlock.get(key) || [];
527
+ current.push({
528
+ id: item.id,
529
+ range: segment.range,
530
+ ...(item.color ? { color: item.color } : {}),
531
+ });
532
+ highlightsByBlock.set(key, current);
533
+ });
297
534
  });
298
535
  annotations.notes.forEach((item) => {
299
- const key = this.toBlockKey(item.pageId, item.blockId);
300
- const current = notesByBlock.get(key) || [];
301
- current.push(item);
302
- notesByBlock.set(key, current);
536
+ this.resolveAnnotationSegments(item.pageId, item.blockId, item.range, item.segments).forEach((segment) => {
537
+ const key = this.toBlockKey(segment.pageId, segment.blockId);
538
+ const current = notesByBlock.get(key) || [];
539
+ current.push({
540
+ id: item.id,
541
+ range: segment.range,
542
+ });
543
+ notesByBlock.set(key, current);
544
+ });
303
545
  });
304
546
  const bookmarks = Array.isArray(annotations.bookmarks)
305
547
  ? annotations.bookmarks
@@ -313,6 +555,39 @@ export class EBookPlayer {
313
555
  toBlockKey(pageId, blockId) {
314
556
  return `${pageId}::${blockId}`;
315
557
  }
558
+ resolveAnnotationSegments(pageId, blockId, range, segments) {
559
+ const resolvedSegments = Array.isArray(segments)
560
+ ? segments
561
+ .map((segment) => {
562
+ const segmentPageId = typeof segment?.pageId === "string" ? segment.pageId : "";
563
+ const segmentBlockId = typeof segment?.blockId === "string" ? segment.blockId : "";
564
+ const normalizedRange = this.normalizeOffsetRange(segment?.range?.start || 0, segment?.range?.end || 0);
565
+ if (!segmentPageId || !segmentBlockId || !normalizedRange) {
566
+ return null;
567
+ }
568
+ return {
569
+ pageId: segmentPageId,
570
+ blockId: segmentBlockId,
571
+ range: normalizedRange,
572
+ ...(segment?.selectedText
573
+ ? { selectedText: segment.selectedText }
574
+ : {}),
575
+ };
576
+ })
577
+ .filter((segment) => segment !== null)
578
+ : [];
579
+ if (resolvedSegments.length) {
580
+ return resolvedSegments;
581
+ }
582
+ if (!pageId || !blockId || !range) {
583
+ return [];
584
+ }
585
+ const normalizedRange = this.normalizeOffsetRange(range.start, range.end);
586
+ if (!normalizedRange) {
587
+ return [];
588
+ }
589
+ return [{ pageId, blockId, range: normalizedRange }];
590
+ }
316
591
  applyHighlightMarks(container, highlights) {
317
592
  [...highlights]
318
593
  .filter((item) => Number.isFinite(item.range.start) && Number.isFinite(item.range.end))
@@ -332,16 +607,12 @@ export class EBookPlayer {
332
607
  }
333
608
  applyNoteMarks(container, notes) {
334
609
  [...notes]
335
- .filter((item) => !!item.range)
336
610
  .sort((a, b) => {
337
- const startA = a.range?.start || 0;
338
- const startB = b.range?.start || 0;
611
+ const startA = a.range.start || 0;
612
+ const startB = b.range.start || 0;
339
613
  return startB - startA;
340
614
  })
341
615
  .forEach((item) => {
342
- if (!item.range) {
343
- return;
344
- }
345
616
  const range = this.createRangeFromOffsets(container, item.range.start, item.range.end);
346
617
  if (!range || range.collapsed) {
347
618
  return;
@@ -438,20 +709,13 @@ export class EBookPlayer {
438
709
  return;
439
710
  }
440
711
  const range = selection.getRangeAt(0);
441
- const startContainer = this.resolveTextContainer(range.startContainer);
442
- const endContainer = this.resolveTextContainer(range.endContainer);
443
- if (!startContainer || !endContainer || startContainer !== endContainer) {
444
- this.hideSelectionToolbar();
445
- return;
446
- }
447
- const offsets = this.getSelectionOffsets(startContainer, selection);
448
- if (!offsets) {
712
+ const segments = this.collectSelectionSegments(selection, range);
713
+ if (!segments?.length) {
449
714
  this.hideSelectionToolbar();
450
715
  return;
451
716
  }
452
- const pageId = startContainer.dataset.pageId;
453
- const blockId = startContainer.dataset.blockId;
454
- if (!pageId || !blockId) {
717
+ const firstSegment = segments[0];
718
+ if (!firstSegment) {
455
719
  this.hideSelectionToolbar();
456
720
  return;
457
721
  }
@@ -461,10 +725,11 @@ export class EBookPlayer {
461
725
  return;
462
726
  }
463
727
  this.selectionDraft = {
464
- pageId,
465
- blockId,
466
- range: offsets,
728
+ pageId: firstSegment.pageId,
729
+ blockId: firstSegment.blockId,
730
+ range: firstSegment.range,
467
731
  text: selection.toString().trim(),
732
+ segments,
468
733
  };
469
734
  this.selectionToolbar.style.left = `${Math.round(rect.left + rect.width / 2)}px`;
470
735
  this.selectionToolbar.style.top = `${Math.round(rect.top - 20)}px`;
@@ -501,6 +766,103 @@ export class EBookPlayer {
501
766
  const end = endRange.toString().length;
502
767
  return this.normalizeOffsetRange(start, end);
503
768
  }
769
+ collectSelectionSegments(selection, range) {
770
+ const startContainer = this.resolveTextContainer(range.startContainer);
771
+ const endContainer = this.resolveTextContainer(range.endContainer);
772
+ if (!startContainer || !endContainer) {
773
+ return null;
774
+ }
775
+ const textContainers = Array.from(this.content.querySelectorAll(".hyepl-block-text"));
776
+ const startIndex = textContainers.indexOf(startContainer);
777
+ const endIndex = textContainers.indexOf(endContainer);
778
+ if (startIndex < 0 || endIndex < 0) {
779
+ return null;
780
+ }
781
+ const from = Math.min(startIndex, endIndex);
782
+ const to = Math.max(startIndex, endIndex);
783
+ const segments = [];
784
+ for (let index = from; index <= to; index += 1) {
785
+ const container = textContainers[index];
786
+ if (!container) {
787
+ continue;
788
+ }
789
+ const pageId = container.dataset.pageId;
790
+ const blockId = container.dataset.blockId;
791
+ if (!pageId || !blockId) {
792
+ continue;
793
+ }
794
+ let start = 0;
795
+ let end = this.getContainerTextLength(container);
796
+ if (container === startContainer) {
797
+ const boundaryOffset = this.getBoundaryOffsetInContainer(container, range.startContainer, range.startOffset);
798
+ if (boundaryOffset === null) {
799
+ continue;
800
+ }
801
+ start = boundaryOffset;
802
+ }
803
+ if (container === endContainer) {
804
+ const boundaryOffset = this.getBoundaryOffsetInContainer(container, range.endContainer, range.endOffset);
805
+ if (boundaryOffset === null) {
806
+ continue;
807
+ }
808
+ end = boundaryOffset;
809
+ }
810
+ const normalized = this.normalizeOffsetRange(start, end);
811
+ if (!normalized) {
812
+ continue;
813
+ }
814
+ const text = this.getRangeTextByOffsets(container, normalized);
815
+ segments.push({
816
+ pageId,
817
+ blockId,
818
+ range: normalized,
819
+ text,
820
+ });
821
+ }
822
+ if (!segments.length) {
823
+ const fallback = this.getSelectionOffsets(startContainer, selection);
824
+ if (!fallback) {
825
+ return null;
826
+ }
827
+ const pageId = startContainer.dataset.pageId;
828
+ const blockId = startContainer.dataset.blockId;
829
+ if (!pageId || !blockId) {
830
+ return null;
831
+ }
832
+ return [
833
+ {
834
+ pageId,
835
+ blockId,
836
+ range: fallback,
837
+ text: selection.toString().trim(),
838
+ },
839
+ ];
840
+ }
841
+ return segments;
842
+ }
843
+ getContainerTextLength(container) {
844
+ const range = document.createRange();
845
+ range.selectNodeContents(container);
846
+ return range.toString().length;
847
+ }
848
+ getBoundaryOffsetInContainer(container, boundaryNode, boundaryOffset) {
849
+ if (!container.contains(boundaryNode) && container !== boundaryNode) {
850
+ return null;
851
+ }
852
+ const range = document.createRange();
853
+ range.selectNodeContents(container);
854
+ try {
855
+ range.setEnd(boundaryNode, boundaryOffset);
856
+ }
857
+ catch {
858
+ return null;
859
+ }
860
+ return range.toString().length;
861
+ }
862
+ getRangeTextByOffsets(container, offsets) {
863
+ const range = this.createRangeFromOffsets(container, offsets.start, offsets.end);
864
+ return range ? range.toString().trim() : "";
865
+ }
504
866
  hideSelectionToolbar() {
505
867
  this.selectionToolbar.classList.remove("show");
506
868
  this.selectionDraft = null;
@@ -529,16 +891,40 @@ export class EBookPlayer {
529
891
  if (!this.selectionDraft) {
530
892
  return;
531
893
  }
894
+ const segments = this.selectionDraft.segments && this.selectionDraft.segments.length
895
+ ? this.selectionDraft.segments
896
+ : [
897
+ {
898
+ pageId: this.selectionDraft.pageId,
899
+ blockId: this.selectionDraft.blockId,
900
+ range: this.selectionDraft.range,
901
+ text: this.selectionDraft.text,
902
+ },
903
+ ];
904
+ const firstSegment = segments[0];
905
+ if (!firstSegment) {
906
+ return;
907
+ }
908
+ const mergedSelectedText = this.selectionDraft.text ||
909
+ segments
910
+ .map((segment) => segment.text)
911
+ .filter(Boolean)
912
+ .join("\n")
913
+ .trim();
532
914
  const payload = {
533
- pageId: this.selectionDraft.pageId,
534
- blockId: this.selectionDraft.blockId,
535
- range: this.selectionDraft.range,
536
- ...(this.selectionDraft.text
537
- ? { selectedText: this.selectionDraft.text }
538
- : {}),
915
+ pageId: firstSegment.pageId,
916
+ blockId: firstSegment.blockId,
917
+ range: firstSegment.range,
918
+ ...(mergedSelectedText ? { selectedText: mergedSelectedText } : {}),
539
919
  ...(this.selectionOptions.highlightColor
540
920
  ? { color: this.selectionOptions.highlightColor }
541
921
  : {}),
922
+ segments: segments.map((segment) => ({
923
+ pageId: segment.pageId,
924
+ blockId: segment.blockId,
925
+ range: segment.range,
926
+ ...(segment.text ? { selectedText: segment.text } : {}),
927
+ })),
542
928
  };
543
929
  await this.createHighlight(payload, "user");
544
930
  this.clearSelection();
@@ -548,22 +934,46 @@ export class EBookPlayer {
548
934
  if (!this.selectionDraft) {
549
935
  return;
550
936
  }
551
- const defaultText = this.selectionDraft.text || "";
937
+ const defaultText = "";
552
938
  const content = this.promptTextWithLimit("请输入笔记内容", defaultText, this.inputLimits.noteMaxLength);
553
939
  if (content === null) {
554
940
  return;
555
941
  }
942
+ const segments = this.selectionDraft.segments && this.selectionDraft.segments.length
943
+ ? this.selectionDraft.segments
944
+ : [
945
+ {
946
+ pageId: this.selectionDraft.pageId,
947
+ blockId: this.selectionDraft.blockId,
948
+ range: this.selectionDraft.range,
949
+ text: this.selectionDraft.text,
950
+ },
951
+ ];
952
+ const firstSegment = segments[0];
953
+ if (!firstSegment) {
954
+ return;
955
+ }
956
+ const mergedSelectedText = this.selectionDraft.text ||
957
+ segments
958
+ .map((segment) => segment.text)
959
+ .filter(Boolean)
960
+ .join("\n")
961
+ .trim();
556
962
  const payload = {
557
- pageId: this.selectionDraft.pageId,
558
- blockId: this.selectionDraft.blockId,
559
- range: this.selectionDraft.range,
963
+ pageId: firstSegment.pageId,
964
+ blockId: firstSegment.blockId,
965
+ range: firstSegment.range,
560
966
  content,
561
- ...(this.selectionDraft.text
562
- ? { selectedText: this.selectionDraft.text }
563
- : {}),
967
+ ...(mergedSelectedText ? { selectedText: mergedSelectedText } : {}),
564
968
  ...(this.selectionOptions.noteColor
565
969
  ? { color: this.selectionOptions.noteColor }
566
970
  : {}),
971
+ segments: segments.map((segment) => ({
972
+ pageId: segment.pageId,
973
+ blockId: segment.blockId,
974
+ range: segment.range,
975
+ ...(segment.text ? { selectedText: segment.text } : {}),
976
+ })),
567
977
  };
568
978
  await this.createNote(payload, "user");
569
979
  this.clearSelection();
@@ -748,3 +1158,14 @@ function createRuntimeProvider(provider, getRuntimeDocument) {
748
1158
  function cloneEbookDoc(doc) {
749
1159
  return JSON.parse(JSON.stringify(doc));
750
1160
  }
1161
+ function escapeHtml(value) {
1162
+ return value
1163
+ .replace(/&/g, "&amp;")
1164
+ .replace(/</g, "&lt;")
1165
+ .replace(/>/g, "&gt;")
1166
+ .replace(/\"/g, "&quot;")
1167
+ .replace(/'/g, "&#39;");
1168
+ }
1169
+ function escapeAttribute(value) {
1170
+ return escapeHtml(value);
1171
+ }
@@ -58,6 +58,7 @@ export declare class PlayerEngine {
58
58
  private loadAnnotationsState;
59
59
  private findPageIndexByPageId;
60
60
  private normalizeRange;
61
+ private normalizeSegments;
61
62
  private normalizeAnnotations;
62
63
  private mapPreviewAnnotationsToReader;
63
64
  private persistAnnotations;
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../../../core/src/player/engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../../../core/src/player/engine.ts"],"names":[],"mappings":"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"}