@embedpdf-editor/chapter-snippet 1.0.0 → 1.0.2

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
@@ -43,7 +43,18 @@ import ChapterEmbedPDF from '@embedpdf-editor/chapter-snippet';
43
43
 
44
44
  发包构建会对 `dist/embedpdf-chapter.js` 做语法降级(去掉 `??` 等 ES2020 语法),Webpack / Vue CLI 默认可 parse。默认 `wasmUrl` 指向 jsDelivr 上的 `pdfium.wasm`,无需复制到 `public/`。
45
45
 
46
- 离线或内网部署时,可传 `wasmUrl: '/pdfium.wasm'`(自行把 `node_modules/@embedpdf-editor/chapter-snippet/dist/pdfium.wasm` 放到静态目录)。
46
+ 离线或内网部署时,可传 `wasmUrl`(**init 顶层**,与 `options` 同级):
47
+
48
+ ```js
49
+ // 相对路径:自行把 dist/pdfium.wasm 放到静态目录
50
+ wasmUrl: '/pdfium.wasm',
51
+
52
+ // 自有 OSS / CDN(完整 HTTPS 地址,示例)
53
+ wasmUrl:
54
+ 'https://hep-editor.oss-cn-beijing.aliyuncs.com/public/editor-public/js/pdfium.wasm',
55
+ ```
56
+
57
+ 详见 [docs/get-started/01-installation.md](../../docs/get-started/01-installation.md)。
47
58
 
48
59
  `@embedpdf-editor/chapter-snippet/webpack` 为**可选**辅助(仅 monorepo Vue 2.6 解析、或自定义 devServer COOP/COEP),普通用户不必使用。
49
60
 
