@embedpdf-editor/chapter-snippet 1.0.0 → 1.0.3
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 +84 -5
- package/dist/embedpdf-chapter.d.ts +101 -22
- package/dist/embedpdf-chapter.js +23136 -14491
- package/dist/embedpdf-chapter.js.map +1 -1
- package/package.json +5 -5
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
|
|
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,48 @@ viewer?.addEventListener(CHAPTER_SNIPPET_EVENTS.ready, (event) => {
|
|
|
132
143
|
|
|
133
144
|
旧版本的 `editorInput` 仍可使用,但新代码应改为 `options`,并把 `features` 写在 `options.features` 或顶层 `features`。
|
|
134
145
|
|
|
146
|
+
### PDF 三步加载(Vue 2 推荐)
|
|
147
|
+
|
|
148
|
+
| 步骤 | 说明 |
|
|
149
|
+
| --- | --- |
|
|
150
|
+
| 1 | `manifest.chapters`:仅页码;`segmentPageThreshold` 写在章节上 |
|
|
151
|
+
| 2 | `chapterPdfLoader.loadChapterUrls`:按章 `getOneChap`,**每章只调一次** |
|
|
152
|
+
| 3 | `chapterPdfLoader.openPdf`(可选):解密等;省略则直接打开 `ctx.url` |
|
|
153
|
+
|
|
154
|
+
```js
|
|
155
|
+
options: {
|
|
156
|
+
manifest: {
|
|
157
|
+
chapters: [
|
|
158
|
+
{
|
|
159
|
+
chapterId: item._id,
|
|
160
|
+
title: item.title,
|
|
161
|
+
globalPageRange: [item.startPage, item.endPage],
|
|
162
|
+
localPageRange: [0, item.endPage - item.startPage],
|
|
163
|
+
segmentPageThreshold: item.page,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
chapterPdfLoader: {
|
|
168
|
+
async loadChapterUrls(chapter) {
|
|
169
|
+
const res = await getOneChap(chapter.chapterId);
|
|
170
|
+
if (!res.success) throw new Error(res.message);
|
|
171
|
+
const raw = res.data.resourceUrl;
|
|
172
|
+
return Array.isArray(raw) ? raw : raw ? [raw] : [];
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
notes: { /* ... */ },
|
|
176
|
+
bookmarks: { /* ... */ },
|
|
177
|
+
},
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
勿在 loader 外再请求章节详情;勿把 `segmentPageThreshold` 放进 `source`。
|
|
181
|
+
|
|
182
|
+
[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)
|
|
183
|
+
|
|
184
|
+
### 按章持久化
|
|
185
|
+
|
|
186
|
+
`options.notes` / `options.bookmarks` 回调里的 `chapterId`、`localPageIndex` 与单 URL 章相同。划线备份用 `exportChapterAnnotations`,JSON 键为 `chapters[chapterId]`(导出 markup 时会拉全部分段)。
|
|
187
|
+
|
|
135
188
|
### `ChapterViewerOptions`(与 React / Vue3 一致)
|
|
136
189
|
|
|
137
190
|
| 字段 | 说明 |
|
|
@@ -155,7 +208,7 @@ viewer?.addEventListener(CHAPTER_SNIPPET_EVENTS.ready, (event) => {
|
|
|
155
208
|
| `notes` | `marker.renderIcon`、`renderMenuActions`、`highlightColor` |
|
|
156
209
|
| `zoom` | `pageWidth`、`min` / `max` / `enabled`;实际上限不超过 `[data-chapter-scroll-viewport]` 宽度,resize 时自动 clamp |
|
|
157
210
|
| `scrollViewport` | `background`(默认 `#f1f5f9`),`[data-chapter-scroll-viewport]` 背景 |
|
|
158
|
-
| `selectionToolbar` | `hiddenBuiltinActions`、`extraActions`;扩展动作监听 `selectionExtraAction` 事件 |
|
|
211
|
+
| `selectionToolbar` | `hiddenBuiltinActions`(含 `copy`)、`renderCopyIcon`、`extraActions`;扩展动作监听 `selectionExtraAction` 事件 |
|
|
159
212
|
|
|
160
213
|
```js
|
|
161
214
|
features: {
|
|
@@ -182,12 +235,27 @@ features: {
|
|
|
182
235
|
},
|
|
183
236
|
},
|
|
184
237
|
selectionToolbar: {
|
|
238
|
+
// 复制默认开启,浮窗最左侧;隐藏:hiddenBuiltinActions: ['copy']
|
|
239
|
+
renderCopyIcon: () => {
|
|
240
|
+
const span = document.createElement('span');
|
|
241
|
+
span.textContent = '📋';
|
|
242
|
+
span.setAttribute('aria-hidden', 'true');
|
|
243
|
+
return span;
|
|
244
|
+
},
|
|
185
245
|
extraActions: [{ id: 'cite', label: '引用', order: 10 }],
|
|
186
246
|
},
|
|
187
247
|
},
|
|
188
248
|
```
|
|
189
249
|
|
|
190
|
-
|
|
250
|
+
划词后点**复制**会将选中文本写入剪贴板。程序化:
|
|
251
|
+
|
|
252
|
+
```js
|
|
253
|
+
import { copyTextToClipboard } from '@embedpdf-editor/chapter-snippet';
|
|
254
|
+
|
|
255
|
+
await copyTextToClipboard('文本');
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
`renderMenu` / `renderIcon` / `renderCopyIcon` 在 snippet(Preact)内执行,请返回 **DOM 或 Preact 节点**;Vue 2 宿主不要用 `h()` 直接塞进 Shadow DOM。
|
|
191
259
|
|
|
192
260
|
## 事件
|
|
193
261
|
|
|
@@ -247,11 +315,21 @@ import {
|
|
|
247
315
|
| 选项 | 说明 |
|
|
248
316
|
| --- | --- |
|
|
249
317
|
| `mode` | `replace` 清空后导入;`merge` 合并 |
|
|
250
|
-
| `ensureChapterLoaded` | 默认 true
|
|
318
|
+
| `ensureChapterLoaded` | 默认 true;含 markup 的分段章会加载 **全部段** 再合并页码 |
|
|
251
319
|
| `bookmarks` / `notes` / `markup` | 默认 true,可关闭某一类 |
|
|
252
320
|
| `persistNotes` / `persistBookmarks` | 导入后写回业务存储 |
|
|
253
321
|
|
|
254
|
-
示例见 `examples/chapter-viewer-demo-vue2/src/components/AnnotationsDemoBar.vue
|
|
322
|
+
示例见 `examples/chapter-viewer-demo-vue2/src/components/AnnotationsDemoBar.vue`。详见 [10-annotations-io.md](../../docs/get-started/10-annotations-io.md)。
|
|
323
|
+
|
|
324
|
+
## 教程索引
|
|
325
|
+
|
|
326
|
+
| 主题 | 文档 |
|
|
327
|
+
| --- | --- |
|
|
328
|
+
| 目录 | [docs/get-started/README.md](../../docs/get-started/README.md) |
|
|
329
|
+
| `wasmUrl`(含 OSS 示例) | [01-installation.md](../../docs/get-started/01-installation.md) |
|
|
330
|
+
| 划词复制 | [07-selection-toolbar.md](../../docs/get-started/07-selection-toolbar.md) |
|
|
331
|
+
| 事件常量 | [11-events-callbacks-and-component-api.md](../../docs/get-started/11-events-callbacks-and-component-api.md) |
|
|
332
|
+
| 分段 + 按章存储 | [12-segmented-pdf-and-per-chapter-storage.md](../../docs/get-started/12-segmented-pdf-and-per-chapter-storage.md) |
|
|
255
333
|
|
|
256
334
|
## 与 React / Vue3 渲染器的区别
|
|
257
335
|
|
|
@@ -277,3 +355,4 @@ import {
|
|
|
277
355
|
| 只显示空白 | 确认宿主元素有高度,且没有被父容器 `overflow`/flex 布局压到 0 |
|
|
278
356
|
| 划词后没有笔记弹窗 | 监听 `chapter-note-request-create`,保存后调用 `detail.complete(noteId)` |
|
|
279
357
|
| Vue/Vite 开发环境异常预构建 | 使用 `chapterSnippetViteResolve()`,确保 snippet 和 PDFium 引擎没有被 optimizeDeps 预构建 |
|
|
358
|
+
| 分段章存了 `#sN` | 业务层只用 `chapterId`;引擎段 ID 勿写入库 |
|
|
@@ -110,6 +110,11 @@ export declare interface ChapterDescriptor {
|
|
|
110
110
|
* PDF 来源。可省略:须在 ChapterManager 配置 `chapterPdfLoader` 中统一加载。
|
|
111
111
|
*/
|
|
112
112
|
source?: ChapterSource;
|
|
113
|
+
/**
|
|
114
|
+
* 章内多 PDF 分段时,每段最多页数(如 5 表示 13 页 → 3 段)。
|
|
115
|
+
* URL 由 `chapterPdfLoader.loadChapterUrls` 按章拉取,**不要**写在 `source` 里。
|
|
116
|
+
*/
|
|
117
|
+
segmentPageThreshold?: number;
|
|
113
118
|
encrypted?: boolean;
|
|
114
119
|
/**
|
|
115
120
|
* 仅当 OverlapOwnerStrategy = 'explicit' 时使用:
|
|
@@ -140,8 +145,16 @@ declare interface ChapterManagerCapability {
|
|
|
140
145
|
getVirtualPageMap(): VirtualPageMap;
|
|
141
146
|
/** ChapterScrollPlugin 在可见页位变化时调用,用以驱动按需加载 */
|
|
142
147
|
setVisibleGlobalPages(visiblePageIndices: number[]): void;
|
|
143
|
-
/**
|
|
148
|
+
/** 显式触发某章节加载(分段章节仅加载第 0 段) */
|
|
144
149
|
ensureChapterLoaded(chapterId: string): Promise<ChapterLoadStatus>;
|
|
150
|
+
/** 加载章内指定分段 PDF */
|
|
151
|
+
ensureSegmentLoaded(chapterId: string, segmentIndex: number): Promise<ChapterLoadStatus>;
|
|
152
|
+
/** 加载章内全部分段(导出划线等) */
|
|
153
|
+
ensureAllSegmentsLoaded(chapterId: string): Promise<ChapterLoadStatus>;
|
|
154
|
+
/** 将章内 localPageIndex 映射为 documentManager 的 documentId + 段内页码 */
|
|
155
|
+
resolvePageDocument(chapterId: string, localPageIndex: number): ChapterPageDocumentRef | null;
|
|
156
|
+
getSegmentPlan(chapterId: string): ChapterSegmentPlan | null;
|
|
157
|
+
isSegmentLoaded(chapterId: string, segmentIndex: number): boolean;
|
|
145
158
|
/** 状态查询 */
|
|
146
159
|
getChapterStatus(chapterId: string): ChapterLoadStatus;
|
|
147
160
|
getChapter(chapterId: string): ChapterDescriptor | null;
|
|
@@ -156,10 +169,8 @@ declare interface ChapterManagerCapability {
|
|
|
156
169
|
/**
|
|
157
170
|
* 章节生命周期管理:以章节为粒度懒加载/预取/卸载 PDF 文件,并屏蔽密码协议细节。
|
|
158
171
|
*
|
|
159
|
-
*
|
|
160
|
-
* -
|
|
161
|
-
* - 视觉上的「activeDocumentId」由本插件根据当前可见页位维护;上层无须感知。
|
|
162
|
-
* - 渲染层最终始终通过 `(chapterId, localPageIndex)` 索引到 owner 章节的 PDF 文档。
|
|
172
|
+
* - 单 URL:`documentId === chapterId`
|
|
173
|
+
* - 多段 URL:`documentId === chapterId#s{index}`,滚动时按段加载
|
|
163
174
|
*/
|
|
164
175
|
export declare class ChapterManagerPlugin extends BasePlugin<ChapterManagerPluginConfig, ChapterManagerCapability> {
|
|
165
176
|
static readonly id: "chapter-manager";
|
|
@@ -171,15 +182,14 @@ export declare class ChapterManagerPlugin extends BasePlugin<ChapterManagerPlugi
|
|
|
171
182
|
private manifest;
|
|
172
183
|
private overlapStrategy;
|
|
173
184
|
private virtualPageMap;
|
|
174
|
-
/** 每个章节当前状态(in-memory;不需要 redux 同步) */
|
|
175
185
|
private readonly chapterStatus;
|
|
176
|
-
/** 章节最近一次进入视口或被显式请求的时间戳,用于卸载判断 */
|
|
177
186
|
private readonly chapterLastUsed;
|
|
178
|
-
/** 章节密码累计尝试次数 */
|
|
179
187
|
private readonly passwordAttempts;
|
|
180
|
-
|
|
181
|
-
private readonly
|
|
182
|
-
/**
|
|
188
|
+
private readonly pendingChapterLoadPromises;
|
|
189
|
+
private readonly pendingSegmentLoadPromises;
|
|
190
|
+
/** 步骤 2:`loadChapterUrls` 按章缓存 */
|
|
191
|
+
private readonly chapterUrlsCache;
|
|
192
|
+
private readonly pendingChapterUrlsPromises;
|
|
183
193
|
private unloadTimer;
|
|
184
194
|
private documentManagerUnsubs;
|
|
185
195
|
constructor(id: string, registry: PluginRegistry);
|
|
@@ -187,16 +197,21 @@ export declare class ChapterManagerPlugin extends BasePlugin<ChapterManagerPlugi
|
|
|
187
197
|
protected buildCapability(): ChapterManagerCapability;
|
|
188
198
|
destroy(): void;
|
|
189
199
|
private setManifestInternal;
|
|
190
|
-
/** manifest 就绪后预取前 N 章(不依赖滚动视口是否已算出可见页) */
|
|
191
200
|
private eagerPrefetchFromManifest;
|
|
192
201
|
private findChapter;
|
|
193
|
-
private
|
|
202
|
+
private getSegmentPlanForChapter;
|
|
203
|
+
private isSegmentDocumentOpen;
|
|
204
|
+
private syncChapterStatusFromDocuments;
|
|
194
205
|
private handleVisibleChange;
|
|
195
206
|
private collectIdleChapters;
|
|
196
207
|
ensureChapterLoaded(chapterId: string): Promise<ChapterLoadStatus>;
|
|
208
|
+
ensureAllSegmentsLoaded(chapterId: string): Promise<ChapterLoadStatus>;
|
|
209
|
+
ensureSegmentLoaded(chapterId: string, segmentIndex: number): Promise<ChapterLoadStatus>;
|
|
210
|
+
private ensureSingleDocumentChapter;
|
|
211
|
+
private resolveChapterUrls;
|
|
212
|
+
private openPayloadFromUrl;
|
|
197
213
|
private resolvePdfPayload;
|
|
198
|
-
private
|
|
199
|
-
/** 阻塞等待该章节状态进入 loaded / error / password-required / closed */
|
|
214
|
+
private startLoadSegment;
|
|
200
215
|
private waitForTerminalStatus;
|
|
201
216
|
private handleDocumentError;
|
|
202
217
|
private closeChapter;
|
|
@@ -238,6 +253,26 @@ export declare type ChapterNoteRequestEditDetail = {
|
|
|
238
253
|
anchor: NoteAnchor;
|
|
239
254
|
};
|
|
240
255
|
|
|
256
|
+
declare interface ChapterPageDocumentRef {
|
|
257
|
+
documentId: string;
|
|
258
|
+
pageIndex: number;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 打开某一段 PDF 时的上下文(步骤 2 已拿到 urls,步骤 3 可选处理)。
|
|
263
|
+
*/
|
|
264
|
+
declare interface ChapterPdfLoadContext {
|
|
265
|
+
chapter: ChapterDescriptor;
|
|
266
|
+
/** 当前要打开的段,0-based */
|
|
267
|
+
segmentIndex: number;
|
|
268
|
+
/** 该章总段数 */
|
|
269
|
+
segmentCount: number;
|
|
270
|
+
/** 步骤 2:`loadChapterUrls` 返回的完整列表 */
|
|
271
|
+
urls: string[];
|
|
272
|
+
/** 当前段对应 URL:`urls[segmentIndex]` */
|
|
273
|
+
url: string;
|
|
274
|
+
}
|
|
275
|
+
|
|
241
276
|
/**
|
|
242
277
|
* Backend-supplied chapter contract.
|
|
243
278
|
*
|
|
@@ -253,6 +288,22 @@ export declare type ChapterPdfPayload = {
|
|
|
253
288
|
buffer: ArrayBuffer;
|
|
254
289
|
};
|
|
255
290
|
|
|
291
|
+
declare interface ChapterSegmentInfo {
|
|
292
|
+
index: number;
|
|
293
|
+
/** 静态 manifest 有值;动态 loader 时为空,打开段时再解析 URL */
|
|
294
|
+
url: string;
|
|
295
|
+
localPageStart: number;
|
|
296
|
+
localPageEnd: number;
|
|
297
|
+
documentId: string;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
declare interface ChapterSegmentPlan {
|
|
301
|
+
chapterId: string;
|
|
302
|
+
threshold: number;
|
|
303
|
+
pageCount: number;
|
|
304
|
+
segments: ChapterSegmentInfo[];
|
|
305
|
+
}
|
|
306
|
+
|
|
256
307
|
/** 划词工具栏扩展操作事件 */
|
|
257
308
|
export declare type ChapterSelectionActionDetail = {
|
|
258
309
|
actionId: string;
|
|
@@ -272,6 +323,14 @@ export declare type ChapterSource = {
|
|
|
272
323
|
/** 按章预处理:拉取、解密、转换后返回 url 或 buffer */
|
|
273
324
|
| {
|
|
274
325
|
load: () => Promise<ChapterPdfPayload>;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* @deprecated 请用章节级 `segmentPageThreshold` + `chapterPdfLoader.loadChapterUrls`。
|
|
329
|
+
* 静态多 URL 时:`urls.length` 须为 ceil(章页数 / segmentPageThreshold)。
|
|
330
|
+
*/
|
|
331
|
+
| {
|
|
332
|
+
urls: string[];
|
|
333
|
+
segmentPageThreshold: number;
|
|
275
334
|
};
|
|
276
335
|
|
|
277
336
|
declare interface ChapterStatusEvent {
|
|
@@ -294,6 +353,8 @@ export declare type ChapterTreeInput = {
|
|
|
294
353
|
startPage: number;
|
|
295
354
|
endPage: number;
|
|
296
355
|
source?: ChapterSource;
|
|
356
|
+
/** 章内分段阈值;URL 由 `chapterPdfLoader.loadChapterUrls` 拉取 */
|
|
357
|
+
segmentPageThreshold?: number;
|
|
297
358
|
encrypted?: boolean;
|
|
298
359
|
children?: ChapterTreeInput[];
|
|
299
360
|
};
|
|
@@ -429,6 +490,9 @@ export declare type ContainerInitConfig = ChapterViewerContainerConfig & {
|
|
|
429
490
|
target: Element;
|
|
430
491
|
};
|
|
431
492
|
|
|
493
|
+
/** 将划选文本写入系统剪贴板(Clipboard API,失败时回退 execCommand) */
|
|
494
|
+
export declare function copyTextToClipboard(text: string): Promise<boolean>;
|
|
495
|
+
|
|
432
496
|
/** 生成插件列表 + 规范化后的 features(供 EmbedPDF / PdfChapterViewport 使用) */
|
|
433
497
|
export declare function createChapterViewerBundle(input: ChapterViewerOptions | CreateChapterViewerEditorOptionsInput): {
|
|
434
498
|
plugins: PluginBatchRegistration<any, any, any, any>[];
|
|
@@ -557,17 +621,32 @@ declare interface HoverBookmarkUiConfig_2 {
|
|
|
557
621
|
}
|
|
558
622
|
|
|
559
623
|
/**
|
|
560
|
-
*
|
|
624
|
+
* 章节 PDF 加载(推荐三步;`loadPdf` 仍兼容旧版单步写法)。
|
|
561
625
|
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
*
|
|
565
|
-
*
|
|
626
|
+
* | 步骤 | 方法 | 说明 |
|
|
627
|
+
* | --- | --- | --- |
|
|
628
|
+
* | 1 | manifest 章节树 | 仅 `chapterId`、页码、`segmentPageThreshold` 等,**无 URL** |
|
|
629
|
+
* | 2 | `loadChapterUrls(chapter)` | 按章请求详情,返回 URL 列表(分段时多项) |
|
|
630
|
+
* | 3 | `openPdf(ctx)`(可选) | 对 `ctx.url` 解密/下载;省略则直接用 `ctx.url` 打开 |
|
|
566
631
|
*
|
|
567
|
-
*
|
|
632
|
+
* 仍可使用 `loadPdf(chapter, segmentIndex)` 单步实现(等价于 2+3 合并)。
|
|
568
633
|
*/
|
|
569
634
|
export declare interface IChapterPdfLoader {
|
|
570
|
-
|
|
635
|
+
/**
|
|
636
|
+
* 步骤 2:按章拉取 PDF 地址列表。
|
|
637
|
+
* 单 PDF 章节返回长度为 1 的数组;分段章节返回与 `ceil(页数/threshold)` 一致的多项。
|
|
638
|
+
*/
|
|
639
|
+
loadChapterUrls?(chapter: ChapterDescriptor): Promise<string[]>;
|
|
640
|
+
/**
|
|
641
|
+
* 步骤 3(可选):将步骤 2 的 URL 转为可打开的 payload。
|
|
642
|
+
* 未实现时引擎使用 `{ url: ctx.url }`。
|
|
643
|
+
*/
|
|
644
|
+
openPdf?(ctx: ChapterPdfLoadContext): Promise<ChapterPdfPayload>;
|
|
645
|
+
/**
|
|
646
|
+
* @deprecated 单步加载;新代码请用 `loadChapterUrls` + 可选 `openPdf`。
|
|
647
|
+
* @param segmentIndex 章内分段索引;单 URL 章节为 0 或省略
|
|
648
|
+
*/
|
|
649
|
+
loadPdf?(chapter: ChapterDescriptor, segmentIndex?: number): Promise<ChapterPdfPayload>;
|
|
571
650
|
}
|
|
572
651
|
|
|
573
652
|
/**
|