kindy-editor 1.0.0 → 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 +204 -195
- 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 +55221 -30317
- 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
package/README.md
CHANGED
|
@@ -1,269 +1,278 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
|
|
1
|
+
# Kindy Editor v2
|
|
2
|
+
|
|
3
|
+
Kindy Editor là SDK Document Library chuyên cho DOCX, xây dựng bằng Vue 3 và Tiptap/ProseMirror. Thư viện cung cấp giao diện quản lý tài liệu, editor trên web, lịch sử phiên bản, DOCX codec và hợp đồng API để ứng dụng chủ kết nối backend của mình.
|
|
4
|
+
|
|
5
|
+
Kindy Editor **không** cung cấp backend, database, authentication, object storage hoặc nghiệp vụ hợp đồng. JSON `KindyDocumentState` là trạng thái chỉnh sửa chuẩn; DOCX gốc và DOCX export là artifact do server ứng dụng chủ lưu.
|
|
6
|
+
|
|
7
|
+
## Phạm vi v2.0
|
|
8
|
+
|
|
9
|
+
- Import DOCX trong Web Worker, kiểm tra ZIP/OOXML và trả `CompatibilityReport`.
|
|
10
|
+
- Chỉnh sửa nội dung bằng Tiptap/ProseMirror với preset `Contract`: toolbar compact, page-only và chỉ giữ công cụ cần cho hợp đồng.
|
|
11
|
+
- Explorer theo thư mục, tìm kiếm, tạo tài liệu trống và tạo từ template.
|
|
12
|
+
- Autosave có debounce, cancellation và optimistic concurrency.
|
|
13
|
+
- Lưu thủ công tạo version, xem read-only và khôi phục version.
|
|
14
|
+
- Export DOCX OOXML thật bằng package `docx`.
|
|
15
|
+
- In hoặc Save as PDF qua print dialog của trình duyệt.
|
|
16
|
+
- UI engine có shell responsive, theme tokens, locale messages, slots và typed hooks.
|
|
17
|
+
|
|
18
|
+
Khả năng round-trip DOCX chỉ được cam kết trong [Kindy DOCX Compatibility Profile](./CAPABILITIES.md). Kindy không phải Microsoft Word layout engine và không cam kết giữ nguyên mọi tính năng OOXML.
|
|
19
|
+
|
|
20
|
+
Ngoại lệ duy nhất có thể bảo đảm byte-for-byte là tài liệu vừa import và **chưa có revision chỉnh sửa**: workspace tải lại artifact `original-docx` thay vì serialize JSON. Sau lần chỉnh sửa/save đầu tiên, DOCX mới được dựng từ `KindyDocumentState` và chỉ có cam kết theo compatibility profile.
|
|
21
|
+
|
|
22
|
+
## Kiến trúc
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
Ứng dụng chủ
|
|
26
|
+
├─ KindyDocumentLibrary
|
|
27
|
+
│ ├─ DocumentLibraryShell layout/responsive/theme
|
|
28
|
+
│ ├─ KindyDocumentExplorer list/search/folder/import/template
|
|
29
|
+
│ ├─ KindyEditor Tiptap/ProseMirror editor
|
|
30
|
+
│ └─ KindyVersionPanel preview/restore
|
|
31
|
+
├─ DocumentLibraryClient autosave/conflict/events
|
|
32
|
+
├─ DOCX codecs / browser print
|
|
33
|
+
└─ DocumentApiAdapter
|
|
34
|
+
├─ RestDocumentAdapter
|
|
35
|
+
├─ MemoryDocumentAdapter
|
|
36
|
+
└─ adapter riêng của ứng dụng
|
|
37
|
+
```
|
|
18
38
|
|
|
19
|
-
|
|
39
|
+
UI không gọi URL cố định và không biết token. Mọi IO đi qua `DocumentApiAdapter`.
|
|
20
40
|
|
|
21
|
-
##
|
|
41
|
+
## Cài đặt
|
|
22
42
|
|
|
23
43
|
```bash
|
|
24
44
|
npm install kindy-editor
|
|
25
|
-
# hoặc
|
|
26
|
-
pnpm add kindy-editor
|
|
27
|
-
# hoặc
|
|
28
|
-
yarn add kindy-editor
|
|
29
45
|
```
|
|
30
46
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
```ts
|
|
48
|
+
import 'kindy-editor/style'
|
|
49
|
+
```
|
|
34
50
|
|
|
35
|
-
|
|
51
|
+
Vue phải được cài trong ứng dụng chủ và đáp ứng peer dependency `vue ^3.5.0`.
|
|
36
52
|
|
|
37
|
-
|
|
53
|
+
## Quick start với REST API
|
|
38
54
|
|
|
39
55
|
```vue
|
|
40
56
|
<template>
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
57
|
+
<KindyDocumentLibrary
|
|
58
|
+
ref="workspace"
|
|
59
|
+
:adapter="adapter"
|
|
60
|
+
:autosave="{ enabled: true, delay: 5000 }"
|
|
61
|
+
:ui="{ explorerWidth: '320px', versionsWidth: '300px' }"
|
|
62
|
+
:theme="{ '--kindy-library-primary': '#0b74de' }"
|
|
63
|
+
locale="vi-VN"
|
|
64
|
+
style="height: 100vh"
|
|
65
|
+
@saved="onSaved"
|
|
66
|
+
@error="onError"
|
|
67
|
+
/>
|
|
44
68
|
</template>
|
|
45
69
|
|
|
46
|
-
<script setup>
|
|
70
|
+
<script setup lang="ts">
|
|
47
71
|
import { ref } from 'vue'
|
|
48
|
-
import {
|
|
72
|
+
import {
|
|
73
|
+
KindyDocumentLibrary,
|
|
74
|
+
createRestDocumentAdapter,
|
|
75
|
+
} from 'kindy-editor'
|
|
49
76
|
import 'kindy-editor/style'
|
|
50
77
|
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
78
|
+
const workspace = ref()
|
|
79
|
+
const adapter = createRestDocumentAdapter({
|
|
80
|
+
baseUrl: '/document-api',
|
|
81
|
+
transport: (url, init) => fetch(url, {
|
|
82
|
+
...init,
|
|
83
|
+
credentials: 'include',
|
|
84
|
+
headers: { ...init?.headers, 'x-tenant-id': 'acme' },
|
|
85
|
+
}),
|
|
58
86
|
})
|
|
87
|
+
|
|
88
|
+
function onSaved(result: unknown) {
|
|
89
|
+
console.info('Saved', result)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function onError(error: unknown) {
|
|
93
|
+
console.error(error)
|
|
94
|
+
}
|
|
59
95
|
</script>
|
|
60
96
|
```
|
|
61
97
|
|
|
62
|
-
|
|
98
|
+
Server phải triển khai contract trong [`openapi/document-api.yaml`](./openapi/document-api.yaml). OpenAPI không bắt buộc cơ chế auth; cookie, bearer token hoặc API gateway do ứng dụng chủ quyết định.
|
|
63
99
|
|
|
64
|
-
|
|
65
|
-
import { createApp } from 'vue'
|
|
66
|
-
import App from './App.vue'
|
|
67
|
-
import { useKindyEditor } from 'kindy-editor'
|
|
68
|
-
import 'kindy-editor/style'
|
|
100
|
+
### UI hợp đồng mặc định
|
|
69
101
|
|
|
70
|
-
|
|
102
|
+
`KindyDocumentLibrary` tự dùng `CONTRACT_EDITOR_OPTIONS`: toolbar một hàng theo hướng Google Docs, chỉ có Định dạng/Chèn/Bảng/Trang/Xuất, không có Web view, Mermaid, biểu đồ, media web hoặc chuyển đổi số tiếng Trung. Nhóm Xuất chỉ gọi pipeline an toàn của Document Library cho Import DOCX, Export DOCX và In/PDF. Thanh trạng thái chỉ còn `Trang X / Y`, số ký tự, zoom và ngôn ngữ.
|
|
71
103
|
|
|
72
|
-
|
|
73
|
-
app.use(useKindyEditor, {
|
|
74
|
-
locale: 'vi-VN',
|
|
75
|
-
theme: 'light',
|
|
76
|
-
})
|
|
104
|
+
Ứng dụng chủ vẫn có thể override rõ ràng qua `editorOptions`:
|
|
77
105
|
|
|
78
|
-
|
|
106
|
+
```vue
|
|
107
|
+
<KindyDocumentLibrary
|
|
108
|
+
:adapter="adapter"
|
|
109
|
+
:editor-options="{
|
|
110
|
+
toolbar: { menus: ['base', 'insert', 'table', 'page', 'export'] },
|
|
111
|
+
statusbar: { showLocale: false },
|
|
112
|
+
}"
|
|
113
|
+
/>
|
|
79
114
|
```
|
|
80
115
|
|
|
81
|
-
|
|
116
|
+
Để dùng preset với `KindyEditor` độc lập:
|
|
82
117
|
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
import { KindyEditor } from 'kindy-editor' // Tự động resolve qua package.json "require"/"import"
|
|
86
|
-
import 'kindy-editor/style'
|
|
87
|
-
```
|
|
118
|
+
```ts
|
|
119
|
+
import { createContractEditorOptions } from 'kindy-editor'
|
|
88
120
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
```html
|
|
92
|
-
<!DOCTYPE html>
|
|
93
|
-
<html>
|
|
94
|
-
<head>
|
|
95
|
-
<!-- 1. CSS -->
|
|
96
|
-
<link rel="stylesheet" href="https://unpkg.com/kindy-editor/dist/kindy-editor.css">
|
|
97
|
-
<!-- 2. Vue 3 -->
|
|
98
|
-
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
|
99
|
-
<!-- 3. Kindy Editor IIFE Bundle -->
|
|
100
|
-
<script src="https://unpkg.com/kindy-editor/dist/kindy-editor.iife.js"></script>
|
|
101
|
-
</head>
|
|
102
|
-
<body>
|
|
103
|
-
<div id="app">
|
|
104
|
-
<kindy-editor :locale="'vi-VN'"></kindy-editor>
|
|
105
|
-
</div>
|
|
106
|
-
|
|
107
|
-
<script>
|
|
108
|
-
const { createApp } = Vue;
|
|
109
|
-
const app = createApp({});
|
|
110
|
-
app.use(KindyEditor.useKindyEditor);
|
|
111
|
-
app.mount('#app');
|
|
112
|
-
</script>
|
|
113
|
-
</body>
|
|
114
|
-
### Cách 5: Tích hợp vào React.js & Next.js (Sử dụng `mountKindyEditor`)
|
|
115
|
-
|
|
116
|
-
Hàm `mountKindyEditor` cho phép bạn gắn `kindy-editor` vào bất kỳ thẻ HTML nào trong React.js / Next.js / Angular / Svelte mà **không cần cài thêm bất kỳ thư viện phụ thuộc nào**:
|
|
117
|
-
|
|
118
|
-
#### A. Trong React.js (Component Wrapper):
|
|
119
|
-
|
|
120
|
-
```tsx
|
|
121
|
-
import React, { useEffect, useRef } from 'react'
|
|
122
|
-
import { mountKindyEditor } from 'kindy-editor'
|
|
123
|
-
import 'kindy-editor/style'
|
|
121
|
+
const editorOptions = createContractEditorOptions({ locale: 'vi-VN' })
|
|
122
|
+
```
|
|
124
123
|
|
|
125
|
-
|
|
126
|
-
const containerRef = useRef(null)
|
|
124
|
+
## Chạy demo không cần backend
|
|
127
125
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// Gắn Editor vào DOM container
|
|
131
|
-
const instance = mountKindyEditor(containerRef.current, props)
|
|
132
|
-
// Cleanup khi component bị unmount
|
|
133
|
-
return () => instance.unmount()
|
|
134
|
-
}, [])
|
|
126
|
+
```ts
|
|
127
|
+
import { createMemoryDocumentAdapter } from 'kindy-editor'
|
|
135
128
|
|
|
136
|
-
|
|
137
|
-
}
|
|
129
|
+
const adapter = createMemoryDocumentAdapter()
|
|
138
130
|
```
|
|
139
131
|
|
|
140
|
-
|
|
132
|
+
`MemoryDocumentAdapter` chỉ dành cho demo và test; dữ liệu mất khi reload trang.
|
|
141
133
|
|
|
142
|
-
|
|
134
|
+
Trong repository:
|
|
143
135
|
|
|
144
|
-
```
|
|
145
|
-
|
|
136
|
+
```bash
|
|
137
|
+
npm install
|
|
138
|
+
npm run dev
|
|
139
|
+
```
|
|
146
140
|
|
|
147
|
-
|
|
141
|
+
Mở `http://localhost:9000/kindy-editor`.
|
|
148
142
|
|
|
149
|
-
|
|
150
|
-
const KindyEditor = dynamic(
|
|
151
|
-
() => import('./KindyEditorReact').then((mod) => mod.KindyEditorReact),
|
|
152
|
-
{ ssr: false }
|
|
153
|
-
)
|
|
143
|
+
## Dùng editor độc lập
|
|
154
144
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
145
|
+
```vue
|
|
146
|
+
<KindyEditor
|
|
147
|
+
ref="editor"
|
|
148
|
+
:document="{ content: initialState.content, assets: initialState.assets }"
|
|
149
|
+
:page="initialState.page"
|
|
150
|
+
/>
|
|
162
151
|
```
|
|
163
152
|
|
|
164
|
-
|
|
153
|
+
```ts
|
|
154
|
+
const state = editor.value.getState()
|
|
155
|
+
editor.value.setContent(nextContent)
|
|
156
|
+
editor.value.setReadOnly(true)
|
|
157
|
+
const printable = await editor.value.preparePrint()
|
|
158
|
+
editor.value.print()
|
|
159
|
+
```
|
|
165
160
|
|
|
166
|
-
|
|
161
|
+
Vanilla/React/Angular/Svelte có thể dùng mount helper:
|
|
167
162
|
|
|
168
|
-
|
|
163
|
+
```ts
|
|
164
|
+
import { mountKindyEditor } from 'kindy-editor'
|
|
169
165
|
|
|
170
|
-
|
|
166
|
+
const mounted = mountKindyEditor('#editor', {
|
|
167
|
+
locale: 'vi-VN',
|
|
168
|
+
document: { content: initialState.content },
|
|
169
|
+
})
|
|
171
170
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
data-color="rgba(255, 213, 79, 0.4)"
|
|
176
|
-
data-thread='{"id":"c1a2b3","text":"Cần chỉnh sửa","replies":[]}'
|
|
177
|
-
class="kindy-comment"
|
|
178
|
-
>
|
|
179
|
-
Đoạn văn bản được bình luận
|
|
180
|
-
</span>
|
|
171
|
+
mounted.instance.setContent(nextState.content)
|
|
172
|
+
mounted.instance.print()
|
|
173
|
+
mounted.unmount()
|
|
181
174
|
```
|
|
182
175
|
|
|
183
|
-
|
|
176
|
+
V2.0 chưa có native React component; wrapper dùng Vue mount helper.
|
|
184
177
|
|
|
185
|
-
|
|
178
|
+
## Dùng headless client
|
|
186
179
|
|
|
187
|
-
|
|
188
|
-
// 1. Thêm bình luận cho vùng bôi đen hiện tại
|
|
189
|
-
editorRef.value.addComment('Nội dung bình luận mới')
|
|
180
|
+
Subpath `kindy-editor/core` không tải Vue UI:
|
|
190
181
|
|
|
191
|
-
|
|
192
|
-
|
|
182
|
+
```ts
|
|
183
|
+
import {
|
|
184
|
+
createDocumentLibrary,
|
|
185
|
+
createRestDocumentAdapter,
|
|
186
|
+
} from 'kindy-editor/core'
|
|
193
187
|
|
|
194
|
-
|
|
195
|
-
|
|
188
|
+
const client = createDocumentLibrary({
|
|
189
|
+
adapter: createRestDocumentAdapter({ baseUrl: '/document-api' }),
|
|
190
|
+
autosave: { enabled: true, delay: 5000 },
|
|
191
|
+
})
|
|
196
192
|
|
|
197
|
-
|
|
198
|
-
|
|
193
|
+
const snapshot = await client.open('document-123')
|
|
194
|
+
client.updateState(snapshot.state)
|
|
195
|
+
await client.save('manual')
|
|
196
|
+
```
|
|
199
197
|
|
|
200
|
-
|
|
201
|
-
editorRef.value.focusComment(commentId)
|
|
198
|
+
`VERSION_CONFLICT` dừng autosave. SDK không tự merge hoặc ghi đè revision mới hơn:
|
|
202
199
|
|
|
203
|
-
|
|
204
|
-
|
|
200
|
+
```ts
|
|
201
|
+
client.on('save-failed', (error) => {
|
|
202
|
+
if (error.code === 'VERSION_CONFLICT') {
|
|
203
|
+
// Hiển thị lựa chọn reload hoặc lưu thành bản sao.
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
```
|
|
205
207
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
+
`kindy-editor/core` dùng được trong Node/SSR. Entry `kindy-editor` chứa Vue UI và
|
|
209
|
+
các công cụ browser như cropper/print, vì vậy chỉ import nó trong bundle phía client.
|
|
208
210
|
|
|
209
|
-
|
|
210
|
-
editorRef.value.toggleCommentSidebar(true) // true: hiện, false: ẩn
|
|
211
|
-
```
|
|
211
|
+
## Import và export DOCX
|
|
212
212
|
|
|
213
|
-
|
|
213
|
+
```ts
|
|
214
|
+
import { importDocxInWorker, exportDocx } from 'kindy-editor'
|
|
214
215
|
|
|
215
|
-
|
|
216
|
+
const imported = await importDocxInWorker(file, {
|
|
217
|
+
mode: 'best-effort',
|
|
218
|
+
profile: 'kindy-docx-v2.0',
|
|
219
|
+
})
|
|
220
|
+
if (imported.report.issues.length) {
|
|
221
|
+
// Hiển thị warning trước khi tiếp tục.
|
|
222
|
+
}
|
|
216
223
|
|
|
217
|
-
|
|
224
|
+
const output = await exportDocx(imported.state) // strict mặc định
|
|
225
|
+
```
|
|
218
226
|
|
|
219
|
-
|
|
227
|
+
UI workspace tự xử lý luồng xác nhận best-effort khi import hoặc download. API programmatic mặc định strict và ném `DOCX_UNSUPPORTED` nếu state có feature ngoài profile.
|
|
228
|
+
Mặc định là v2.0. Chọn `kindy-docx-v2.1` cho sections/header/footer hoặc
|
|
229
|
+
`kindy-docx-v2.2` cho comments/Track Changes sau khi integration đã chạy corpus tương ứng.
|
|
220
230
|
|
|
221
|
-
|
|
222
|
-
- **Đọc DB**: Truyền lại `content` vào `:options="{ document: { content: data.content } }"` ➔ Bình luận tự động hiển thị lại 100%.
|
|
231
|
+
`POST /documents/import` phải lưu blob gốc và trả `DocumentRecord.originalSource` gồm `artifactId`, `revisionId`, `format: 'original-docx'` và `fileName`. Nhờ đó `KindyDocumentLibrary.downloadDocx()` trả đúng file gốc khi revision chưa đổi. URL artifact của REST backend phải là URL tải được (thường là signed URL); Memory adapter trả trực tiếp `Blob`.
|
|
223
232
|
|
|
224
|
-
|
|
233
|
+
## Public components
|
|
225
234
|
|
|
226
|
-
|
|
235
|
+
| Export | Mục đích |
|
|
236
|
+
|---|---|
|
|
237
|
+
| `KindyDocumentLibrary` | Workspace hoàn chỉnh, kết nối adapter và điều phối state |
|
|
238
|
+
| `KindyDocumentLibraryShell` | Layout ba vùng độc lập cho UI tùy biến |
|
|
239
|
+
| `KindyDocumentExplorer` | List/search/folder/import/template độc lập |
|
|
240
|
+
| `KindyEditor` | Editor độc lập |
|
|
241
|
+
| `KindyVersionPanel` | Lịch sử version độc lập |
|
|
227
242
|
|
|
228
|
-
|
|
229
|
-
const saveToBackend = async () => {
|
|
230
|
-
const htmlContent = editorRef.value.getHTML()
|
|
231
|
-
const commentsList = editorRef.value.getComments()
|
|
243
|
+
## Events chuẩn
|
|
232
244
|
|
|
233
|
-
|
|
234
|
-
method: 'POST',
|
|
235
|
-
headers: { 'Content-Type': 'application/json' },
|
|
236
|
-
body: JSON.stringify({
|
|
237
|
-
docId: 'doc_123',
|
|
238
|
-
content: htmlContent,
|
|
239
|
-
comments: commentsList, // Danh sách mảng comment object
|
|
240
|
-
}),
|
|
241
|
-
})
|
|
242
|
-
}
|
|
243
|
-
```
|
|
245
|
+
`ready`, `opened`, `changed`, `save-started`, `saved`, `save-failed`, `created`, `imported`, `compatibility-warning`, `version-restored`, `printed`, `error`.
|
|
244
246
|
|
|
245
|
-
|
|
247
|
+
## Error codes
|
|
246
248
|
|
|
247
|
-
|
|
249
|
+
`DOCX_INVALID`, `DOCX_UNSUPPORTED`, `IMPORT_FAILED`, `EXPORT_FAILED`, `ADAPTER_ERROR`, `NETWORK_ERROR`, `VERSION_CONFLICT`, `DOCUMENT_NOT_FOUND`, `OPERATION_CANCELLED`.
|
|
248
250
|
|
|
249
|
-
|
|
250
|
-
# Cài đặt phụ thuộc
|
|
251
|
-
npm install
|
|
251
|
+
## Hiệu năng tài liệu dài
|
|
252
252
|
|
|
253
|
-
|
|
254
|
-
npm run dev
|
|
253
|
+
Import parsing đã chạy ngoài main thread. Editor vẫn dùng một ProseMirror instance liên tục; pagination cache phép đo block và workspace gom state sync. Trên production preview cục bộ (Chrome 151, Apple M2/24GB, 40 mẫu), corpus 100 trang text mở khoảng 0,49 giây với typing p95 21,5ms; mixed mở khoảng 0,85 giây với typing p95 36,5ms. Pagination cached của hai corpus lần lượt khoảng 2,8ms và 3,7ms. Đây là regression baseline, không phải SLA cho mọi thiết bị. Corpus mixed 200 trang vẫn chỉ được xem là stress limit. Xem [Performance guide](./docs/performance.md) để biết phương pháp đo và giới hạn.
|
|
255
254
|
|
|
256
|
-
|
|
257
|
-
npm run build
|
|
255
|
+
## Tài liệu
|
|
258
256
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
257
|
+
- [Bắt đầu tích hợp](./docs/getting-started.md)
|
|
258
|
+
- [UI engine, theme, responsive và slots](./docs/ui-engine.md)
|
|
259
|
+
- [API reference](./docs/api-reference.md)
|
|
260
|
+
- [Backend, security và adapter](./GUIDE.md)
|
|
261
|
+
- [DOCX capability matrix](./CAPABILITIES.md)
|
|
262
|
+
- [Performance và tài liệu 100 trang](./docs/performance.md)
|
|
263
|
+
- [Migration v1 → v2](./MIGRATION.md)
|
|
264
|
+
- [REST OpenAPI](./openapi/document-api.yaml)
|
|
262
265
|
|
|
263
|
-
|
|
266
|
+
## Phát triển và kiểm thử
|
|
264
267
|
|
|
265
|
-
|
|
268
|
+
```bash
|
|
269
|
+
npm test
|
|
270
|
+
npm run test:e2e
|
|
271
|
+
npm run test:package
|
|
272
|
+
npm run test:libreoffice # cần có soffice; CI tự cài LibreOffice
|
|
273
|
+
npm run typecheck
|
|
274
|
+
npm run lint:check
|
|
275
|
+
npm run build
|
|
276
|
+
```
|
|
266
277
|
|
|
267
|
-
|
|
268
|
-
- Dựa trên mã nguồn gốc [Umo Editor](https://github.com/umodoc/editor) bởi đội ngũ umodoc.
|
|
269
|
-
- Bản quyền thuộc về **Kindy** ([GitHub](https://github.com/hotienky)).
|
|
278
|
+
License: MIT.
|