@embedpdf-editor/react-chapter-viewer 0.2.1 → 0.2.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 +159 -146
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +64 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +636 -503
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
业务侧只需安装本包与 `react` / `react-dom`;PDFium(`@embedpdf/engines`)、`scheduler` 及阅读器依赖的 `@embedpdf/*` 插件由本包 **dependencies** 带入,**不必**在业务 `package.json` 里逐个声明 `@embedpdf/engines` 等。
|
|
6
6
|
|
|
7
7
|
> 组件样式以 **inline style** 为主,**不需要** Tailwind。
|
|
8
|
-
>
|
|
8
|
+
> 下文提供可直接复制到业务项目中的完整示例(不依赖仓库内 demo 工程)。
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -114,14 +114,13 @@ const catalog: ChapterViewerCatalog = {
|
|
|
114
114
|
|
|
115
115
|
## 快速开始:`<ChapterPdfViewer />`
|
|
116
116
|
|
|
117
|
-
适合「单容器铺满、manifest
|
|
117
|
+
适合「单容器铺满、manifest 已就绪」的页面。推荐只传 **`options`**(不必再拆 `editorOptions` + `features`,也不必包一层 `callbacks`)。
|
|
118
118
|
|
|
119
119
|
```tsx
|
|
120
120
|
import {
|
|
121
121
|
usePdfiumEngine,
|
|
122
122
|
ChapterPdfViewer,
|
|
123
|
-
|
|
124
|
-
DEFAULT_CHAPTER_VIEWER_FEATURES,
|
|
123
|
+
type ChapterManifest,
|
|
125
124
|
} from '@embedpdf-editor/react-chapter-viewer';
|
|
126
125
|
|
|
127
126
|
export function Reader({ manifest }: { manifest: ChapterManifest }) {
|
|
@@ -130,44 +129,31 @@ export function Reader({ manifest }: { manifest: ChapterManifest }) {
|
|
|
130
129
|
if (error) return <div>引擎失败:{error.message}</div>;
|
|
131
130
|
if (isLoading || !engine) return <div>正在加载 PDFium…</div>;
|
|
132
131
|
|
|
133
|
-
const editorOptions = createChapterViewerEditorOptions({
|
|
134
|
-
manifest,
|
|
135
|
-
bookmarks: {
|
|
136
|
-
callbacks: {
|
|
137
|
-
load: () => fetchBookmarks(),
|
|
138
|
-
persist: (list) => saveBookmarks(list),
|
|
139
|
-
onRequestRemove: async (b) => {
|
|
140
|
-
await api.deleteBookmark(b.id);
|
|
141
|
-
return true;
|
|
142
|
-
},
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
notes: {
|
|
146
|
-
callbacks: {
|
|
147
|
-
loadNotes: () => fetchNotes(),
|
|
148
|
-
onRequestCreateNote: ({ draft, complete }) => {
|
|
149
|
-
// 打开你的弹窗,用户确认后:
|
|
150
|
-
openCreateModal(draft).then((noteId) => complete(noteId));
|
|
151
|
-
},
|
|
152
|
-
onRequestEditNote: (noteId) => openEditModal(noteId),
|
|
153
|
-
onDeleteNote: (id) => api.deleteNote(id),
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
});
|
|
157
|
-
|
|
158
132
|
return (
|
|
159
133
|
<div style={{ height: '100vh' }}>
|
|
160
134
|
<ChapterPdfViewer
|
|
161
135
|
engine={engine}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
136
|
+
options={{
|
|
137
|
+
manifest,
|
|
138
|
+
bookmarks: {
|
|
139
|
+
load: () => fetchBookmarks(),
|
|
140
|
+
persist: (list) => saveBookmarks(list),
|
|
141
|
+
onRequestRemove: async (b) => {
|
|
142
|
+
await api.deleteBookmark(b.id);
|
|
143
|
+
return true;
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
notes: {
|
|
147
|
+
loadNotes: () => fetchNotes(),
|
|
148
|
+
onRequestCreateNote: ({ draft, complete }) => {
|
|
149
|
+
openCreateModal(draft).then((noteId) => complete(noteId));
|
|
150
|
+
},
|
|
151
|
+
onRequestEditNote: (noteId) => openEditModal(noteId),
|
|
152
|
+
onDeleteNote: (id) => api.deleteNote(id),
|
|
153
|
+
},
|
|
154
|
+
features: {
|
|
155
|
+
zoom: { pageWidth: 800 },
|
|
156
|
+
},
|
|
171
157
|
}}
|
|
172
158
|
/>
|
|
173
159
|
</div>
|
|
@@ -175,87 +161,147 @@ export function Reader({ manifest }: { manifest: ChapterManifest }) {
|
|
|
175
161
|
}
|
|
176
162
|
```
|
|
177
163
|
|
|
164
|
+
### `features` 简写
|
|
165
|
+
|
|
166
|
+
| 写法 | 含义 |
|
|
167
|
+
|------|------|
|
|
168
|
+
| 省略 | 划线、书签、笔记、选区浮窗、缩放 **全部开启** |
|
|
169
|
+
| `markup: false` | 关闭划词高亮/划线 |
|
|
170
|
+
| `zoom: false` | 关闭缩放 |
|
|
171
|
+
| `zoom: { pageWidth: 800 }` | 按宽度适配 |
|
|
172
|
+
|
|
178
173
|
**交互说明(默认开启时):**
|
|
179
174
|
|
|
180
175
|
- 划词 → 浮窗:高亮、下划线、波浪线、删除线、笔记
|
|
181
176
|
- 鼠标移到文本行末 → 显示「添加书签」;已加书签点击 → 删除确认(走 `onRequestRemove`)
|
|
182
177
|
- 笔记区域悬停 → 编辑 / 删除
|
|
183
|
-
- `Ctrl/Cmd +
|
|
178
|
+
- **缩放**:`features.zoom.enabled !== false` 时,在 PDF 滚动区域内 **`Ctrl/Cmd + 滚轮`** 或 **双指捏合**(触控板)缩放;缩放写入 core 的 `document.scale`,PDF 与书签/笔记 overlay 同步变化(不是只放大外层 div)
|
|
179
|
+
|
|
180
|
+
> 普通滚轮用于上下滚动章节,不会触发缩放;Mac 触控板请按住 Ctrl 或双指捏合。
|
|
184
181
|
|
|
185
182
|
---
|
|
186
183
|
|
|
187
184
|
## 进阶:自定义布局(目录 + 首章预加载)
|
|
188
185
|
|
|
189
|
-
|
|
186
|
+
需要**左侧章节目录**、或要在挂载视口前**先 `ensureChapterLoaded`** 时,不要用一站式 `<ChapterPdfViewer />`,改用:
|
|
187
|
+
|
|
188
|
+
`createChapterViewerBundle` → `EmbedPDF` → `ChapterTreePanel` + `PdfChapterViewport`
|
|
189
|
+
|
|
190
|
+
**步骤简述:**
|
|
190
191
|
|
|
191
|
-
|
|
192
|
-
|
|
192
|
+
1. 用 `createChapterViewerBundle(options)` 得到 `{ plugins, features }`(`options` 字段与上一节 `ChapterPdfViewer` 的 `options` 相同)。
|
|
193
|
+
2. 用 `EmbedPDF` 挂载 `plugins`,在 `pluginsReady` 后再渲染子树。
|
|
194
|
+
3. 在 `EmbedPDF` 子树内用 `useCapability(ChapterManagerPlugin.id)` 对**首章**(或当前章)调用 `ensureChapterLoaded`。
|
|
195
|
+
4. 章节状态为 `loaded` 后再挂载 `PdfChapterViewport`,并传入 **`bundle.features`**(不要另写一套 `features`)。
|
|
193
196
|
|
|
194
|
-
|
|
197
|
+
**完整示例(可复制):**
|
|
195
198
|
|
|
196
199
|
```tsx
|
|
200
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
197
201
|
import {
|
|
198
202
|
usePdfiumEngine,
|
|
199
203
|
EmbedPDF,
|
|
200
204
|
useCapability,
|
|
201
205
|
ChapterManagerPlugin,
|
|
206
|
+
createChapterViewerBundle,
|
|
202
207
|
ChapterTreePanel,
|
|
203
208
|
PdfChapterViewport,
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
DEFAULT_CHAPTER_VIEWER_FEATURES,
|
|
209
|
+
type ChapterViewerCatalog,
|
|
210
|
+
type ChapterViewerOptions,
|
|
207
211
|
} from '@embedpdf-editor/react-chapter-viewer';
|
|
208
212
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
type ReaderProps = {
|
|
214
|
+
catalog: ChapterViewerCatalog;
|
|
215
|
+
notes: ChapterViewerOptions['notes'];
|
|
216
|
+
bookmarks: ChapterViewerOptions['bookmarks'];
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export function ChapterReaderWithSidebar({ catalog, notes, bookmarks }: ReaderProps) {
|
|
220
|
+
const { engine, isLoading, error } = usePdfiumEngine();
|
|
221
|
+
const bundle = useMemo(
|
|
216
222
|
() =>
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
223
|
+
createChapterViewerBundle({
|
|
224
|
+
manifest: catalog.manifest,
|
|
225
|
+
notes,
|
|
226
|
+
bookmarks,
|
|
227
|
+
features: { zoom: { pageWidth: 800 } },
|
|
228
|
+
}),
|
|
229
|
+
[catalog.manifest, notes, bookmarks],
|
|
222
230
|
);
|
|
231
|
+
const firstChapterId = catalog.manifest.chapters[0]?.chapterId ?? '';
|
|
223
232
|
|
|
224
|
-
if (
|
|
233
|
+
if (error) return <div>引擎失败:{error.message}</div>;
|
|
234
|
+
if (isLoading || !engine) return <div>正在加载 PDFium…</div>;
|
|
225
235
|
|
|
226
236
|
return (
|
|
227
|
-
<
|
|
228
|
-
{
|
|
229
|
-
pluginsReady
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
237
|
+
<div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
|
238
|
+
<EmbedPDF engine={engine} plugins={bundle.plugins}>
|
|
239
|
+
{({ pluginsReady }) =>
|
|
240
|
+
pluginsReady ? (
|
|
241
|
+
<ChapterWorkspace
|
|
242
|
+
tree={catalog.tree}
|
|
243
|
+
firstChapterId={firstChapterId}
|
|
244
|
+
features={bundle.features}
|
|
245
|
+
/>
|
|
246
|
+
) : (
|
|
247
|
+
<div>正在初始化插件…</div>
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
</EmbedPDF>
|
|
251
|
+
</div>
|
|
236
252
|
);
|
|
237
253
|
}
|
|
238
254
|
|
|
239
|
-
function
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
255
|
+
function ChapterWorkspace({
|
|
256
|
+
tree,
|
|
257
|
+
firstChapterId,
|
|
258
|
+
features,
|
|
259
|
+
}: {
|
|
260
|
+
tree: ChapterViewerCatalog['tree'];
|
|
261
|
+
firstChapterId: string;
|
|
262
|
+
features: ReturnType<typeof createChapterViewerBundle>['features'];
|
|
263
|
+
}) {
|
|
264
|
+
const [activeChapterId, setActiveChapterId] = useState(firstChapterId);
|
|
265
|
+
const [chapterReady, setChapterReady] = useState(false);
|
|
266
|
+
const { provides: chapterManager } = useCapability<ChapterManagerPlugin>(
|
|
267
|
+
ChapterManagerPlugin.id,
|
|
268
|
+
);
|
|
243
269
|
|
|
244
270
|
useEffect(() => {
|
|
245
|
-
if (!chapterManager || !
|
|
246
|
-
|
|
247
|
-
|
|
271
|
+
if (!chapterManager || !firstChapterId) return;
|
|
272
|
+
|
|
273
|
+
let cancelled = false;
|
|
274
|
+
setChapterReady(false);
|
|
275
|
+
|
|
276
|
+
const unsub = chapterManager.onChapterStatusChange(() => {
|
|
277
|
+
if (cancelled) return;
|
|
278
|
+
if (chapterManager.getChapterStatus(firstChapterId) === 'loaded') {
|
|
279
|
+
setChapterReady(true);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
void chapterManager.ensureChapterLoaded(firstChapterId).then((status) => {
|
|
284
|
+
if (!cancelled && status === 'loaded') setChapterReady(true);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
return () => {
|
|
288
|
+
cancelled = true;
|
|
289
|
+
unsub();
|
|
290
|
+
};
|
|
291
|
+
}, [chapterManager, firstChapterId]);
|
|
248
292
|
|
|
249
293
|
return (
|
|
250
|
-
<div style={{ display: 'flex',
|
|
294
|
+
<div style={{ display: 'flex', flex: 1, minHeight: 0, gap: 12 }}>
|
|
251
295
|
<ChapterTreePanel
|
|
252
|
-
tree={
|
|
253
|
-
activeChapterId={
|
|
254
|
-
onActiveChapterChange={
|
|
296
|
+
tree={tree}
|
|
297
|
+
activeChapterId={activeChapterId}
|
|
298
|
+
onActiveChapterChange={setActiveChapterId}
|
|
255
299
|
/>
|
|
256
|
-
<div style={{ flex: 1, minWidth: 0 }}>
|
|
257
|
-
{
|
|
258
|
-
<
|
|
300
|
+
<div style={{ flex: 1, minWidth: 0, minHeight: 0, position: 'relative' }}>
|
|
301
|
+
{chapterReady ? (
|
|
302
|
+
<div style={{ position: 'absolute', inset: 0 }}>
|
|
303
|
+
<PdfChapterViewport features={features} />
|
|
304
|
+
</div>
|
|
259
305
|
) : (
|
|
260
306
|
<div>正在加载章节 PDF…</div>
|
|
261
307
|
)}
|
|
@@ -265,66 +311,36 @@ function Layout({ catalog }: { catalog: ChapterViewerCatalog }) {
|
|
|
265
311
|
}
|
|
266
312
|
```
|
|
267
313
|
|
|
268
|
-
|
|
314
|
+
切换目录项时,可对新的 `activeChapterId` 同样调用 `ensureChapterLoaded`,再在 `loaded` 后更新视口(`ChapterTreePanel` 内部也会触发章节加载,按产品需求二选一或组合使用即可)。
|
|
269
315
|
|
|
270
316
|
---
|
|
271
317
|
|
|
272
318
|
## 笔记与书签回调
|
|
273
319
|
|
|
274
|
-
|
|
320
|
+
直接写在 `options.notes` / `options.bookmarks`(不再嵌套 `callbacks`)。
|
|
321
|
+
|
|
322
|
+
### 笔记 `options.notes`
|
|
275
323
|
|
|
276
324
|
| 回调 | 说明 |
|
|
277
325
|
|------|------|
|
|
278
326
|
| `loadNotes` | 初始化加载已有笔记 |
|
|
279
|
-
| `onRequestCreateNote` |
|
|
280
|
-
| `onCreateNote` |
|
|
281
|
-
| `onRequestEditNote`
|
|
282
|
-
| `onUpdateNote` / `onDeleteNote` | 更新、删除 |
|
|
327
|
+
| `onRequestCreateNote` | **推荐**:宿主弹窗后 `complete(noteId)` |
|
|
328
|
+
| `onCreateNote` | 内置创建(与上一项二选一) |
|
|
329
|
+
| `onRequestEditNote` / `onUpdateNote` / `onDeleteNote` | 编辑、更新、删除 |
|
|
283
330
|
|
|
284
|
-
### 书签 `
|
|
331
|
+
### 书签 `options.bookmarks`
|
|
285
332
|
|
|
286
333
|
| 回调 | 说明 |
|
|
287
334
|
|------|------|
|
|
288
335
|
| `load` | 加载已有书签 |
|
|
289
|
-
| `persist` |
|
|
290
|
-
| `onRequestRemove` |
|
|
291
|
-
| `onRemoveSuccess` | 移除后的收尾通知 |
|
|
336
|
+
| `persist` | 增删改后持久化 |
|
|
337
|
+
| `onRequestRemove` | 删除确认,返回 `true` 后从渲染层移除 |
|
|
292
338
|
|
|
293
339
|
---
|
|
294
340
|
|
|
295
|
-
##
|
|
341
|
+
## 内部默认(无需配置)
|
|
296
342
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
```ts
|
|
300
|
-
import type { ChapterViewerFeaturesConfig } from '@embedpdf-editor/react-chapter-viewer';
|
|
301
|
-
|
|
302
|
-
const features: ChapterViewerFeaturesConfig = {
|
|
303
|
-
markup: {
|
|
304
|
-
enabled: true,
|
|
305
|
-
styles: {
|
|
306
|
-
underline: { color: '#dc2626', thickness: 1.5, offsetY: 3 },
|
|
307
|
-
highlight: { color: '#facc15', opacity: 0.35 },
|
|
308
|
-
},
|
|
309
|
-
},
|
|
310
|
-
bookmarks: { enabled: true },
|
|
311
|
-
notes: { enabled: true },
|
|
312
|
-
selectionToolbar: {
|
|
313
|
-
enabled: true,
|
|
314
|
-
hiddenBuiltinActions: ['squiggly'],
|
|
315
|
-
extraActions: [{ id: 'translate', label: '翻译', order: 10 }],
|
|
316
|
-
},
|
|
317
|
-
zoom: {
|
|
318
|
-
enabled: true,
|
|
319
|
-
min: 0.5,
|
|
320
|
-
max: 3,
|
|
321
|
-
initial: 1,
|
|
322
|
-
pageWidth: 800, // 按 CSS 宽度适配首页 PDF
|
|
323
|
-
},
|
|
324
|
-
};
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
`onExtraSelectionAction` / `buildSelectionMenu` 可处理 `extraActions` 或完全自定义选区菜单。
|
|
343
|
+
以下由引擎内置,业务**不用**再写:`overlapStrategy`、`passwordProvider`、`prefetchChapters`、`loadDefaultStampLibrary`、`toolbar`(Annotate 模式栏已关闭)、`placeholderPageWidth` 等。需要改时再使用进阶 API `createPdfChapterEditor`。
|
|
328
344
|
|
|
329
345
|
---
|
|
330
346
|
|
|
@@ -332,14 +348,13 @@ const features: ChapterViewerFeaturesConfig = {
|
|
|
332
348
|
|
|
333
349
|
| 导出 | 用途 |
|
|
334
350
|
|------|------|
|
|
335
|
-
| `ChapterPdfViewer` |
|
|
336
|
-
| `
|
|
337
|
-
| `
|
|
338
|
-
| `
|
|
339
|
-
| `
|
|
340
|
-
| `
|
|
341
|
-
| `
|
|
342
|
-
| `DEFAULT_CHAPTER_VIEWER_FEATURES` | 默认功能开关 |
|
|
351
|
+
| `ChapterPdfViewer` | 一站式阅读器(传 `options`) |
|
|
352
|
+
| `createChapterViewerBundle` | 生成 `{ plugins, features }`(自定义布局) |
|
|
353
|
+
| `PdfChapterViewport` | 仅视口(需在 `EmbedPDF` 内) |
|
|
354
|
+
| `ChapterTreePanel` | 章节目录树 |
|
|
355
|
+
| `usePdfiumEngine` | PDFium 引擎 |
|
|
356
|
+
| `ChapterViewerOptions` | `options` 的类型 |
|
|
357
|
+
| `ChapterViewerConfig` | `options.features` 的类型 |
|
|
343
358
|
| `ChapterManifest` / `ChapterDescriptor` / `IChapterPdfLoader` | 数据与加载契约 |
|
|
344
359
|
| `ChapterViewerCatalog` / `ChapterTreeNode` | 目录树类型 |
|
|
345
360
|
| `applySelectionMarkup` | 编程式应用划线(高级) |
|
|
@@ -349,23 +364,16 @@ const features: ChapterViewerFeaturesConfig = {
|
|
|
349
364
|
|
|
350
365
|
---
|
|
351
366
|
|
|
352
|
-
##
|
|
367
|
+
## 接入检查清单
|
|
353
368
|
|
|
354
|
-
|
|
355
|
-
# 在 monorepo 根目录
|
|
356
|
-
pnpm install
|
|
357
|
-
pnpm --filter @embedpdf-editor/react-chapter-viewer build
|
|
358
|
-
pnpm --filter @embedpdf-editor/example-chapter-viewer-react dev
|
|
359
|
-
```
|
|
369
|
+
在业务项目中接入时,请确认:
|
|
360
370
|
|
|
361
|
-
|
|
371
|
+
1. **静态资源**:`manifest` 里每章 `source.url` 可被浏览器访问(或实现 `chapterPdfLoader` / `load()`)。
|
|
372
|
+
2. **目录数据**:若使用 `ChapterTreePanel`,自行从后端组装 `ChapterViewerCatalog`(`tree` + `manifest` 字段一致)。
|
|
373
|
+
3. **首章加载**:自定义布局下,首章 `ensureChapterLoaded` 返回 `loaded` 后再挂载 `PdfChapterViewport`。
|
|
374
|
+
4. **笔记 UI**:`onRequestCreateNote` 需在宿主弹窗后调用 `complete(noteId)`。
|
|
362
375
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
```bash
|
|
366
|
-
pnpm --filter @embedpdf-editor/example-chapter-viewer-react build
|
|
367
|
-
pnpm --filter @embedpdf-editor/example-chapter-viewer-react preview
|
|
368
|
-
```
|
|
376
|
+
本包随 npm 发布,**不包含**可运行的示例站点或 mock 数据;请以上文代码片段为模板,在业务仓库中创建页面并放置自有 PDF。
|
|
369
377
|
|
|
370
378
|
---
|
|
371
379
|
|
|
@@ -391,5 +399,10 @@ Vue 3 包同样只需安装对应包 + `vue`;视口内核与 React 版能力
|
|
|
391
399
|
**Q:如何关闭缩放?**
|
|
392
400
|
`features={{ ...DEFAULT_CHAPTER_VIEWER_FEATURES, zoom: { enabled: false } }}`。
|
|
393
401
|
|
|
402
|
+
**Q:`zoom.enabled: true` 但缩放手势没反应?**
|
|
403
|
+
1. 确认使用 `<ChapterPdfViewer />` 或 `<PdfChapterViewport features={...} />`(缩放逻辑在 `PdfChapterViewport` 内)。
|
|
404
|
+
2. 在 PDF 区域使用 **Ctrl/Cmd + 滚轮**,不要用普通滚轮。
|
|
405
|
+
3. 重新构建 `@embedpdf-editor/editor-engine` 与 `@embedpdf-editor/react-chapter-viewer` 后硬刷新浏览器。
|
|
406
|
+
|
|
394
407
|
**Q:笔记弹窗想完全自定义?**
|
|
395
408
|
使用 `onRequestCreateNote` + `complete(noteId)`,不要实现 `onCreateNote`。
|