kindy-editor 1.0.1 → 2.0.0
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/CAPABILITIES.md +39 -0
- package/CHANGELOG.md +56 -0
- package/GUIDE.md +190 -0
- package/MIGRATION.md +46 -0
- package/README.md +198 -253
- package/README.zh-CN.md +41 -192
- package/dist/assets/docx.worker-9ZRUe-2j.js +243 -0
- package/dist/kindy-core.cjs +786 -0
- package/dist/kindy-core.js +738 -0
- package/dist/kindy-editor.cjs +421 -76
- package/dist/kindy-editor.css +1 -1
- package/dist/kindy-editor.d.ts +165 -169
- package/dist/kindy-editor.js +55210 -30323
- package/dist/types/core/adapters/legacy.d.ts +9 -0
- package/dist/types/core/adapters/memory.d.ts +87 -0
- package/dist/types/core/adapters/rest.d.ts +27 -0
- package/dist/types/core/client.d.ts +47 -0
- package/dist/types/core/collaboration.d.ts +30 -0
- package/dist/types/core/editor-state.d.ts +47 -0
- package/dist/types/core/emitter.d.ts +19 -0
- package/dist/types/core/errors.d.ts +13 -0
- package/dist/types/core/index.d.ts +10 -0
- package/dist/types/core/state.d.ts +9 -0
- package/dist/types/core/types.d.ts +235 -0
- package/docs/README.md +15 -0
- package/docs/api-reference.md +510 -0
- package/docs/completion-roadmap.md +62 -0
- package/docs/getting-started.md +180 -0
- package/docs/performance.md +149 -0
- package/docs/plugin-development.md +100 -0
- package/docs/ui-engine.md +242 -0
- package/openapi/document-api.yaml +267 -0
- package/package.json +59 -9
- package/dist/kindy-editor.iife.js +0 -148
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
# API reference
|
|
2
|
+
|
|
3
|
+
Public API của `kindy-editor@2`. TypeScript declarations trong package là nguồn chính xác khi tài liệu và runtime khác nhau.
|
|
4
|
+
|
|
5
|
+
## Package entrypoints
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { KindyEditor, KindyDocumentLibrary } from 'kindy-editor'
|
|
9
|
+
import 'kindy-editor/style'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`kindy-editor` export UI, core, codecs, engines và mock helpers.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import {
|
|
16
|
+
createDocumentLibrary,
|
|
17
|
+
createRestDocumentAdapter,
|
|
18
|
+
} from 'kindy-editor/core'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`kindy-editor/core` chỉ export types, client, state, errors và adapters; entrypoint này không tải Vue UI.
|
|
22
|
+
|
|
23
|
+
## `KindyDocumentLibrary`
|
|
24
|
+
|
|
25
|
+
Workspace hoàn chỉnh kết nối UI với `DocumentLibraryClient`.
|
|
26
|
+
|
|
27
|
+
### Props
|
|
28
|
+
|
|
29
|
+
| Prop | Type | Default | Ghi chú |
|
|
30
|
+
|---|---|---|---|
|
|
31
|
+
| `adapter` | `DocumentApiAdapter` | — | Bắt buộc nếu không truyền `client` |
|
|
32
|
+
| `client` | `DocumentLibraryClient` | — | Client do ứng dụng chủ quản lý |
|
|
33
|
+
| `autosave` | `{ enabled?: boolean; delay?: number }` | client default | Delay tối thiểu 250ms |
|
|
34
|
+
| `stateSyncDelay` | `number` | `300` | Gom chuỗi transaction trước khi tạo canonical JSON; tối thiểu 32ms |
|
|
35
|
+
| `docxProfile` | `'kindy-docx-v2.0' \| 'kindy-docx-v2.1' \| 'kindy-docx-v2.2'` | `kindy-docx-v2.0` | Profile strict dùng cho import/export; chỉ nâng khi integration đã chấp nhận capability matrix tương ứng |
|
|
36
|
+
| `collaboration` | `CollaborationAdapter` | — | Adapter realtime của host; workspace connect/disconnect theo vòng đời document/editor |
|
|
37
|
+
| `user` | `{ id?, name?, color? }` | — | Identity dùng chung cho Track Changes và collaboration |
|
|
38
|
+
| `locale` | `string` | `vi-VN` | Library UI và editor locale |
|
|
39
|
+
| `theme` | `Record<string,string>` | `{}` | CSS variable overrides |
|
|
40
|
+
| `messages` | `Partial<KindyLibraryMessages>` | `{}` | Override Library UI labels |
|
|
41
|
+
| `ui` | `Partial<KindyLibraryUiOptions>` | defaults | Layout/density/panels |
|
|
42
|
+
| `editorOptions` | `Record<string,unknown>` | preset Contract | Override cấu hình `KindyEditor`; mặc định compact và page-only |
|
|
43
|
+
| `showVersions` | `boolean` | `true` | Backward-compatible versions switch |
|
|
44
|
+
| `confirmCompatibility` | `(report) => boolean \| Promise<boolean>` | `window.confirm` | Xác nhận best-effort import/export |
|
|
45
|
+
|
|
46
|
+
Phải truyền đúng một trong `adapter` hoặc `client`. Khi tự truyền client, component không destroy client lúc unmount.
|
|
47
|
+
|
|
48
|
+
### Events
|
|
49
|
+
|
|
50
|
+
| Event | Payload |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `ready` | `{ document, editor }` |
|
|
53
|
+
| `opened` | `DocumentSnapshot` |
|
|
54
|
+
| `changed` | `DocumentSnapshot` |
|
|
55
|
+
| `save-started` | `{ documentId, reason }` |
|
|
56
|
+
| `saved` | `SaveResult & { documentId }` |
|
|
57
|
+
| `save-failed` | `DocumentLibraryError` |
|
|
58
|
+
| `created` | `DocumentSummary` |
|
|
59
|
+
| `imported` | `DocumentSummary` |
|
|
60
|
+
| `compatibility-warning` | `CompatibilityReport` |
|
|
61
|
+
| `version-restored` | `DocumentSnapshot` |
|
|
62
|
+
| `printed` | `DocumentRecord` |
|
|
63
|
+
| `locale-changed` | `string` |
|
|
64
|
+
| `error` | `unknown` |
|
|
65
|
+
|
|
66
|
+
### Exposed handle
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
workspace.client
|
|
70
|
+
workspace.openDocument(document)
|
|
71
|
+
workspace.closeDocument()
|
|
72
|
+
workspace.save()
|
|
73
|
+
workspace.exportDocx({ mode, store, fileName, preferOriginal })
|
|
74
|
+
workspace.downloadDocx()
|
|
75
|
+
workspace.importDocument() // mở file picker và import qua client/adapter
|
|
76
|
+
workspace.preparePrint()
|
|
77
|
+
workspace.print()
|
|
78
|
+
workspace.getState()
|
|
79
|
+
workspace.flushState()
|
|
80
|
+
workspace.getEditor()
|
|
81
|
+
workspace.refresh()
|
|
82
|
+
workspace.toggleExplorer(open?)
|
|
83
|
+
workspace.toggleVersions(open?)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`exportDocx()` trả `{ blob, report, source }` nhưng không tự download; `source` là `original` hoặc `serialized`. Mặc định hàm ưu tiên blob gốc nếu `DocumentRecord.originalSource.revisionId` vẫn là revision đang mở; đặt `preferOriginal: false` để buộc serialize. `downloadDocx()` cũng ưu tiên artifact gốc (Blob hoặc URL), sau đó mới chạy flow strict → confirm best-effort → download.
|
|
87
|
+
`save()` luôn flush transaction đang chờ. `flushState()` chỉ đồng bộ editor vào client và đánh dấu dirty, không tự tạo version.
|
|
88
|
+
|
|
89
|
+
## `KindyDocumentLibraryShell`
|
|
90
|
+
|
|
91
|
+
Layout component không có IO hoặc document state.
|
|
92
|
+
|
|
93
|
+
### Props
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
{
|
|
97
|
+
density?: 'compact' | 'comfortable'
|
|
98
|
+
explorerWidth?: string
|
|
99
|
+
versionsWidth?: string
|
|
100
|
+
explorerOpen?: boolean
|
|
101
|
+
versionsOpen?: boolean
|
|
102
|
+
showExplorer?: boolean
|
|
103
|
+
showVersions?: boolean
|
|
104
|
+
showTopbar?: boolean
|
|
105
|
+
theme?: Record<string, string>
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Slots: `explorer`, `topbar`, default workspace content, `versions`.
|
|
110
|
+
|
|
111
|
+
Event: `close-panels` khi người dùng click scrim ở viewport nhỏ.
|
|
112
|
+
|
|
113
|
+
## `KindyDocumentExplorer`
|
|
114
|
+
|
|
115
|
+
Component Explorer độc lập. Component gọi trực tiếp `client.listDocuments`, `adapter.listTemplates`, `adapter.listFolders`, import codec và `client.create/import`.
|
|
116
|
+
|
|
117
|
+
### Props
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
{
|
|
121
|
+
client: DocumentLibraryClient
|
|
122
|
+
selectedId?: string
|
|
123
|
+
locale?: string
|
|
124
|
+
messages?: Partial<KindyLibraryMessages>
|
|
125
|
+
confirmCompatibility?: (report: CompatibilityReport) => boolean | Promise<boolean>
|
|
126
|
+
closable?: boolean
|
|
127
|
+
pageSize?: number
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Events: `open`, `created`, `imported`, `compatibility-warning`, `error`, `close`.
|
|
132
|
+
|
|
133
|
+
Slots: `title`, `actions`, `document`.
|
|
134
|
+
|
|
135
|
+
Handle: `refresh()`, `clearSearch()`.
|
|
136
|
+
|
|
137
|
+
Refresh mới abort request cũ để tránh kết quả search đến sai thứ tự.
|
|
138
|
+
|
|
139
|
+
## `KindyVersionPanel`
|
|
140
|
+
|
|
141
|
+
### Props
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
{
|
|
145
|
+
client: DocumentLibraryClient
|
|
146
|
+
documentId?: string
|
|
147
|
+
currentVersionId?: string
|
|
148
|
+
previewVersionId?: string
|
|
149
|
+
canRestore?: boolean
|
|
150
|
+
locale?: string
|
|
151
|
+
messages?: Partial<KindyLibraryMessages>
|
|
152
|
+
closable?: boolean
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Events: `preview`, `restore`, `error`, `close`.
|
|
157
|
+
|
|
158
|
+
Handle: `refresh()`.
|
|
159
|
+
|
|
160
|
+
Panel không tự load snapshot hoặc restore; component cha quyết định hành động khi nhận event.
|
|
161
|
+
|
|
162
|
+
## `KindyEditor`
|
|
163
|
+
|
|
164
|
+
Editor Vue độc lập. Props đầy đủ vẫn tương thích options hiện có; các prop chính:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
interface KindyEditorOptions {
|
|
168
|
+
locale?: 'vi-VN' | 'en-US' | 'zh-CN' | 'it-IT' | 'ru-RU'
|
|
169
|
+
theme?: 'light' | 'dark' | 'auto'
|
|
170
|
+
skin?: 'default' | 'modern'
|
|
171
|
+
height?: string
|
|
172
|
+
toolbar?: Record<string, unknown>
|
|
173
|
+
statusbar?: Record<string, boolean>
|
|
174
|
+
page?: Record<string, unknown>
|
|
175
|
+
document?: {
|
|
176
|
+
title?: string
|
|
177
|
+
content?: string | JSONContent
|
|
178
|
+
assets?: AssetReference[]
|
|
179
|
+
readOnly?: boolean
|
|
180
|
+
autoSave?: { enabled: boolean; interval?: number }
|
|
181
|
+
}
|
|
182
|
+
translations?: Record<string, Record<string, string>>
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Contract editor preset
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
CONTRACT_EDITOR_OPTIONS
|
|
190
|
+
createContractEditorOptions(overrides?)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Preset mặc định của `KindyDocumentLibrary` dùng `toolbar.defaultMode = 'classic'`, `toolbar.allowModeSwitch = false`, các nhóm `base/insert/table/page/export`, `page.layouts = ['page']` và vô hiệu hóa các extension ngoài phạm vi hợp đồng. Trong workspace, Import/Export trên toolbar được chuyển sang pipeline của `DocumentLibraryClient` để giữ đúng revision và artifact. Các field status bar:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
{
|
|
197
|
+
showOutline, showSpellcheck, showShortcuts, showReset,
|
|
198
|
+
showLayout, showPageStatus, showWordCount, showBranding,
|
|
199
|
+
showFullscreen, showPreview, showZoom, showLocale
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Các method public thường dùng:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
editor.setContent(content, options?)
|
|
207
|
+
editor.insertContent(content)
|
|
208
|
+
editor.getContent(type?)
|
|
209
|
+
editor.getJSON()
|
|
210
|
+
editor.getState()
|
|
211
|
+
editor.getHTML()
|
|
212
|
+
editor.getText()
|
|
213
|
+
editor.getPage()
|
|
214
|
+
editor.setPage(page)
|
|
215
|
+
editor.setReadOnly(readOnly)
|
|
216
|
+
editor.focus()
|
|
217
|
+
editor.preparePrint()
|
|
218
|
+
editor.print()
|
|
219
|
+
editor.saveContent()
|
|
220
|
+
editor.useEditor() // raw Tiptap Editor
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Khi editor nằm trong `KindyDocumentLibrary`, không bật `document.autoSave`; workspace dùng `DocumentLibraryClient` làm pipeline lưu duy nhất.
|
|
224
|
+
|
|
225
|
+
## `mountKindyEditor`
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
const mounted = mountKindyEditor(container, props)
|
|
229
|
+
|
|
230
|
+
mounted.app // Vue App
|
|
231
|
+
mounted.instance // KindyEditor handle
|
|
232
|
+
mounted.unmount()
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`container` là HTMLElement hoặc CSS selector.
|
|
236
|
+
|
|
237
|
+
## `KindyDocumentState`
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
interface KindyDocumentState {
|
|
241
|
+
schemaVersion: '2.0'
|
|
242
|
+
content: JSONContent
|
|
243
|
+
page: KindyPageState
|
|
244
|
+
assets: AssetReference[]
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
State phải là JSON serializable. Không lưu Vue proxy, DOM node, File object hoặc callback trong state.
|
|
249
|
+
|
|
250
|
+
`DocumentRecord` của một tài liệu import có thể chứa:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
originalSource?: {
|
|
254
|
+
artifactId: string
|
|
255
|
+
revisionId: string
|
|
256
|
+
format: 'original-docx'
|
|
257
|
+
fileName: string
|
|
258
|
+
compatibilityReport?: CompatibilityReport
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Backend REST phải trả binding này từ `/documents/import` nếu muốn workspace hỗ trợ tải nguyên bản byte-for-byte.
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
const state = createEmptyDocumentState({ content, page, assets })
|
|
266
|
+
const migrated = migrateDocumentState(input)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
`migrateDocumentState` chấp nhận raw ProseMirror document v1 và bọc thành state v2.
|
|
270
|
+
|
|
271
|
+
## `DocumentLibraryClient`
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
const client = createDocumentLibrary({
|
|
275
|
+
adapter,
|
|
276
|
+
autosave: { enabled: true, delay: 1500 },
|
|
277
|
+
codecs,
|
|
278
|
+
engine,
|
|
279
|
+
locale: 'vi-VN',
|
|
280
|
+
theme,
|
|
281
|
+
})
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Properties
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
client.adapter
|
|
288
|
+
client.current // clone của snapshot hiện tại hoặc null
|
|
289
|
+
client.hasUnsavedChanges
|
|
290
|
+
client.hasConflict
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Methods
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
client.listDocuments(query?, signal?)
|
|
297
|
+
client.open(documentId, versionId?, signal?)
|
|
298
|
+
client.create(input, signal?)
|
|
299
|
+
client.import(input, signal?)
|
|
300
|
+
const snapshot = client.updateState(state)
|
|
301
|
+
client.save('autosave' | 'manual')
|
|
302
|
+
client.restore(versionId, signal?)
|
|
303
|
+
client.storeArtifact(input, signal?)
|
|
304
|
+
client.resolveConflict(snapshot)
|
|
305
|
+
client.destroy()
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
`updateState` trả snapshot clone, đánh dấu dirty và lên lịch autosave. Nếu có save đang chạy, `save()` chờ save đó hoàn tất rồi mới chạy lượt kế tiếp. Backend phải dùng `baseRevisionId` để compare-and-swap.
|
|
309
|
+
|
|
310
|
+
### Client events
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
const off = client.on('saved', listener)
|
|
314
|
+
off()
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Events: `opened`, `imported`, `changed`, `save-started`, `saved`, `save-failed`, `version-restored`, `error`.
|
|
318
|
+
|
|
319
|
+
## `DocumentApiAdapter`
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
interface DocumentApiAdapter {
|
|
323
|
+
listDocuments(query?, signal?): Promise<Page<DocumentSummary>>
|
|
324
|
+
getDocument(documentId, signal?): Promise<DocumentRecord>
|
|
325
|
+
createDocument(input, signal?): Promise<DocumentRecord>
|
|
326
|
+
importDocument(input, signal?): Promise<DocumentRecord>
|
|
327
|
+
updateDocument(documentId, patch, signal?): Promise<DocumentRecord>
|
|
328
|
+
loadState(documentId, versionId?, signal?): Promise<DocumentSnapshot>
|
|
329
|
+
saveState(documentId, input, signal?): Promise<SaveResult>
|
|
330
|
+
listVersions(documentId, query?, signal?): Promise<Page<DocumentVersion>>
|
|
331
|
+
restoreVersion(documentId, versionId, signal?): Promise<DocumentSnapshot>
|
|
332
|
+
listFolders(query?, signal?): Promise<Folder[]>
|
|
333
|
+
listTemplates(query?, signal?): Promise<Page<DocumentSummary>>
|
|
334
|
+
storeArtifact(documentId, input, signal?): Promise<DocumentArtifact>
|
|
335
|
+
getArtifact(documentId, artifactId, signal?): Promise<DocumentArtifact>
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Mọi method phải tôn trọng `AbortSignal` nếu transport hỗ trợ cancellation.
|
|
340
|
+
|
|
341
|
+
### Save input
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
interface SaveStateInput {
|
|
345
|
+
state: KindyDocumentState
|
|
346
|
+
baseRevisionId: string
|
|
347
|
+
reason: 'autosave' | 'manual'
|
|
348
|
+
clientMutationId: string
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Backend trả HTTP 409 hoặc lỗi `VERSION_CONFLICT` khi `baseRevisionId` không phải revision hiện hành.
|
|
353
|
+
|
|
354
|
+
## REST adapter
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
const adapter = createRestDocumentAdapter({
|
|
358
|
+
baseUrl: '/document-api',
|
|
359
|
+
transport: customFetch,
|
|
360
|
+
})
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
`transport` có cùng contract với `fetch`. REST adapter map:
|
|
364
|
+
|
|
365
|
+
- HTTP 404 → `DOCUMENT_NOT_FOUND`.
|
|
366
|
+
- HTTP 409 → `VERSION_CONFLICT`.
|
|
367
|
+
- HTTP lỗi khác → `ADAPTER_ERROR`.
|
|
368
|
+
- Fetch/transport failure → `NETWORK_ERROR`.
|
|
369
|
+
|
|
370
|
+
## Memory adapter
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
const adapter = createMemoryDocumentAdapter({
|
|
374
|
+
documents: snapshots,
|
|
375
|
+
folders,
|
|
376
|
+
})
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Manual save tạo version; autosave chỉ tạo revision. Restore tạo revision và version mới. Adapter giữ Blob trong memory và không bền vững qua reload.
|
|
380
|
+
|
|
381
|
+
## DOCX codecs
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
inspectDocx(file)
|
|
385
|
+
extractDocxPackage(file, limits?)
|
|
386
|
+
ooxmlToDocumentState(parts)
|
|
387
|
+
importDocx(file, options?)
|
|
388
|
+
importDocxInWorker(file, options?)
|
|
389
|
+
exportDocx(state, options?)
|
|
390
|
+
createDocxCodec()
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Import options
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
{
|
|
397
|
+
mode?: 'strict' | 'best-effort'
|
|
398
|
+
signal?: AbortSignal // worker variant
|
|
399
|
+
limits?: Partial<DocxImportLimits>
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Limits mặc định kiểm tra compressed bytes, uncompressed bytes, số ZIP entry và compression ratio.
|
|
404
|
+
|
|
405
|
+
### Results
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
interface DocxImportResult {
|
|
409
|
+
state: KindyDocumentState
|
|
410
|
+
report: CompatibilityReport
|
|
411
|
+
messages: Array<{ type: string; message: string }>
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
interface DocxExportResult {
|
|
415
|
+
blob: Blob
|
|
416
|
+
report: CompatibilityReport
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## Errors
|
|
421
|
+
|
|
422
|
+
```ts
|
|
423
|
+
class DocumentLibraryError extends Error {
|
|
424
|
+
readonly code: DocumentErrorCode
|
|
425
|
+
readonly status?: number
|
|
426
|
+
readonly details?: unknown
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Codes:
|
|
431
|
+
|
|
432
|
+
```text
|
|
433
|
+
DOCX_INVALID
|
|
434
|
+
DOCX_UNSUPPORTED
|
|
435
|
+
IMPORT_FAILED
|
|
436
|
+
EXPORT_FAILED
|
|
437
|
+
ADAPTER_ERROR
|
|
438
|
+
NETWORK_ERROR
|
|
439
|
+
VERSION_CONFLICT
|
|
440
|
+
DOCUMENT_NOT_FOUND
|
|
441
|
+
OPERATION_CANCELLED
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Không kiểm tra message string; luôn branch theo `error.code`.
|
|
445
|
+
|
|
446
|
+
## UI theme/messages helpers
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
DEFAULT_LIBRARY_UI
|
|
450
|
+
DEFAULT_LIBRARY_THEME
|
|
451
|
+
VI_LIBRARY_MESSAGES
|
|
452
|
+
EN_LIBRARY_MESSAGES
|
|
453
|
+
resolveLibraryUi(options)
|
|
454
|
+
resolveLibraryMessages(locale, overrides)
|
|
455
|
+
createLibraryTheme(overrides)
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Xem [UI engine](./ui-engine.md).
|
|
459
|
+
|
|
460
|
+
## Engine adapter
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
interface EditorEngineAdapter {
|
|
464
|
+
readonly id: string
|
|
465
|
+
mount(container, options?): EditorEngineHandle | Promise<EditorEngineHandle>
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
V2 ship `TiptapEngineAdapter`:
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
const engine = createTiptapEngineAdapter()
|
|
473
|
+
const handle = engine.mount(container, options)
|
|
474
|
+
await handle.load(state)
|
|
475
|
+
handle.onChange(nextState => {})
|
|
476
|
+
handle.setReadOnly(true)
|
|
477
|
+
handle.destroy()
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
`EditorEngineAdapter` là extension boundary. Workspace Vue hiện dùng `KindyEditor` trực tiếp; adapter phù hợp khi ứng dụng muốn mount engine theo kiểu headless controller.
|
|
481
|
+
|
|
482
|
+
## Collaboration adapter
|
|
483
|
+
|
|
484
|
+
```ts
|
|
485
|
+
const collaboration = createYjsCollaborationAdapter({
|
|
486
|
+
providerFactory: context => createCompanyProvider(context),
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
// Workspace sở hữu vòng đời connect/disconnect; Yjs editor extensions/provider
|
|
490
|
+
// vẫn do ứng dụng chủ cấu hình vì SDK không ship collaboration server.
|
|
491
|
+
<KindyDocumentLibrary
|
|
492
|
+
:adapter="documentAdapter"
|
|
493
|
+
:collaboration="collaboration"
|
|
494
|
+
:user="{ id: currentUser.id, name: currentUser.name, color: currentUser.color }"
|
|
495
|
+
/>
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
SDK không ship WebSocket server. Ứng dụng chủ chịu trách nhiệm auth, document isolation, provider lifecycle và awareness.
|
|
499
|
+
|
|
500
|
+
## Mock REST transport
|
|
501
|
+
|
|
502
|
+
```ts
|
|
503
|
+
const mock = createMockDocumentTransport({ latency: 20 })
|
|
504
|
+
const adapter = createRestDocumentAdapter({
|
|
505
|
+
baseUrl: mock.baseUrl,
|
|
506
|
+
transport: mock.transport,
|
|
507
|
+
})
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Mock dùng Memory adapter phía sau và phù hợp cho contract test không cần HTTP server.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Completion roadmap của Kindy Editor v2
|
|
2
|
+
|
|
3
|
+
Tài liệu này là bảng kiểm phát hành, không phải danh sách ý tưởng. Một capability
|
|
4
|
+
chỉ chuyển sang **Done** khi runtime, automated test, compatibility fixture và
|
|
5
|
+
tài liệu public cùng khớp nhau.
|
|
6
|
+
|
|
7
|
+
## Trạng thái hiện tại
|
|
8
|
+
|
|
9
|
+
| Hạng mục | Trạng thái | Bằng chứng / gate còn thiếu |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Document state, migration, client, optimistic concurrency | Done v2.0 | Contract/unit tests trong `src/core/__tests__` |
|
|
12
|
+
| Memory/REST/legacy adapters và OpenAPI | Done v2.0 | Adapter contract + OpenAPI route test |
|
|
13
|
+
| Explorer, workspace, version preview/restore | Done v2.0 | Playwright E2E production và CI job |
|
|
14
|
+
| DOCX body text/style/list/table/image/page break/page setup | Done trong profile v2.0 | Golden/unit test; DOCX v2.2 đã mở và convert PDF bằng LibreOffice headless không yêu cầu repair |
|
|
15
|
+
| Browser print | Supported browser flow | E2E kiểm tra iframe/@page/content; vẫn không phải deterministic PDF Blob |
|
|
16
|
+
| Tài liệu dài | 100 trang text/mixed pass local production gate | Text 490,3ms/21,5ms p95; mixed 849,7ms/36,5ms p95, 40 mẫu. Pagination 2,8–3,7ms. CI upload JSON; 200 mixed vẫn là stress limit |
|
|
17
|
+
| Multiple section DOCX round-trip | Done v2.1 codec/pagination/print | Editing canvas liên tục vẫn có thể khác Word; cần Chromium visual regression |
|
|
18
|
+
| Header/footer DOCX import + variants | Done v2.1 codec | Golden strict gồm default/first/even và logo header |
|
|
19
|
+
| Page-number start/first/odd/even | Done v2.1 codec | Golden strict gồm page-number start và section landscape |
|
|
20
|
+
| Comment DOCX round-trip | Done v2.2 codec/editor | Range, reply, resolved và strict round-trip pass |
|
|
21
|
+
| Track Changes DOCX round-trip | Done v2.2 codec/editor | Insert/delete/replace transaction, author/time, accept/reject, undo/redo, table text pass |
|
|
22
|
+
| Realtime Yjs | SDK lifecycle + local convergence done | Hai Tiptap editor dùng chung host Y.Doc hội tụ/recreate không nhân đôi; host vẫn phải test provider/server, auth và network reconnect thật |
|
|
23
|
+
|
|
24
|
+
## Thứ tự hoàn thiện bắt buộc
|
|
25
|
+
|
|
26
|
+
### Gate A — v2.0 release candidate
|
|
27
|
+
|
|
28
|
+
- Regression, typecheck, build và package audit pass.
|
|
29
|
+
- Golden DOCX v2.2 mở được bằng LibreOffice headless không yêu cầu repair. **Done** trên Debian/LibreOffice 7.4.7 ARM64 và chạy lại trong CI.
|
|
30
|
+
- Browser E2E: Explorer → import → edit → autosave → version → restore → export → print. **Done** bằng Playwright production build.
|
|
31
|
+
- Corpus text 100/200 trang ghi JSON benchmark artifact trong CI (`benchmark:ci` + GitHub Actions đã cấu hình).
|
|
32
|
+
- Corpus text/mixed 100 trang chạy trong Playwright; không công bố SLA nếu p95 fail trên thiết bị tích hợp.
|
|
33
|
+
- Xóa hoặc giữ ngoài public entrypoint toàn bộ prototype AI/IO/storage cũ.
|
|
34
|
+
|
|
35
|
+
### Gate B — v2.1 sections/header/footer
|
|
36
|
+
|
|
37
|
+
- Mọi section có stable ID và semantic `sectionBreak` node.
|
|
38
|
+
- Import/export `sectPr`, page size/orientation/margin và page-number start theo từng section.
|
|
39
|
+
- Header/footer là ProseMirror sub-document; hỗ trợ default/first/odd/even trong profile đã công bố.
|
|
40
|
+
- Golden DOCX cho section landscape giữa tài liệu, header logo, footer số trang và different-first-page.
|
|
41
|
+
- Pagination browser đọc section boundary; vẫn ghi rõ không phải Word layout engine.
|
|
42
|
+
|
|
43
|
+
### Gate C — v2.2 review/realtime
|
|
44
|
+
|
|
45
|
+
- Comment range/thread import/export bằng OOXML comments parts.
|
|
46
|
+
- Track insert/delete có author, timestamp, stable ID; accept/reject từng change và toàn bộ.
|
|
47
|
+
- Track Changes golden test gồm text, undo/redo và text trong table.
|
|
48
|
+
- Hai editor dùng host-provided Yjs provider hội tụ cùng document state; reconnect không nhân đôi transaction.
|
|
49
|
+
- Comment, review và collaboration dùng identity do ứng dụng chủ truyền vào.
|
|
50
|
+
|
|
51
|
+
### Gate D — release nhưng chưa publish
|
|
52
|
+
|
|
53
|
+
- `npm pack --dry-run` chỉ chứa file public đã khai báo. **Done** — 34 file, khoảng 1,7MB nén; `test:package` build lại tarball trong một app Vite consumer và kiểm tra DOCX Worker/CSS.
|
|
54
|
+
- README/API/types/capability matrix khớp runtime. **Done** và được kiểm tra cùng build/typecheck.
|
|
55
|
+
- Changelog và migration guide hoàn chỉnh. **Done**.
|
|
56
|
+
- Tạo release candidate local. **Done** với `kindy-editor-2.0.0.tgz`; chỉ publish npm khi chủ dự án yêu cầu lại.
|
|
57
|
+
|
|
58
|
+
## Definition of done cho hiệu năng
|
|
59
|
+
|
|
60
|
+
Mỗi báo cáo phải ghi browser, máy, build mode, corpus và số mẫu. Ngân sách mặc
|
|
61
|
+
định: open 100 trang text ≤ 3 giây, typing p95 ≤ 50 ms, pagination sau khi dừng
|
|
62
|
+
gõ ≤ 500 ms. Kết quả text không được suy rộng sang bảng, ảnh hoặc review.
|