@embedpdf-editor/chapter-snippet 0.3.0 → 0.3.1

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/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @embedpdf-editor/chapter-snippet
2
2
 
3
- 章节 PDF 阅读器独立运行时(Preact + Web Component),供 **Vue 2.6** 等宿主通过薄包装挂载,无需引入 Vue 3。
3
+ 框架无关的章节 PDF 阅读器运行时。它把 Preact 阅读器封装成 Web Component,适合 Vue 2、传统页面、微前端或任何不想引入 React/Vue3 渲染器的宿主。
4
+
5
+ 能力与 React/Vue3 渲染器保持一致:多章连续滚动、划线/高亮、附注、段落书签、章节目录和标注导入导出。
4
6
 
5
7
  ## 安装
6
8
 
@@ -8,61 +10,171 @@
8
10
  pnpm add @embedpdf-editor/chapter-snippet
9
11
  ```
10
12
 
13
+ ## Vite
14
+
15
+ `chapterSnippetViteResolve()` 用于 Vue2/snippet 场景:
16
+
17
+ ```ts
18
+ // vite.config.ts
19
+ import { defineConfig } from 'vite';
20
+ import { chapterSnippetViteResolve } from '@embedpdf-editor/chapter-snippet/vite';
21
+
22
+ export default defineConfig({
23
+ ...chapterSnippetViteResolve(),
24
+ });
25
+ ```
26
+
27
+ 它会:
28
+
29
+ | 配置 | 作用 |
30
+ | --- | --- |
31
+ | `server.headers` | 开发环境添加 COOP/COEP,满足 worker/wasm 常见要求 |
32
+ | `optimizeDeps.exclude` | 避免 Vite 二次预构建 snippet 和 PDFium 引擎 |
33
+
11
34
  ## 快速开始
12
35
 
36
+ 不需要把 `pdfium.wasm` 手动放到宿主项目的 `public/`。默认会从 `@embedpdf-editor/chapter-snippet/dist/pdfium.wasm` 加载。只有当你要走 CDN 或自定义静态域名时,才需要传 `wasmUrl`。
37
+
13
38
  ```js
14
- import ChapterEmbedPDF, { createChapterViewerEditorOptions } from '@embedpdf-editor/chapter-snippet';
39
+ import ChapterEmbedPDF, {
40
+ CHAPTER_SNIPPET_EVENTS,
41
+ } from '@embedpdf-editor/chapter-snippet';
15
42
 