@@ -132,6 +143,25 @@ viewer?.addEventListener(CHAPTER_SNIPPET_EVENTS.ready, (event) => {
132
143
 
133
144
  旧版本的 `editorInput` 仍可使用,但新代码应改为 `options`,并把 `features` 写在 `options.features` 或顶层 `features`。
134
145
 
146
+ ### 章内多 PDF 分段(`source.urls[]`)
147
+
148
+ ```js
149
+ source: {
150
+ urls: [part0Url, part1Url, part2Url],
151
+ segmentPageThreshold: 5, // 例:13 页 → 3 段
152
+ },
153
+ ```
154
+
155
+ - 先加载第一段,滚动懒加载后续段;`localPageIndex` 仍为章内连续页。
156
+ - 引擎内部 `documentId` 为 `chapterId#s0`、`#s1`…;**业务 API / 数据库只存 `chapterId` + `localPageIndex`**。
157
+ - `chapterPdfLoader.loadPdf(chapter, segmentIndex)` 第二参数为段索引(0-based)。
158
+
159
+ [03-manifest.md](../../docs/get-started/03-manifest.md) · [12-segmented-pdf-and-per-chapter-storage.md](../../docs/get-started/12-segmented-pdf-and-per-chapter-storage.md)
160
+
161
+ ### 按章持久化
162
+
163
+ `options.notes` / `options.bookmarks` 回调里的 `chapterId`、`localPageIndex` 与单 URL 章相同。划线备份用 `exportChapterAnnotations`,JSON 键为 `chapters[chapterId]`(导出 markup 时会拉全部分段)。
164
+
135
165
  ### `ChapterViewerOptions`(与 React / Vue3 一致)
136
166
 
137
167
  | 字段 | 说明 |
@@ -155,7 +185,7 @@ viewer?.addEventListener(CHAPTER_SNIPPET_EVENTS.ready, (event) => {
155
185
  | `notes` | `marker.renderIcon`、`renderMenuActions`、`highlightColor` |
156
186
  | `zoom` | `pageWidth`、`min` / `max` / `enabled`;实际上限不超过 `[data-chapter-scroll-viewport]` 宽度,resize 时自动 clamp |
157
187
  | `scrollViewport` | `background`(默认 `#f1f5f9`),`[data-chapter-scroll-viewport]` 背景 |
158
- | `selectionToolbar` | `hiddenBuiltinActions`、`extraActions`;扩展动作监听 `selectionExtraAction` 事件 |
188
+ | `selectionToolbar` | `hiddenBuiltinActions`(含 `copy`)、`renderCopyIcon`、`extraActions`;扩展动作监听 `selectionExtraAction` 事件 |
159
189
 
160
190
  ```js
161
191
  features: {
@@ -182,12 +212,27 @@ features: {
182
212
  },
183
213
  },
184
214
  selectionToolbar: {
215
+ // 复制默认开启,浮窗最左侧;隐藏:hiddenBuiltinActions: ['copy']
216
+ renderCopyIcon: () => {
217
+ const span = document.createElement('span');
218
+ span.textContent = '📋';
219
+ span.setAttribute('aria-hidden', 'true');
220
+ return span;
221
+ },
185
222
  extraActions: [{ id: 'cite', label: '引用', order: 10 }],
186
223
  },
187
224
  },
188
225
  ```
189
226
 
190
- `renderMenu` / `renderIcon` 在 snippet(Preact)内执行,请返回 **DOM 或 Preact 节点**;Vue 2 宿主不要用 `h()` 直接塞进 Shadow DOM。
227
+ 划词后点**复制**会将选中文本写入剪贴板。程序化:
228
+
229
+ ```js
230
+ import { copyTextToClipboard } from '@embedpdf-editor/chapter-snippet';
231
+
232
+ await copyTextToClipboard('文本');
233
+ ```
234
+
235
+ `renderMenu` / `renderIcon` / `renderCopyIcon` 在 snippet(Preact)内执行,请返回 **DOM 或 Preact 节点**;Vue 2 宿主不要用 `h()` 直接塞进 Shadow DOM。
191
236
 
192
237
  ## 事件
193
238
 
@@ -247,11 +292,21 @@ import {
247
292
  | 选项 | 说明 |
248
293
  | --- | --- |
249
294
  | `mode` | `replace` 清空后导入;`merge` 合并 |
250
- | `ensureChapterLoaded` | 默认 true,导入 markup 前打开 PDF |
295
+ | `ensureChapterLoaded` | 默认 true;含 markup 的分段章会加载 **全部段** 再合并页码 |
251
296
  | `bookmarks` / `notes` / `markup` | 默认 true,可关闭某一类 |
252
297
  | `persistNotes` / `persistBookmarks` | 导入后写回业务存储 |
253
298
 
254
- 示例见 `examples/chapter-viewer-demo-vue2/src/components/AnnotationsDemoBar.vue`。
299
+ 示例见 `examples/chapter-viewer-demo-vue2/src/components/AnnotationsDemoBar.vue`。详见 [10-annotations-io.md](../../docs/get-started/10-annotations-io.md)。
300
+
301
+ ## 教程索引
302
+
303
+ | 主题 | 文档 |
304
+ | --- | --- |
305
+ | 目录 | [docs/get-started/README.md](../../docs/get-started/README.md) |
306
+ | `wasmUrl`(含 OSS 示例) | [01-installation.md](../../docs/get-started/01-installation.md) |
307
+ | 划词复制 | [07-selection-toolbar.md](../../docs/get-started/07-selection-toolbar.md) |
308
+ | 事件常量 | [11-events-callbacks-and-component-api.md](../../docs/get-started/11-events-callbacks-and-component-api.md) |
309
+ | 分段 + 按章存储 | [12-segmented-pdf-and-per-chapter-storage.md](../../docs/get-started/12-segmented-pdf-and-per-chapter-storage.md) |
255
310
 
256
311
  ## 与 React / Vue3 渲染器的区别
257
312
 
@@ -277,3 +332,4 @@ import {
277
332
  | 只显示空白 | 确认宿主元素有高度,且没有被父容器 `overflow`/flex 布局压到 0 |
278
333
  | 划词后没有笔记弹窗 | 监听 `chapter-note-request-create`,保存后调用 `detail.complete(noteId)` |
279
334
  | Vue/Vite 开发环境异常预构建 | 使用 `chapterSnippetViteResolve()`,确保 snippet 和 PDFium 引擎没有被 optimizeDeps 预构建 |
335
+ | 分段章存了 `#sN` | 业务层只用 `chapterId`;引擎段 ID 勿写入库 |
@@ -140,8 +140,16 @@ declare interface ChapterManagerCapability {
140
140
  getVirtualPageMap(): VirtualPageMap;
141
141
  /** ChapterScrollPlugin 在可见页位变化时调用,用以驱动按需加载 */
142
142
  setVisibleGlobalPages(visiblePageIndices: number[]): void;
143
- /** 显式触发某章节加载(如点击章节标题跳转时) */
143
+ /** 显式触发某章节加载(分段章节仅加载第 0 段) */
144
144
  ensureChapterLoaded(chapterId: string): Promise<ChapterLoadStatus>;
145
+ /** 加载章内指定分段 PDF */
146
+ ensureSegmentLoaded(chapterId: string, segmentIndex: number): Promise<ChapterLoadStatus>;
147
+ /** 加载章内全部分段(导出划线等) */
148
+ ensureAllSegmentsLoaded(chapterId: string): Promise<ChapterLoadStatus>;
149
+ /** 将章内 localPageIndex 映射为 documentManager 的 documentId + 段内页码 */
150
+ resolvePageDocument(chapterId: string, localPageIndex: number): ChapterPageDocumentRef | null;
151
+ getSegmentPlan(chapterId: string): ChapterSegmentPlan | null;
152
+ isSegmentLoaded(chapterId: string, segmentIndex: number): boolean;
145
153
  /** 状态查询 */
146
154
  getChapterStatus(chapterId: string): ChapterLoadStatus;
147
155
  getChapter(chapterId: string): ChapterDescriptor | null;
@@ -156,10 +164,8 @@ declare interface ChapterManagerCapability {
156
164
  /**
157
165
  * 章节生命周期管理:以章节为粒度懒加载/预取/卸载 PDF 文件,并屏蔽密码协议细节。
158
166
  *
159
- * 关键不变量:
160
- * - 每个章节对 DocumentManager 用 `documentId === chapterId`,从而保持 1:1 缓存。
161
- * - 视觉上的「activeDocumentId」由本插件根据当前可见页位维护;上层无须感知。
162
- * - 渲染层最终始终通过 `(chapterId, localPageIndex)` 索引到 owner 章节的 PDF 文档。
167
+ * - 单 URL:`documentId === chapterId`
168
+ * - 多段 URL:`documentId === chapterId#s{index}`,滚动时按段加载
163
169
  */
164
170
  export declare class ChapterManagerPlugin extends BasePlugin<ChapterManagerPluginConfig, ChapterManagerCapability> {
165
171
  static readonly id: "chapter-manager";
@@ -171,15 +177,11 @@ export declare class ChapterManagerPlugin extends BasePlugin<ChapterManagerPlugi
171
177
  private manifest;
172
178
  private overlapStrategy;
173
179
  private virtualPageMap;
174
- /** 每个章节当前状态(in-memory;不需要 redux 同步) */
175
180
  private readonly chapterStatus;
176
- /** 章节最近一次进入视口或被显式请求的时间戳,用于卸载判断 */
177
181
  private readonly chapterLastUsed;
178
- /** 章节密码累计尝试次数 */
179
182
  private readonly passwordAttempts;
180
- /** 等待 ensureChapterLoaded 完成的 Promise 列表(按章节去重) */
181
- private readonly pendingLoadPromises;
182
- /** unload tick 句柄 */
183
+ private readonly pendingChapterLoadPromises;
184
+ private readonly pendingSegmentLoadPromises;
183
185
  private unloadTimer;
184
186
  private documentManagerUnsubs;
185
187
  constructor(id: string, registry: PluginRegistry);
@@ -187,16 +189,19 @@ export declare class ChapterManagerPlugin extends BasePlugin<ChapterManagerPlugi
187
189
  protected buildCapability(): ChapterManagerCapability;
188
190
  destroy(): void;
189
191
  private setManifestInternal;
190
- /** manifest 就绪后预取前 N 章(不依赖滚动视口是否已算出可见页) */
191
192
  private eagerPrefetchFromManifest;
192
193
  private findChapter;
193
- private isOwnedChapter;
194
+ private getSegmentPlanForChapter;
195
+ private isSegmentDocumentOpen;
196
+ private syncChapterStatusFromDocuments;
194
197
  private handleVisibleChange;
195
198
  private collectIdleChapters;
196
199
  ensureChapterLoaded(chapterId: string): Promise<ChapterLoadStatus>;
200
+ ensureAllSegmentsLoaded(chapterId: string): Promise<ChapterLoadStatus>;
201
+ ensureSegmentLoaded(chapterId: string, segmentIndex: number): Promise<ChapterLoadStatus>;
202
+ private ensureSingleDocumentChapter;
197
203
  private resolvePdfPayload;
198
- private startLoad;
199
- /** 阻塞等待该章节状态进入 loaded / error / password-required / closed */
204
+ private startLoadSegment;
200
205
  private waitForTerminalStatus;
201
206
  private handleDocumentError;
202
207
  private closeChapter;
@@ -238,6 +243,11 @@ export declare type ChapterNoteRequestEditDetail = {
238
243
  anchor: NoteAnchor;
239
244
  };
240
245
 
246
+ declare interface ChapterPageDocumentRef {
247
+ documentId: string;
248
+ pageIndex: number;
249
+ }
250
+
241
251
  /**
242
252
  * Backend-supplied chapter contract.
243
253
  *
@@ -253,6 +263,21 @@ export declare type ChapterPdfPayload = {
253
263
  buffer: ArrayBuffer;
254
264
  };
255
265
 
266
+ declare interface ChapterSegmentInfo {
267
+ index: number;
268
+ url: string;
269
+ localPageStart: number;
270
+ localPageEnd: number;
271
+ documentId: string;
272
+ }
273
+
274
+ declare interface ChapterSegmentPlan {
275
+ chapterId: string;
276
+ threshold: number;
277
+ pageCount: number;
278
+ segments: ChapterSegmentInfo[];
279
+ }
280
+
256
281
  /** 划词工具栏扩展操作事件 */
257
282
  export declare type ChapterSelectionActionDetail = {
258
283
  actionId: string;
@@ -272,6 +297,14 @@ export declare type ChapterSource = {
272
297
  /** 按章预处理:拉取、解密、转换后返回 url 或 buffer */
273
298
  | {
274
299
  load: () => Promise<ChapterPdfPayload>;
300
+ }
301
+ /**
302
+ * 章内多 PDF 分段(后端已按段拆分)。
303
+ * `urls.length` 须为 ceil(章页数 / segmentPageThreshold)。
304
+ */
305
+ | {
306
+ urls: string[];
307
+ segmentPageThreshold: number;
275
308
  };
276
309
 
277
310
  declare interface ChapterStatusEvent {
@@ -429,6 +462,9 @@ export declare type ContainerInitConfig = ChapterViewerContainerConfig & {
429
462
  target: Element;
430
463
  };
431
464
 
465
+ /** 将划选文本写入系统剪贴板(Clipboard API,失败时回退 execCommand) */
466
+ export declare function copyTextToClipboard(text: string): Promise<boolean>;
467
+
432
468
  /** 生成插件列表 + 规范化后的 features(供 EmbedPDF / PdfChapterViewport 使用) */
433
469
  export declare function createChapterViewerBundle(input: ChapterViewerOptions | CreateChapterViewerEditorOptionsInput): {
434
470
  plugins: PluginBatchRegistration<any, any, any, any>[];
@@ -567,7 +603,10 @@ declare interface HoverBookmarkUiConfig_2 {
567
603
  * 适合:所有章节走同一套 API、在内存中解密后再以 buffer 打开、动态签名 URL 等。
568
604
  */
569
605
  export declare interface IChapterPdfLoader {
570
- loadPdf(chapter: ChapterDescriptor): Promise<ChapterPdfPayload>;
606
+ /**
607
+ * @param segmentIndex 章内分段索引;单 URL 章节为 0 或省略
608
+ */
609
+ loadPdf(chapter: ChapterDescriptor, segmentIndex?: number): Promise<ChapterPdfPayload>;
571
610
  }
572
611
 
573
612
  /**