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 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
- - 🔠 **Phông chữ hệ thống & Web Fonts chuẩn**: Tích hợp phông hệ thống mượt nét (`San Francisco`, `Segoe UI`, `Roboto`, `Arial`, `Times New Roman`...) không bị vỡ hay lệch dấu Tiếng Việt.
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 cho dự án khác (Integration Methods)
33
+ ## 🚀 Hướng dẫn tích hợp (Integration Guides)
34
34
 
35
- Thư viện `kindy-editor` hỗ trợ 4 cách tích hợp linh hoạt cho mọi môi trường dự án:
35
+ Thư viện `kindy-editor` hỗ trợ đa dạng môi trường dự án:
36
36
 
37
- ### Cách 1: Import Component (Vite / Webpack / Vue 3 SPA)
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
- ### Cách 2: Đăng ký Global Vue Plugin (`main.js`)
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
- ### Cách 3: Tích hợp SSR / Nuxt 3 (CommonJS Bundle)
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
- 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):
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
- #### B. Trong Next.js (App Router / Pages Router):
106
+ ### 4. Next.js (App Router / Pages Router)
141
107
 
142
- Do trình biên tập tài liệu Rich Text phụ thuộc vào DOM trình duyệt (`window`, `document`), trong Next.js bạn cần nạp component dưới dạng Client Component với `next/dynamic` (`ssr: false`):
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
- ## 💬 Hướng dẫn & API Hệ thống Bình luận (Comments)
159
+ ### 6. Svelte / SvelteKit Component
167
160
 
168
- ### 1. Cơ chế hoạt động
161
+ ```svelte
162
+ <script>
163
+ import { onMount, onDestroy } from 'svelte';
164
+ import { mountKindyEditor } from 'kindy-editor';
165
+ import 'kindy-editor/style';
169
166
 
170
- 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:
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
- <span
174
- data-comment="c1a2b3"
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>
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
- ### 2. Các API tương tác với Bình luận
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 `<KindyEditor ref="editorRef" />`:
252
+ Thông qua `ref` của component (hoặc `mountKindyEditor` instance):
186
253
 
187
254
  ```javascript
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')
255
+ // 1. Lấy nội dung HTML
256
+ const html = editorRef.value.getContent('html')
190
257
 
191
- // 2. Thêm phản hồi (Reply) cho 1 bình luận
192
- editorRef.value.addReply(commentId, 'Nội dung phản hồi')
258
+ // 2. Lấy nội dung JSON
259
+ const json = editorRef.value.getContent('json')
193
260
 
194
- // 3. Đánh dấu Đã giải quyết (Resolve) hoặc Mở lại
195
- editorRef.value.resolveComment(commentId, true) // true: Đã giải quyết, false: Mở lại
261
+ // 3. Đặt nội dung mới
262
+ editorRef.value.setContent('<h1>Nội dung mới</h1>')
196
263
 
197
- // 4. Xóa bình luận
198
- editorRef.value.removeComment(commentId)
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. Tập trung con trỏ & cuộn màn hình tới vị trí bình luận
201
- editorRef.value.focusComment(commentId)
267
+ // 5. Xuất tài liệu PDF
268
+ await editorRef.value.exportPdf('tai-lieu.pdf')
202
269
 
203
- // 6. Lấy danh sách toàn bộ bình luận dưới dạng mảng Object
204
- const comments = editorRef.value.getComments()
270
+ // 6. Xuất trang thành hình ảnh
271
+ await editorRef.value.exportImage('png', 'tai-lieu.png')
205
272
 
206
- // 7. Đếm tổng số bình luận
207
- const total = editorRef.value.getCommentCount()
273
+ // 7. In tài liệu
274
+ editorRef.value.print()
208
275
 
209
- // 8. Ẩn / Hiện thanh Sidebar bình luận
210
- editorRef.value.toggleCommentSidebar(true) // true: hiện, false: ẩn
276
+ // 8. Đổi giao diện tối / sáng
277
+ editorRef.value.setTheme('dark')
211
278
  ```
212
279
 
213
280
  ---
214
281
 
215
- ## 🗄️ Hướng dẫn tích hợp Cơ sở dữ liệu (Database Integration)
282
+ ## 💬 Hệ thống Bình luận (Word-style Comments)
216
283
 
217
- ### Cách 1: Gộp chung vào Nội dung (Khuyên dùng - Đơn giản nhất)
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
- Khi bạn lưu `content` thu được từ `onSave` hoặc `editorRef.value.getHTML()`, toàn bộ bình luận đã nằm sẵn bên trong thẻ `<span data-comment>`:
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
- - **Lưu DB**: Lưu chuỗi `content` vào cột `content TEXT` trong bảng `documents`.
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%.
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
- ### Cách 2: Tách Bình luận ra Bảng Database riêng
302
+ ---
225
303
 
226
- Nếu bạn muốn quản lý bình luận riêng trong bảng `comments` (để thông báo, phân quyền):
304
+ ## 💻 Dự án Mẫu (Examples)
227
305
 
228
- ```javascript
229
- const saveToBackend = async () => {
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