kindy-editor 1.0.0 → 1.0.1
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 +165 -101
- package/dist/kindy-editor.cjs +13 -13
- package/dist/kindy-editor.css +1 -1
- package/dist/kindy-editor.iife.js +14 -14
- package/dist/kindy-editor.js +188 -171
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,10 +10,10 @@ Trình biên tập tài liệu chuẩn Office (Word-like Document Editor) hiện
|
|
|
10
10
|
## ✨ Tính năng nổi bật
|
|
11
11
|
|
|
12
12
|
- 📄 **Phân trang dạng Word (Page-based Pagination)**: Hỗ trợ ngắt trang, căn lề, khổ giấy (A4, A3, Letter, Legal...) chân trang & đầu trang (Header/Footer).
|
|
13
|
-
- 💬 **Hệ thống Bình luận (Word-style Comments)**: Bôi đen văn bản để gắn bình luận, phản hồi (Reply), hoàn thành (Resolve), cuộn mượt và highlight từng người dùng.
|
|
13
|
+
- 💬 **Hệ thống Bình luận (Word-style Comments)**: Bôi đen văn bản để gắn bình luận, phản hồi (Reply), hoàn thành (Resolve), cuộn mượt và highlight từng người dùng theo tọa độ Y.
|
|
14
14
|
- 🇻🇳 **Tiếng Việt 100%**: Chuẩn hóa toàn bộ nhãn giao diện, bộ cỡ chữ tiêu chuẩn (`pt`), từ điển ký hiệu và phông chữ mượt mà.
|
|
15
15
|
- 🎨 **Giao diện hiện đại (Ribbon & Classic)**: Hỗ trợ 2 chế độ thanh công cụ dạng Ribbon (như MS Word) hoặc Classic, chế độ tối (Dark mode).
|
|
16
|
-
-
|
|
16
|
+
- 🌐 **Đa nền tảng**: Tích hợp dễ dàng vào **Vue 3**, **React.js**, **Next.js**, **Nuxt 3**, **Angular**, **Svelte**, hoặc **Vanilla JS (CDN)**.
|
|
17
17
|
- 🗄️ **Lưu trữ linh hoạt**: Tự động mã hóa nhúng bình luận vào HTML/JSON hoặc tách riêng lưu vào cơ sở dữ liệu (Database).
|
|
18
18
|
|
|
19
19
|
---
|
|
@@ -30,11 +30,11 @@ yarn add kindy-editor
|
|
|
30
30
|
|
|
31
31
|
---
|
|
32
32
|
|
|
33
|
-
## 🚀 Hướng dẫn tích hợp
|
|
33
|
+
## 🚀 Hướng dẫn tích hợp (Integration Guides)
|
|
34
34
|
|
|
35
|
-
Thư viện `kindy-editor` hỗ trợ
|
|
35
|
+
Thư viện `kindy-editor` hỗ trợ đa dạng môi trường dự án:
|
|
36
36
|
|
|
37
|
-
###
|
|
37
|
+
### 1. Vue 3 (Vite / Webpack)
|
|
38
38
|
|
|
39
39
|
```vue
|
|
40
40
|
<template>
|
|
@@ -55,11 +55,14 @@ const editorOptions = ref({
|
|
|
55
55
|
title: 'Tài liệu mới',
|
|
56
56
|
content: '<h1>Nội dung ban đầu</h1>',
|
|
57
57
|
},
|
|
58
|
+
async onSave(content) {
|
|
59
|
+
console.log('Đã lưu:', content)
|
|
60
|
+
},
|
|
58
61
|
})
|
|
59
62
|
</script>
|
|
60
63
|
```
|
|
61
64
|
|
|
62
|
-
###
|
|
65
|
+
### 2. Vue 3 Global Plugin (`main.js`)
|
|
63
66
|
|
|
64
67
|
```javascript
|
|
65
68
|
import { createApp } from 'vue'
|
|
@@ -78,44 +81,9 @@ app.use(useKindyEditor, {
|
|
|
78
81
|
app.mount('#app')
|
|
79
82
|
```
|
|
80
83
|
|
|
81
|
-
###
|
|
82
|
-
|
|
83
|
-
```javascript
|
|
84
|
-
// Nuxt 3 Plugin hoặc Client-only component
|
|
85
|
-
import { KindyEditor } from 'kindy-editor' // Tự động resolve qua package.json "require"/"import"
|
|
86
|
-
import 'kindy-editor/style'
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### Cách 4: Tích hợp qua CDN / Direct Script Tag (IIFE Bundle)
|
|
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`)
|
|
84
|
+
### 3. React.js (SPA)
|
|
115
85
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
#### A. Trong React.js (Component Wrapper):
|
|
86
|
+
Dùng hàm helper `mountKindyEditor`:
|
|
119
87
|
|
|
120
88
|
```tsx
|
|
121
89
|
import React, { useEffect, useRef } from 'react'
|
|
@@ -127,9 +95,7 @@ export function KindyEditorReact(props) {
|
|
|
127
95
|
|
|
128
96
|
useEffect(() => {
|
|
129
97
|
if (!containerRef.current) return
|
|
130
|
-
// Gắn Editor vào DOM container
|
|
131
98
|
const instance = mountKindyEditor(containerRef.current, props)
|
|
132
|
-
// Cleanup khi component bị unmount
|
|
133
99
|
return () => instance.unmount()
|
|
134
100
|
}, [])
|
|
135
101
|
|
|
@@ -137,16 +103,15 @@ export function KindyEditorReact(props) {
|
|
|
137
103
|
}
|
|
138
104
|
```
|
|
139
105
|
|
|
140
|
-
|
|
106
|
+
### 4. Next.js (App Router / Pages Router)
|
|
141
107
|
|
|
142
|
-
|
|
108
|
+
Vì Rich Text Editor thao tác với DOM trình duyệt, hãy load Client Component bằng `next/dynamic` (`ssr: false`):
|
|
143
109
|
|
|
144
110
|
```tsx
|
|
145
111
|
'use client'
|
|
146
112
|
|
|
147
113
|
import dynamic from 'next/dynamic'
|
|
148
114
|
|
|
149
|
-
// 1. Tắt SSR cho Editor component
|
|
150
115
|
const KindyEditor = dynamic(
|
|
151
116
|
() => import('./KindyEditorReact').then((mod) => mod.KindyEditorReact),
|
|
152
117
|
{ ssr: false }
|
|
@@ -161,86 +126,185 @@ export default function DocumentPage() {
|
|
|
161
126
|
}
|
|
162
127
|
```
|
|
163
128
|
|
|
164
|
-
|
|
129
|
+
### 5. Angular Component
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { Component, ElementRef, ViewChild, AfterViewInit, OnDestroy, Input } from '@angular/core';
|
|
133
|
+
import { mountKindyEditor } from 'kindy-editor';
|
|
134
|
+
import 'kindy-editor/style';
|
|
135
|
+
|
|
136
|
+
@Component({
|
|
137
|
+
selector: 'app-kindy-editor',
|
|
138
|
+
template: `<div #editorContainer style="height: 100vh; width: 100%;"></div>`
|
|
139
|
+
})
|
|
140
|
+
export class KindyEditorComponent implements AfterViewInit, OnDestroy {
|
|
141
|
+
@ViewChild('editorContainer') editorContainer!: ElementRef;
|
|
142
|
+
@Input() locale: string = 'vi-VN';
|
|
143
|
+
private instance: any;
|
|
144
|
+
|
|
145
|
+
ngAfterViewInit() {
|
|
146
|
+
this.instance = mountKindyEditor(this.editorContainer.nativeElement, {
|
|
147
|
+
locale: this.locale,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
ngOnDestroy() {
|
|
152
|
+
if (this.instance) {
|
|
153
|
+
this.instance.unmount();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
165
158
|
|
|
166
|
-
|
|
159
|
+
### 6. Svelte / SvelteKit Component
|
|
167
160
|
|
|
168
|
-
|
|
161
|
+
```svelte
|
|
162
|
+
<script>
|
|
163
|
+
import { onMount, onDestroy } from 'svelte';
|
|
164
|
+
import { mountKindyEditor } from 'kindy-editor';
|
|
165
|
+
import 'kindy-editor/style';
|
|
169
166
|
|
|
170
|
-
|
|
167
|
+
export let locale = 'vi-VN';
|
|
168
|
+
let container;
|
|
169
|
+
let instance;
|
|
170
|
+
|
|
171
|
+
onMount(() => {
|
|
172
|
+
instance = mountKindyEditor(container, { locale });
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
onDestroy(() => {
|
|
176
|
+
if (instance) instance.unmount();
|
|
177
|
+
});
|
|
178
|
+
</script>
|
|
179
|
+
|
|
180
|
+
<div bind:this={container} style="height: 100vh; width: 100%;"></div>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 7. SolidJS Component
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
import { onMount, onCleanup } from 'solid-js';
|
|
187
|
+
import { mountKindyEditor } from 'kindy-editor';
|
|
188
|
+
import 'kindy-editor/style';
|
|
189
|
+
|
|
190
|
+
export function KindyEditorSolid(props) {
|
|
191
|
+
let containerRef;
|
|
192
|
+
let instance;
|
|
193
|
+
|
|
194
|
+
onMount(() => {
|
|
195
|
+
instance = mountKindyEditor(containerRef, {
|
|
196
|
+
locale: 'vi-VN',
|
|
197
|
+
...props,
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
onCleanup(() => {
|
|
202
|
+
if (instance) instance.unmount();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
return <div ref={containerRef} style={{ height: '100vh', width: '100%' }} />;
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 8. Vanilla JS / CDN (Direct Script Tag)
|
|
171
210
|
|
|
172
211
|
```html
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
>
|
|
179
|
-
|
|
180
|
-
|
|
212
|
+
<!DOCTYPE html>
|
|
213
|
+
<html>
|
|
214
|
+
<head>
|
|
215
|
+
<link rel="stylesheet" href="https://unpkg.com/kindy-editor/dist/kindy-editor.css">
|
|
216
|
+
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
|
217
|
+
<script src="https://unpkg.com/kindy-editor/dist/kindy-editor.iife.js"></script>
|
|
218
|
+
</head>
|
|
219
|
+
<body>
|
|
220
|
+
<div id="editor-container" style="height: 100vh;"></div>
|
|
221
|
+
|
|
222
|
+
<script>
|
|
223
|
+
const { mountKindyEditor } = KindyEditor;
|
|
224
|
+
mountKindyEditor('#editor-container', {
|
|
225
|
+
locale: 'vi-VN',
|
|
226
|
+
});
|
|
227
|
+
</script>
|
|
228
|
+
</body>
|
|
229
|
+
</html>
|
|
181
230
|
```
|
|
182
231
|
|
|
183
|
-
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## ⚙️ Cấu hình (Props & Options API)
|
|
235
|
+
|
|
236
|
+
| Thuộc tính | Kiểu dữ liệu | Mặc định | Mô tả |
|
|
237
|
+
|---|---|---|---|
|
|
238
|
+
| `locale` | `string` | `'vi-VN'` | Ngôn ngữ giao diện (`vi-VN`, `en-US`, `zh-CN`) |
|
|
239
|
+
| `theme` | `string` | `'light'` | Chế độ giao diện (`light`, `dark`, `auto`) |
|
|
240
|
+
| `skin` | `string` | `'default'` | Phong cách thanh công cụ (`default`, `modern`) |
|
|
241
|
+
| `height` | `string` | `'100%'` | Chiều cao khung biên tập |
|
|
242
|
+
| `toolbar.mode` | `string` | `'ribbon'` | Chế độ thanh công cụ (`ribbon` hoặc `classic`) |
|
|
243
|
+
| `document.title` | `string` | `''` | Tiêu đề tài liệu mặc định |
|
|
244
|
+
| `document.content` | `string` \| `object` | `''` | Nội dung HTML hoặc JSON ban đầu |
|
|
245
|
+
| `document.readOnly` | `boolean` | `false` | Chế độ chỉ đọc (Chỉ xem) |
|
|
246
|
+
| `document.autoSave` | `object` | `{ enabled: false }` | Tự động lưu (`enabled: true, interval: 30000`) |
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## 🛠️ Danh sách Phương thức (Methods API)
|
|
184
251
|
|
|
185
|
-
Thông qua `ref` của component
|
|
252
|
+
Thông qua `ref` của component (hoặc `mountKindyEditor` instance):
|
|
186
253
|
|
|
187
254
|
```javascript
|
|
188
|
-
// 1.
|
|
189
|
-
editorRef.value.
|
|
255
|
+
// 1. Lấy nội dung HTML
|
|
256
|
+
const html = editorRef.value.getContent('html')
|
|
190
257
|
|
|
191
|
-
// 2.
|
|
192
|
-
editorRef.value.
|
|
258
|
+
// 2. Lấy nội dung JSON
|
|
259
|
+
const json = editorRef.value.getContent('json')
|
|
193
260
|
|
|
194
|
-
// 3.
|
|
195
|
-
editorRef.value.
|
|
261
|
+
// 3. Đặt nội dung mới
|
|
262
|
+
editorRef.value.setContent('<h1>Nội dung mới</h1>')
|
|
196
263
|
|
|
197
|
-
// 4.
|
|
198
|
-
editorRef.value.
|
|
264
|
+
// 4. Chèn nội dung tại vị trí con trỏ
|
|
265
|
+
editorRef.value.insertContent('<p>Đoạn văn mới</p>')
|
|
199
266
|
|
|
200
|
-
// 5.
|
|
201
|
-
editorRef.value.
|
|
267
|
+
// 5. Xuất tài liệu PDF
|
|
268
|
+
await editorRef.value.exportPdf('tai-lieu.pdf')
|
|
202
269
|
|
|
203
|
-
// 6.
|
|
204
|
-
|
|
270
|
+
// 6. Xuất trang thành hình ảnh
|
|
271
|
+
await editorRef.value.exportImage('png', 'tai-lieu.png')
|
|
205
272
|
|
|
206
|
-
// 7.
|
|
207
|
-
|
|
273
|
+
// 7. In tài liệu
|
|
274
|
+
editorRef.value.print()
|
|
208
275
|
|
|
209
|
-
// 8.
|
|
210
|
-
editorRef.value.
|
|
276
|
+
// 8. Đổi giao diện tối / sáng
|
|
277
|
+
editorRef.value.setTheme('dark')
|
|
211
278
|
```
|
|
212
279
|
|
|
213
280
|
---
|
|
214
281
|
|
|
215
|
-
##
|
|
282
|
+
## 💬 Hệ thống Bình luận (Word-style Comments)
|
|
216
283
|
|
|
217
|
-
###
|
|
284
|
+
### Cơ chế lưu trữ:
|
|
285
|
+
Bình luận được lưu trực tiếp dưới dạng thẻ `span` chứa thuộc tính `data-comment` và `data-thread` trong tài liệu HTML/JSON:
|
|
218
286
|
|
|
219
|
-
|
|
287
|
+
```html
|
|
288
|
+
<span
|
|
289
|
+
data-comment="c1a2b3"
|
|
290
|
+
data-color="rgba(255, 213, 79, 0.4)"
|
|
291
|
+
data-thread='{"id":"c1a2b3","text":"Cần chỉnh sửa","replies":[]}'
|
|
292
|
+
class="kindy-comment"
|
|
293
|
+
>
|
|
294
|
+
Đoạn văn bản được bình luận
|
|
295
|
+
</span>
|
|
296
|
+
```
|
|
220
297
|
|
|
221
|
-
|
|
222
|
-
-
|
|
298
|
+
### Lưu trữ Cơ sở dữ liệu (Database Integration):
|
|
299
|
+
- **Cách 1 (Khuyên dùng)**: Lưu toàn bộ chuỗi HTML chứa thẻ `<span data-comment>` vào 1 cột `content TEXT` trong Database. Khi mở lại, bình luận tự động khôi phục 100%.
|
|
300
|
+
- **Cách 2**: Lưu riêng danh sách mảng object comment qua API `editorRef.value.getComments()`.
|
|
223
301
|
|
|
224
|
-
|
|
302
|
+
---
|
|
225
303
|
|
|
226
|
-
|
|
304
|
+
## 💻 Dự án Mẫu (Examples)
|
|
227
305
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const htmlContent = editorRef.value.getHTML()
|
|
231
|
-
const commentsList = editorRef.value.getComments()
|
|
232
|
-
|
|
233
|
-
await fetch('/api/documents/save', {
|
|
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
|
-
```
|
|
306
|
+
Thư mục `examples/` chứa các ứng dụng mẫu đầy đủ:
|
|
307
|
+
- ⚛️ **[examples/react-demo](./examples/react-demo)**: React.js + IndexedDB Database Persistence.
|
|
244
308
|
|
|
245
309
|
---
|
|
246
310
|
|