tiptap-notion-editor 0.6.0 → 0.6.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 CHANGED
@@ -1,25 +1,25 @@
1
1
  # tiptap-notion-editor
2
2
 
3
- Editor rich text kiểu Notion, dựng trên Tiptap 3.
3
+ A Notion-style rich text editor built on Tiptap 3.
4
4
 
5
- Slash command, bubble menu, kéo-thả block, bảng handle, upload ảnh, công thức
6
- toán (KaTeX), emoji, `@mention` mục lục lề.
5
+ Slash commands, bubble menus, block drag-and-drop, tables with handles, image
6
+ upload, math (KaTeX), emoji, `@mention` and a table of contents in the margin.
7
7
 
8
- ## Cài đặt
8
+ ## Install
9
9
 
10
10
  ```bash
11
11
  pnpm add tiptap-notion-editor
12
12
  ```
13
13
 
14
- Peer dependency phải sẵn trong app chủ đây chủ ý, không phải thiếu sót:
15
- gói kèm `@tiptap/*` sẽ tạo instance ProseMirror thứ hai và schema từ hai instance
16
- khác nhau không nhận nhau.
14
+ Peer dependencies must already exist in the host app — that is deliberate, not an
15
+ oversight: bundling `@tiptap/*` would create a second ProseMirror instance, and
16
+ schemas from two different instances do not recognise each other.
17
17
 
18
18
  ```bash
19
19
  pnpm add @tiptap/core @tiptap/pm @tiptap/react react react-dom
20
20
  ```
21
21
 
22
- ## Dùng
22
+ ## Usage
23
23
 
24
24
  ```tsx
25
25
  import { NotionEditor } from "tiptap-notion-editor";
@@ -46,79 +46,93 @@ export function PageEditor({ page }) {
46
46
 
47
47
  ## Tailwind
48
48
 
49
- Component dùng class tiện ích của Tailwind, các class đó do **app chủ** biên
50
- dịch chứ không nằm sẵn trong `style.css`. Thêm dòng sau vào file CSS entry của
51
- app, ngay sau `@import "tailwindcss"`:
49
+ The components use Tailwind utility classes, and those classes are compiled by
50
+ the **host app** they are not baked into `style.css`. Add this line to the
51
+ app's CSS entry file, right after `@import "tailwindcss"`:
52
52
 
53
53
  ```css
54
54
  @source "../node_modules/tiptap-notion-editor/lib/*.js";
55
55
  ```
56
56
 
57
- Không dòng này editor vẫn chạy nhưng gần như không có style.
57
+ Without it the editor still runs, but with almost no styling.
58
58
 
59
- Editor cũng đọc các biến theme của app (`--color-background`, `--color-border`,
60
- `--color-primary`…). Thiếu biến nào thì token trong `lib/styles/tokens.css` tự
61
- rơi về giá trị dự phòng.
59
+ The editor also reads the app's theme variables (`--color-background`,
60
+ `--color-border`, `--color-primary`…). Any variable you leave undefined falls
61
+ back to the default in `lib/styles/tokens.css`.
62
62
 
63
- ## Ngôn ngữ
63
+ ### Content width
64
64
 
65
- Mặc định **tiếng Việt**. Đổi bằng prop `locale`:
65
+ The content column stretches to fill whatever space it is given, reserving 96px
66
+ on each side for the drag handles and the table of contents. To pin it to a
67
+ fixed reading width instead, override the grid track in your own CSS:
68
+
69
+ ```css
70
+ .notion-like-editor-layout {
71
+ --content-width: minmax(auto, 708px);
72
+ }
73
+ ```
74
+
75
+ ## Language
76
+
77
+ The default is **Vietnamese**. Change it with the `locale` prop:
66
78
 
67
79
  ```tsx
68
80
  <NotionEditor locale="en" />
69
81
  ```
70
82
 
71
- Từ điển gồm hai bản `vi` `en`, xuất ra ngoài để app chủ ghi đè từng chuỗi
72
- không phải fork package:
83
+ Both the `vi` and `en` dictionaries are exported, so the host app can override
84
+ individual strings without forking the package:
73
85
 
74
86
  ```tsx