16
- const container = ChapterEmbedPDF.init({
43
+ const viewer = ChapterEmbedPDF.init({
17
44
  type: 'container',
18
- target: document.getElementById('viewer'),
19
- wasmUrl: '/pdfium.wasm',
45
+ target: document.getElementById('reader'),
20
46
  worker: true,
21
- editorInput: createChapterViewerEditorOptions({ manifest, bookmarks, notes }),
47
+ options: {
48
+ manifest: {
49
+ chapters: [
50
+ {
51
+ chapterId: 'ch-1',
52
+ title: '第一章',
53
+ globalPageRange: [1, 12],
54
+ localPageRange: [0, 11],
55
+ source: { url: '/pdfs/chapter-1.pdf' },
56
+ },
57
+ ],
58
+ },
59
+ notes: {
60
+ loadNotes: async () => [],
61
+ onUpdateNote: async () => {},
62
+ onDeleteNote: async () => {},
63
+ },
64
+ bookmarks: {
65
+ load: async () => [],
66
+ persist: async () => {},
67
+ onRequestRemove: async () => true,
68
+ },
69
+ features: {
70
+ markup: true,
71
+ bookmarks: true,
72
+ notes: true,
73
+ selectionToolbar: true,
74
+ zoom: { pageWidth: 720 },
75
+ },
76
+ },
77
+ });
78
+
79
+ viewer?.addEventListener(CHAPTER_SNIPPET_EVENTS.noteRequestCreate, (event) => {
80
+ const { draft, complete } = event.detail;
81
+ openNoteDialog(draft, async (content) => {
82
+ const noteId = await saveNote(content, draft);
83
+ await complete(noteId);
84
+ });
22
85
  });
23
86
 
24
- container.addEventListener('chapter-note-request-create', (e) => {
25
- console.log(e.detail);
87
+ viewer?.addEventListener(CHAPTER_SNIPPET_EVENTS.ready, (event) => {
88
+ console.log('registry ready', event.detail.registry);
26
89
  });
27
90
  ```
28
91
 
29
- ## WASM / Worker
92
+ 容器需要稳定高度:
30
93
 
31
- 构建产物 `dist/` 包含:
94
+ ```css
95
+ #reader {
96
+ height: 100vh;
97
+ min-height: 0;
98
+ }
99
+ ```
32
100
 
33
- - `embedpdf-chapter.js` — 主入口(ESM)
34
- - `pdfium.wasm` — 需与 JS 同域可访问,或通过 `wasmUrl` 指定
101
+ ## 配置
35
102
 
36
- 开发服务器需设置 COOP/COEP(SharedArrayBuffer):
103
+ | 字段 | 默认 | 说明 |
104
+ | --- | --- | --- |
105
+ | `type` | 必填 | 目前使用 `'container'` |
106
+ | `target` | 必填 | 挂载目标元素 |
107
+ | `options` | 必填 | 推荐的章节阅读器配置,和 React/Vue3 的 `options` 语义一致 |
108
+ | `wasmUrl` | `dist/pdfium.wasm` | 自定义 PDFium wasm 地址 |
109
+ | `worker` | `true` | 是否启用 PDFium worker |
110
+ | `fallbackToDirectEngine` | `true` | 首章 worker 加载超时后自动降级到 direct engine |
111
+ | `workerOpenTimeoutMs` | `8000` | 首章加载超时时间 |
112
+ | `features` | - | 兼容旧写法;会覆盖 `options.features` 中同名项 |
113
+ | `className` / `viewportClassName` | - | 传给内部阅读器容器 |
37
114
 
38
- ```
39
- Cross-Origin-Opener-Policy: same-origin
40
- Cross-Origin-Embedder-Policy: require-corp
41
- ```
115
+ 旧版本的 `editorInput` 仍可使用,但新代码应改为 `options`。`options.features` 会参与合并,顶层 `features` 仅作为兼容覆盖保留。
116
+
117
+ ## 事件
118
+
119
+ snippet 通过 DOM `CustomEvent` 把需要宿主 UI 参与的动作抛出来。
42
120
 
43
- ## CustomEvent
121
+ | 常量 | 事件名 | detail |
122
+ | --- | --- | --- |
123
+ | `noteRequestCreate` | `chapter-note-request-create` | `{ draft, complete }` |
124
+ | `noteRequestEdit` | `chapter-note-request-edit` | `{ noteId, anchor }` |
125
+ | `selectionExtraAction` | `chapter-selection-extra-action` | `{ actionId }` |
126
+ | `ready` | `chapter-viewer-ready` | `{ registry }` |
44
127
 
45
- | 事件名 | 说明 |
46
- |--------|------|
47
- | `chapter-note-request-create` | 划词创建笔记 |
48
- | `chapter-note-request-edit` | 编辑笔记 |
49
- | `chapter-selection-extra-action` | 划词工具栏扩展动作 |
50
- | `chapter-viewer-ready` | 引擎就绪,`detail.registry` |
128
+ 附注创建在 snippet 中默认走事件流:宿主展示自己的弹窗、保存内容,然后调用 `complete(noteId)` 让阅读器把锚点与业务 noteId 关联起来。
51
129
 
52
- 常量:`CHAPTER_SNIPPET_EVENTS`
130
+ ## Vue 2 用法
53
131
 
54
- ## Vue 2.6
132
+ Vue 2 组件只需要在 `mounted` 时初始化,在 `beforeDestroy` 时销毁:
55
133
 
56
134
  ```js
57
- import ChapterEmbedPDF, { createChapterViewerEditorOptions } from '@embedpdf-editor/chapter-snippet';
135
+ import ChapterEmbedPDF from '@embedpdf-editor/chapter-snippet';
136
+
137
+ export default {
138
+ props: {
139
+ options: { type: Object, required: true },
140
+ },
141
+ mounted() {
142
+ this.viewer = ChapterEmbedPDF.init({
143
+ type: 'container',
144
+ target: this.$refs.host,
145
+ options: this.options,
146
+ worker: true,
147
+ });
148
+ },
149
+ beforeDestroy() {
150
+ this.viewer?.destroy?.();
151
+ },
152
+ };
58
153
  ```
59
154
 
60
- Demo:`examples/chapter-viewer-demo-vue2`。Vite 需 `optimizeDeps.exclude: ['@embedpdf-editor/chapter-snippet', '@embedpdf/engines']`,并将 `pdfium.wasm` 放到站点根路径。
155
+ 完整示例见 `examples/chapter-viewer-demo-vue2`。
156
+
157
+ ## 标注导入导出
158
+
159
+ 包内直接导出章节标注 IO:
160
+
161
+ ```js
162
+ import {
163
+ exportAllChapterAnnotations,
164
+ importChapterAnnotationsArchive,
165
+ chapterAnnotationsArchiveToJson,
166
+ parseChapterAnnotationsArchiveJson,
167
+ } from '@embedpdf-editor/chapter-snippet';
168
+ ```
61
169
 
62
- 也可使用 `@embedpdf-editor/vue2-chapter-viewer` 薄包装(内部同样 `import` 本包)。
170
+ snippet 中可通过 `chapter-viewer-ready` 事件拿到 `registry`,再调用这些 API。导出格式包含 `bookmarks`、`notes`、`markup`,版本常量为 `CHAPTER_ANNOTATIONS_ARCHIVE_VERSION`。
63
171
 
64
- ## 与 vue3-chapter-viewer 差异
172
+ ## 常见问题
65
173
 
66
- - 渲染在 Shadow DOM 内的 Preact 岛,宿主仅负责配置与弹窗
67
- - 不依赖 `@embedpdf-editor/editor-engine/vue`(Vue 3)
68
- - 能力集与 `DEFAULT_CHAPTER_VIEWER_FEATURES` 对齐
174
+ | 现象 | 处理 |
175
+ | --- | --- |
176
+ | 卡在“正在加载 PDFium” | 检查 `dist/pdfium.wasm` 是否随包发布并能被浏览器访问;自定义部署时传 `wasmUrl` |
177
+ | 卡在“正在加载 xxx.pdf” | 优先确认章节 PDF URL 是否 200;如只在 worker 模式出现,可保留默认 fallback 或临时设 `worker: false` 定位环境问题 |
178
+ | 只显示空白 | 确认宿主元素有高度,且没有被父容器 `overflow`/flex 布局压到 0 |
179
+ | 划词后没有笔记弹窗 | 监听 `chapter-note-request-create`,保存后调用 `detail.complete(noteId)` |
180
+ | Vue/Vite 开发环境异常预构建 | 使用 `chapterSnippetViteResolve()`,确保 snippet 和 PDFium 引擎没有被 optimizeDeps 预构建 |
@@ -32,6 +32,11 @@ declare interface BookmarkMarkerUiConfig_2 {
32
32
  iconSize?: number;
33
33
  }
34
34
 
35
+ export declare function buildChapterViewerCatalog(nodes: ChapterTreeInput[]): {
36
+ tree: ChapterTreeNode_2[];
37
+ manifest: ChapterManifest;
38
+ };
39
+
35
40
  export declare function buildParagraphBookmarkAnchor(chapterId: string, localPageIndex: number, rectsPdfCoord: Rect[], virtualPage?: {
36
41
  globalPageIndex: number;
37
42
  globalPageNumber: number;
@@ -113,6 +118,8 @@ export declare interface ChapterDescriptor {
113
118
  ownedGlobalPages?: number[];
114
119
  }
115
120
 
121
+ export declare function chapterDescriptorFromTreeNode(node: ChapterTreeInput): ChapterDescriptor;
122
+
116
123
  declare const ChapterEmbedPDF: {
117
124
  version: string;
118
125
  init: (config: ContainerInitConfig) => EmbedPdfChapterContainer | undefined;
@@ -261,7 +268,9 @@ export declare type ChapterSource = {
261
268
  url: string;
262
269
  } | {
263
270
  buffer: ArrayBuffer;
264
- } | {
271
+ }
272
+ /** 按章预处理:拉取、解密、转换后返回 url 或 buffer */
273
+ | {
265
274
  load: () => Promise<ChapterPdfPayload>;
266
275
  };
267
276
 
@@ -275,8 +284,31 @@ declare interface ChapterStatusEvent {
275
284
  };
276
285
  }
277
286
 
287
+ /**
288
+ * 业务目录树输入:父节点页范围可覆盖子节点;flatten 后每条对应一个可加载的 PDF 章节。
289
+ * 父节点若仅作分组、无独立 PDF,请勿放入 manifest(不要 flatten 该节点)。
290
+ */
291
+ export declare type ChapterTreeInput = {
292
+ chapterId: string;
293
+ title: string;
294
+ startPage: number;
295
+ endPage: number;
296
+ source?: ChapterSource;
297
+ encrypted?: boolean;
298
+ children?: ChapterTreeInput[];
299
+ };
300
+
278
301
  export { ChapterTreeNode }
279
302
 
303
+ /** 侧栏目录树节点(仅 UI;渲染以 flatten 后的 manifest 为准) */
304
+ declare type ChapterTreeNode_2 = {
305
+ id: string;
306
+ title: string;
307
+ startPage: number;
308
+ endPage: number;
309
+ children?: ChapterTreeNode_2[];
310
+ };
311
+
280
312
  export { ChapterViewerCatalog }
281
313
 
282
314
  /**
@@ -335,12 +367,19 @@ export declare interface ChapterViewerOptions {
335
367
  /** 书签持久化与删除回调 */
336
368
  bookmarks: ParagraphBookmarkCallbacks;
337
369
  chapterPdfLoader?: IChapterPdfLoader;
370
+ /**
371
+ * 同一全局页被多个章节条目覆盖时,用哪一个章节的 PDF 渲染(非 owner 的章节在该页不绘制真实 PDF)。
372
+ * - `{ kind: 'first-wins' }`:manifest 中先出现的章节(默认)
373
+ * - `{ kind: 'last-wins' }`:后出现的章节(目录 flatten 后常用于「以当前页最后一节为准」)
374
+ * 亦可用 `overlapStrategyForSamePageOwner('first' | 'last')`(chapter-core 包)
375
+ */
376
+ overlapStrategy?: OverlapOwnerStrategy;
338
377
  /** 功能开关与样式;省略则全开 */
339
378
  features?: ChapterViewerConfig;
340
379
  }
341
380
 
342
381
  export declare type ChapterViewerSnippetConfig = {
343
- /** PDFium wasm 地址,默认 jsdelivr */
382
+ /** PDFium wasm 地址;默认使用 chapter-snippet/dist/pdfium.wasm */
344
383
  wasmUrl?: string;
345
384
  /** 是否使用 worker 引擎,默认 true */
346
385
  worker?: boolean;
@@ -348,8 +387,10 @@ export declare type ChapterViewerSnippetConfig = {
348
387
  fallbackToDirectEngine?: boolean;
349
388
  /** 首章加载超时时间;用于 worker fallback,默认 8000ms */
350
389
  workerOpenTimeoutMs?: number;
351
- /** 章节编辑器选项(manifest + 书签/笔记回调) */
352
- editorInput: CreateChapterViewerEditorOptionsInput;
390
+ /** 推荐:章节阅读器配置(manifest + 书签/笔记回调 + 可选 features) */
391
+ options?: ChapterViewerOptions;
392
+ /** @deprecated 请改用 options */
393
+ editorInput?: CreateChapterViewerEditorOptionsInput;
353
394
  /** 阅读器功能开关,默认与 DEFAULT_CHAPTER_VIEWER_FEATURES 合并 */
354
395
  features?: ChapterViewerFeaturesConfig;
355
396
  className?: string;
@@ -437,6 +478,8 @@ export declare const DEFAULT_CHAPTER_VIEWER_CONFIG: ChapterViewerConfig;
437
478
  /** 默认全开的功能配置(内部规范化结果) */
438
479
  export declare const DEFAULT_CHAPTER_VIEWER_FEATURES: ChapterViewerFeaturesConfig;
439
480
 
481
+ export declare const defaultOverlapStrategy: OverlapOwnerStrategy;
482
+
440
483
  export declare function downloadChapterAnnotationsArchive(archive: ChapterAnnotationsArchive, filename?: string): void;
441
484
 
442
485
  export declare function downloadChapterAnnotationsSnapshot(snapshot: ChapterAnnotationsSnapshot, filename?: string): void;
@@ -486,6 +529,9 @@ export declare type ExportChapterAnnotationsOptions = ChapterAnnotationsContentF
486
529
  ensureChapterLoaded?: boolean;
487
530
  };
488
531
 
532
+ /** 深度优先展开目录树为 manifest.chapters(含父、子各一条,与 demo flatten 一致) */
533
+ export declare function flattenChapterTree(nodes: ChapterTreeInput[]): ChapterDescriptor[];
534
+
489
535
  declare interface HoverBookmarkUiConfig {
490
536
  /** 悬停行末「添加书签」自定义图标;无背景 */
491
537
  renderAddIcon?: () => unknown;
@@ -498,8 +544,14 @@ declare interface HoverBookmarkUiConfig_2 {
498
544
  }
499
545
 
500
546
  /**
501
- * 全局章节 PDF 加载器:当 `ChapterDescriptor.source` 未提供 url/buffer/load 时使用。
502
- * 适合统一走业务 API、鉴权下载等场景。
547
+ * 全局章节 PDF 加载器:在打开章节前由引擎调用,用于预处理/拉取 PDF。
548
+ *
549
+ * 优先级(`ChapterManagerPlugin.resolvePdfPayload`):
550
+ * 1. `chapter.source.url` / `source.buffer` — 直接使用
551
+ * 2. `chapter.source.load()` — 按章异步,返回 `{ url }` 或 `{ buffer }`
552
+ * 3. `chapterPdfLoader.loadPdf(chapter)` — 全局统一逻辑(鉴权、解密、转 blob URL 等)
553
+ *
554
+ * 适合:所有章节走同一套 API、在内存中解密后再以 buffer 打开、动态签名 URL 等。
503
555
  */
504
556
  export declare interface IChapterPdfLoader {
505
557
  loadPdf(chapter: ChapterDescriptor): Promise<ChapterPdfPayload>;
@@ -658,12 +710,17 @@ declare interface OperationSpec {
658
710
  }
659
711
 
660
712
  /**
661
- * 决定相邻章节在重叠页上谁拥有该页(owner)。
662
- * - first-wins / last-wins:按 manifest 顺序的先到/后到
663
- * - explicit:依赖 `ChapterDescriptor.ownedGlobalPages`
664
- * - custom:开发者完全接管
713
+ * 决定同一全局页被多个章节 manifest 条目覆盖时,用哪一个章节的 PDF 渲染(owner)。
714
+ * owner 章节在该页仅占位/不绘制真实 PDF,避免重复。
715
+ *
716
+ * - **first-wins**:manifest 中**先**出现的章节(默认)
717
+ * - **last-wins**:**后**出现的章节(目录 flatten 后,重叠页常以「当前页最后一节」为准)
718
+ * - **explicit**:`ChapterDescriptor.ownedGlobalPages` 声明归属
719
+ * - **custom**:`resolve(globalPage, candidates)` 完全自定义
720
+ *
721
+ * 简写:`overlapStrategyForSamePageOwner('first' | 'last')`(chapter-core)
665
722
  */
666
- declare type OverlapOwnerStrategy = {
723
+ export declare type OverlapOwnerStrategy = {
667
724
  kind: 'first-wins';
668
725
  } | {
669
726
  kind: 'last-wins';
@@ -674,6 +731,8 @@ declare type OverlapOwnerStrategy = {
674
731
  resolve: (globalPage: number, candidates: ChapterDescriptor[]) => string;
675
732
  };
676
733
 
734
+ export declare function overlapStrategyForSamePageOwner(owner: SamePageOverlapOwner): OverlapOwnerStrategy;
735
+
677
736
  export declare interface ParagraphBookmark {
678
737
  id: string;
679
738
  label: string;
@@ -734,6 +793,9 @@ export declare interface PdfChapterEditorNotesConfig {
734
793
  declare interface PdfChapterEditorToolbarConfig extends CreateEditorUiSchemaOptions {
735
794
  }
736
795
 
796
+ /** 同一全局页多章节重叠时,选用 manifest 中先/后出现的章节 PDF */
797
+ export declare type SamePageOverlapOwner = 'first' | 'last';
798
+
737
799
  /** 划线类型 */
738
800
  declare type SelectionMarkupKind = 'highlight' | 'underline' | 'squiggly' | 'strikeout';
739
801
 
@@ -763,6 +825,8 @@ export declare interface SerializableAnnotationTransferItem {
763
825
  };
764
826
  }
765
827
 
828
+ export declare function toChapterTreeNodes(nodes: ChapterTreeInput[]): ChapterTreeNode_2[];
829
+
766
830
  export declare const version = "0.1.0";
767
831
 
768
832
  /**