75
87
  import { NotionEditor, getMessages, type Messages } from "tiptap-notion-editor";
76
88
 
77
- const t: Messages = getMessages("vi");
89
+ const t: Messages = getMessages("en");
78
90
  const custom: Messages = {
79
91
  ...t,
80
- slash: { ...t.slash, groups: { ...t.slash.groups, insert: "Thêm vào" } },
92
+ slash: { ...t.slash, groups: { ...t.slash.groups, insert: "Add" } },
81
93
  };
82
94
  ```
83
95
 
84
- Kiểu `Messages` suy ra từ bản tiếng Việt, nên thiếu một khoá ở bản dịch khác là
85
- lỗi biên dịch chứ không phải chuỗi rỗng lúc chạy.
96
+ The `Messages` type is derived from the Vietnamese dictionary, so a missing key
97
+ in another translation is a compile error rather than an empty string at runtime.
86
98
 
87
99
  ## Props
88
100
 
89
- | Prop | Kiểu | Mặc định | Ý nghĩa |
101
+ | Prop | Type | Default | Meaning |
90
102
  | --- | --- | --- | --- |
91
- | `content` | `JSONContent \| string` | — | Nội dung khởi tạo. **Không kiểm soát** — xem ghi chú bên dưới. |
92
- | `onChange` | `(json, editor) => void` | — | Gọi mỗi lần document đổi. |
93
- | `onReady` | `(editor) => void` | — | Gọi một lần khi editor khởi tạo xong. |
94
- | `editable` | `boolean` | `true` | `false` để chỉ đọc; đổi được lúc chạy. |
95
- | `uploadImage` | `(file, onProgress?, signal?) => Promise<string>` | — | Đẩy ảnh lên server, trả URL. |
96
- | `maxImageSize` | `number` | `1048576` | Giới hạn dung lượng ảnh, tính bằng byte. |
97
- | `uploadAttachment` | `(file, onProgress?, signal?) => Promise<string \| { src?, fileId? }>` | — | Tải tệp đính kèm lên. Trả `src` nếu storage cấp URL cố định, trả `fileId` nếu storage riêng chỉ phát URL ngắn hạn. |
98
- | `maxAttachmentSize` | `number` | `26214400` | Giới hạn dung lượng tệp đính kèm, tính bằng byte. |
99
- | `onOpenAttachment` | `(attachment) => void` | — | Mở tệp chỉ `fileId`. Tệp `src` dùng thẻ `<a>` thật nên không gọi tới. |
100
- | `mentionSource` | `(query) => MentionItem[] \| Promise<MentionItem[]>` | — | Không truyền thì tắt hẳn `@mention`. |
101
- | `locale` | `"vi" \| "en"` | `"vi"` | Ngôn ngữ của nhãn trong editor. |
102
- | `placeholder` | `string \| (({ editor, node }) => string)` | — | Chữ mờ cho document rỗng. Heading rỗng luôn hiện `Heading N`; node rỗng khác không hiện gì. Truyền hàm để tự quyết hoàn toàn. |
103
- | `showToc` | `boolean` | `true` | Thanh mục lục lề phải. |
104
- | `tocVariant` | `"content" \| "line"` | `"line"` | Kiểu hiển thị mục lục. |
105
- | `onReplaceImage` | `() => void` | — | Xử nút "Replace" trên bubble menu của ảnh. |
106
- | `extensions` | `AnyExtension[]` | `[]` | Extension Tiptap bổ sung. |
107
- | `className` / `editorClassName` | `string` | — | Class cho khung ngoài / vùng soạn thảo. |
108
- | `ref` | `Ref<NotionEditorHandle>` | — | Truy cập `editor`, `getJSON`, `getHTML`, `setContent`, `focus`. |
109
-
110
- ### Tệp đính kèm
111
-
112
- Package không biết về storage. gọi `uploadAttachment`, lưu lại thứ hàm đó
113
- trả về, khi người dùng bấm tải thì hoặc dùng `src` trực tiếp, hoặc gọi
114
- `onOpenAttachment` để app chủ tự lấy nội dung.
103
+ | `content` | `JSONContent \| string` | — | Initial content. **Uncontrolled** — see the note below. |
104
+ | `onChange` | `(json, editor) => void` | — | Called every time the document changes. |
105
+ | `onReady` | `(editor) => void` | — | Called once, when the editor has finished initialising. |
106
+ | `editable` | `boolean` | `true` | `false` for read-only; can be changed at runtime. |
107
+ | `uploadImage` | `(file, onProgress?, signal?) => Promise<string>` | — | Uploads an image to your server, returns its URL. |
108
+ | `maxImageSize` | `number` | `1048576` | Image size limit, in bytes. |
109
+ | `uploadAttachment` | `(file, onProgress?, signal?) => Promise<string \| { src?, fileId? }>` | — | Uploads an attachment. Return `src` if your storage hands out stable URLs, or `fileId` if it is private and only issues short-lived ones. |
110
+ | `maxAttachmentSize` | `number` | `26214400` | Attachment size limit, in bytes. |
111
+ | `onOpenAttachment` | `(attachment) => void` | — | Opens an attachment that only has a `fileId`. Attachments with a `src` use a real `<a>` tag and never reach this. |
112
+ | `mentionSource` | `(query) => MentionItem[] \| Promise<MentionItem[]>` | — | Leave it out to disable `@mention` entirely. |
113
+ | `locale` | `"vi" \| "en"` | `"vi"` | Language of the editor's own labels. |
114
+ | `placeholder` | `string \| (({ editor, node }) => string)` | — | Placeholder for an empty document. Empty headings always show `Heading N`; other empty nodes show nothing. Pass a function to decide entirely on your own. |
115
+ | `showToc` | `boolean` | `true` | Table of contents in the right margin. |
116
+ | `tocVariant` | `"content" \| "line"` | `"line"` | How the table of contents is rendered. |
117
+ | `onReplaceImage` | `() => void` | — | Handles the "Replace" button in the image bubble menu. |
118
+ | `extensions` | `AnyExtension[]` | `[]` | Extra Tiptap extensions. |
119
+ | `className` / `editorClassName` | `string` | — | Classes for the outer frame / the editable area. |
120
+ | `ref` | `Ref<NotionEditorHandle>` | — | Access to `editor`, `getJSON`, `getHTML`, `setContent`, `focus`. |
121
+
122
+ ### Attachments
123
+
124
+ The package knows nothing about your storage. It calls `uploadAttachment`, keeps
125
+ whatever that function returns, and when the user clicks to download it either
126
+ uses `src` directly or calls `onOpenAttachment` so the host app can fetch the
127
+ content itself.
115
128
 
116
129
  ```tsx
117
130
  <NotionEditor
118
131
  uploadAttachment={async (file) => {
119
132
  const { fileId } = await uploadToPrivateStorage(file);
120
- // Storage riêng chỉ phát URL ngắn hạn, nhúng vào nội dung là bảo đảm
121
- // hỏng sau vài phút nên lưu id đổi lấy URL lúc cần.
133
+ // Private storage only issues short-lived URLs; embedding one in the
134
+ // content guarantees it breaks a few minutes later so store the id and
135
+ // exchange it for a URL when it is actually needed.
122
136
  return { fileId };
123
137
  }}
124
138
  onOpenAttachment={async ({ fileId, name }) => {
@@ -128,23 +142,25 @@ trả về, và khi người dùng bấm tải thì hoặc dùng `src` trực ti
128
142
  />
129
143
  ```
130
144
 
131
- `collectFileUrls(doc)` trả về mọi URL tệp mà một document đang tham chiếu, đã
132
- loại ảnh base64 dùng khi app chủ cần dọn storage sau khi xoá trang.
145
+ `collectFileUrls(doc)` returns every file URL a document references, base64
146
+ images excludeduseful when the host app needs to clean up storage after
147
+ deleting a page.
133
148
 
134
- ### `content` không phải prop kiểm soát
149
+ ### `content` is not a controlled prop
135
150
 
136
- Đổi giá trị `content` sẽ **không** nạp lại document. Muốn chuyển sang document
137
- khác thì remount bằng `key` như dụ trên cách này rẻ hơn việc so sánh cây
138
- node, tránh con trỏ nhảy về đầu bài mỗi khi state cha render lại giữa lúc
139
- đang gõ. Nếu cần ghi đè có chủ đích thì dùng `ref.current.setContent(...)`.
151
+ Changing `content` does **not** reload the document. To switch to a different
152
+ document, remount with `key` as shown abovethat is cheaper than diffing the
153
+ node tree, and it avoids the cursor jumping back to the top every time a parent
154
+ re-renders mid-typing. For a deliberate overwrite, use
155
+ `ref.current.setContent(...)`.
140
156
 
141
- ### Upload ảnh
157
+ ### Image upload
142
158
 
143
- Không truyền `uploadImage` thì khối upload báo lỗi khi người dùng chọn file, còn
144
- ảnh kéo-thả rơi về nhúng base64. Base64 làm document phình rất nhanh, nên đừng
145
- để mặc định này đi vào nội dung lưu xuống backend.
159
+ Without `uploadImage`, the upload block reports an error when the user picks a
160
+ file, and dragged-in images fall back to inline base64. Base64 bloats a document
161
+ very quickly, so do not let that fallback reach the content you persist.
146
162
 
147
- ## Phát triển
163
+ ## Development
148
164
 
149
165
  ```bash
150
166
  pnpm install
@@ -152,21 +168,31 @@ pnpm build # -> lib/
152
168
  pnpm type-check
153
169
  ```
154
170
 
155
- ### Thử với app chủ trước khi publish
171
+ ### Trying it in a host app before publishing
156
172
 
157
- Dùng tarball, **không** dùng `link:` hay `pnpm link`:
173
+ Use a tarball, **not** `link:` or `pnpm link`:
158
174
 
159
175
  ```bash
160
- pnpm build && pnpm pack # trong repo này
161
- pnpm add file:../notion-editor/aicademy-notion-editor-0.1.0.tgz # trong app chủ
176
+ pnpm build && pnpm pack # in this repo
177
+ pnpm add file:../notion-editor/tiptap-notion-editor-0.6.0.tgz # in the host app
162
178
  ```
163
179
 
164
- `link:` trỏ ra ngoài cây `node_modules` của app, nên package vẫn nạp `react` và
165
- `prosemirror-*` từ `node_modules` của chính nó. Hai bản ProseMirror giữ hai bộ
166
- đếm `PluginKey` riêng, cùng sinh ra key `plugin$`, ProseMirror ném
167
- `RangeError: Adding different instances of a keyed plugin` ngay lúc dựng editor
168
- view. `resolve.dedupe` của Vite không cứu được, vì Vitest coi package nằm ngoài
169
- thư mục gốc external rồi để Node tự resolve.
180
+ `link:` points outside the app's `node_modules` tree, so the package still loads
181
+ `react` and `prosemirror-*` from its own. Two ProseMirror copies keep two
182
+ separate `PluginKey` counters, both produce the key `plugin$`, and ProseMirror
183
+ throws `RangeError: Adding different instances of a keyed plugin` the moment the
184
+ editor view is created. Vite's `resolve.dedupe` cannot save you here, because
185
+ Vitest treats a package outside the root directory as external and lets Node
186
+ resolve it.
170
187
 
171
- Cài từ tarball thì pnpm đặt package vào đúng store của app peer dependency
172
- resolve về một bản duy nhất giống hệt lúc cài từ npm.
188
+ Installing from a tarball puts the package in the app's own store, and peer
189
+ dependencies resolve to a single copyexactly like installing from npm.
190
+
191
+ ### Publishing
192
+
193
+ ```bash
194
+ pnpm type-check
195
+ npm version minor # creates the version commit and tag
196
+ npm publish # prepublishOnly runs the build
197
+ git push --follow-tags
198
+ ```
@@ -48,7 +48,7 @@ export interface UploadOptions {
48
48
  * @param {AbortSignal} signal - Signal that can be used to abort the upload
49
49
  * @returns {Promise<string>} Promise resolving to the URL of the uploaded file
50
50
  */
51
- upload: (file: File, onProgress: (event: {
51
+ upload?: (file: File, onProgress: (event: {
52
52
  progress: number;
53
53
  }) => void, signal: AbortSignal) => Promise<string>;
54
54
  /**