xdd-rich-editor-vue3 2.0.8
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/LICENSE +21 -0
- package/README.en.md +430 -0
- package/README.md +219 -0
- package/README.zh-CN.md +430 -0
- package/dist/.nojekyll +0 -0
- package/dist/assets/icons/index.d.ts +3 -0
- package/dist/components/bubble-menu/ImageBubbleMenu.vue.d.ts +31 -0
- package/dist/components/bubble-menu/index.vue.d.ts +25 -0
- package/dist/components/color/BackgroundColorPopover.vue.d.ts +77 -0
- package/dist/components/color/ColorPopover.vue.d.ts +77 -0
- package/dist/components/emoji/EmojiIcon.vue.d.ts +34 -0
- package/dist/components/emoji/EmojiList.vue.d.ts +28 -0
- package/dist/components/emoji/suggestion.d.ts +9 -0
- package/dist/components/image/ImageIcon.vue.d.ts +94 -0
- package/dist/components/image/ImageNodeViewWrapper.vue.d.ts +93 -0
- package/dist/components/image/UploadImageModal.vue.d.ts +47 -0
- package/dist/components/imports/index.vue.d.ts +77 -0
- package/dist/components/layout/Contents.vue.d.ts +51 -0
- package/dist/components/link/AddLinkButton.vue.d.ts +51 -0
- package/dist/components/table/ContextMenu.vue.d.ts +62 -0
- package/dist/components/table/TableIcon.vue.d.ts +36 -0
- package/dist/components/task/TaskIcon.vue.d.ts +43 -0
- package/dist/components/text/HeadingPopselect.vue.d.ts +97 -0
- package/dist/components/text/TextAlignPopselect.vue.d.ts +88 -0
- package/dist/components/text/TextLineHeightPopselect.vue.d.ts +88 -0
- package/dist/components/toolbar/ButtonIcon.vue.d.ts +55 -0
- package/dist/components/toolbar/CodeBlockLowlight.vue.d.ts +127 -0
- package/dist/components/toolbar/Toolbar.vue.d.ts +55 -0
- package/dist/constants/default-config.d.ts +1 -0
- package/dist/constants/toolbar-schema.d.ts +1 -0
- package/dist/core/Editor.vue.d.ts +207 -0
- package/dist/core/EditorContent.vue.d.ts +112 -0
- package/dist/core/editor-props.d.ts +143 -0
- package/dist/core/useEditor.d.ts +49 -0
- package/dist/core/useEditorEvents.d.ts +36 -0
- package/dist/css/style.css +1 -0
- package/dist/extensions/code/Code.d.ts +2 -0
- package/dist/extensions/code/CodeBlock.d.ts +2 -0
- package/dist/extensions/code/CodeBlockLowlight.d.ts +2 -0
- package/dist/extensions/emoji/Emoji.d.ts +7 -0
- package/dist/extensions/image/Image.d.ts +1 -0
- package/dist/extensions/imports/Imports.d.ts +3 -0
- package/dist/extensions/index.d.ts +29 -0
- package/dist/extensions/link/Link.d.ts +2 -0
- package/dist/extensions/list/BulletList.d.ts +2 -0
- package/dist/extensions/list/OrderedList.d.ts +2 -0
- package/dist/extensions/marks/Bold.d.ts +2 -0
- package/dist/extensions/marks/Color.d.ts +2 -0
- package/dist/extensions/marks/Highlight.d.ts +2 -0
- package/dist/extensions/marks/Italic.d.ts +2 -0
- package/dist/extensions/marks/Strike.d.ts +2 -0
- package/dist/extensions/marks/Subscript.d.ts +2 -0
- package/dist/extensions/marks/Superscript.d.ts +2 -0
- package/dist/extensions/marks/TextAlignment.d.ts +2 -0
- package/dist/extensions/marks/Underline.d.ts +2 -0
- package/dist/extensions/table/Table.d.ts +2 -0
- package/dist/extensions/task/TaskItem.d.ts +7 -0
- package/dist/extensions/task/TaskList.d.ts +2 -0
- package/dist/extensions/title/Heading.d.ts +49 -0
- package/dist/extensions/utils/BackgroundColor.d.ts +31 -0
- package/dist/extensions/utils/Blockquote.d.ts +2 -0
- package/dist/extensions/utils/Clear.d.ts +3 -0
- package/dist/extensions/utils/History.d.ts +2 -0
- package/dist/extensions/utils/HorizontalRule.d.ts +2 -0
- package/dist/extensions/utils/LineHeight.d.ts +39 -0
- package/dist/favicon.svg +1 -0
- package/dist/hooks/useContextMenu.d.ts +17 -0
- package/dist/hooks/useDiscreteApi.d.ts +12 -0
- package/dist/hooks/useTev3I18n.d.ts +7 -0
- package/dist/hooks/useTheme.d.ts +16 -0
- package/dist/icons.svg +24 -0
- package/dist/index.d.ts +22 -0
- package/dist/install.d.ts +2 -0
- package/dist/locales/en-US.d.ts +106 -0
- package/dist/locales/index.d.ts +19 -0
- package/dist/locales/zh-CN.d.ts +106 -0
- package/dist/theme/index.d.ts +7 -0
- package/dist/typings/index.d.ts +47 -0
- package/dist/utils/color.d.ts +1 -0
- package/dist/utils/config.d.ts +11 -0
- package/dist/utils/header.d.ts +16 -0
- package/dist/utils/image.d.ts +16 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/xdd-rich-editor-vue3.es.js +163670 -0
- package/dist/xdd-rich-editor-vue3.umd.js +2675 -0
- package/package.json +114 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ZekoFront
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# tiptap-editor-vue3
|
|
2
|
+
|
|
3
|
+
[简体中文](./README.zh-CN.md) | English
|
|
4
|
+
|
|
5
|
+
> A high-quality rich text editor component library built on [Tiptap 3](https://tiptap.dev/) + Vue 3 + TypeScript + Vite.
|
|
6
|
+
|
|
7
|
+
- Batteries included: toolbar, bubble menu, table context menu, outline sidebar, floatable image selection
|
|
8
|
+
- Built-in i18n (Chinese / English, powered by vue-i18n)
|
|
9
|
+
- Light / dark / follow-system theme, fully customisable via CSS variables
|
|
10
|
+
- Full TypeScript types, supports tree-shaking and Hooks-based composition
|
|
11
|
+
- Live demo: **https://zekofront.github.io/tiptap-editor-vue3/**
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
| Category | Items |
|
|
18
|
+
| -------- | ----- |
|
|
19
|
+
| **Text marks** | Bold, Italic, Underline, Strike, Highlight, Text color, Background color, Subscript, Superscript, Inline code |
|
|
20
|
+
| **Block nodes** | Heading H1–H6, Paragraph, Bullet list, Ordered list, **Task list** (checkable), Blockquote, Horizontal rule, **Code block** (lowlight syntax highlight), Image, Table, Link, **Emoji** (`:` trigger) |
|
|
21
|
+
| **Layout & behaviour** | Text alignment (left / center / right / justify), Line height, Drag handle (move blocks), RTL support |
|
|
22
|
+
| **Editor UX** | Top toolbar, Selection bubble menu, Image bubble menu, Table context menu, Outline / table-of-contents sidebar, Placeholder, Character count limit, Undo / redo, Clear document, **Import documents** (TXT, DOC, DOCX), Export to **DOCX** |
|
|
23
|
+
| **Component-level** | Read-only view (`XddRichEditorView`), `useEditor` / `useEditorEvents` Hooks, Light / dark / system theme, zh-CN / en-US i18n, Customizable bubble menu items, CSS variables for theming |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install tiptap-editor-vue3
|
|
31
|
+
# or
|
|
32
|
+
pnpm add tiptap-editor-vue3
|
|
33
|
+
# or
|
|
34
|
+
yarn add tiptap-editor-vue3
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
> Requires **Vue 3** in your project (the only external dependency; all other deps — Tiptap, naive-ui, vue-i18n, etc. — are bundled into the dist).
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
### Option 1: Install as a plugin (recommended)
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
// main.ts
|
|
47
|
+
import { createApp } from "vue";
|
|
48
|
+
import XddRichEditorVue3 from "tiptap-editor-vue3";
|
|
49
|
+
import "tiptap-editor-vue3/dist/css/style.css";
|
|
50
|
+
import App from "./App.vue";
|
|
51
|
+
|
|
52
|
+
const app = createApp(App);
|
|
53
|
+
app.use(XddRichEditorVue3);
|
|
54
|
+
app.mount("#app");
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```vue
|
|
58
|
+
<!-- App.vue -->
|
|
59
|
+
<template>
|
|
60
|
+
<XddRichEditorVue3
|
|
61
|
+
v-model="content"
|
|
62
|
+
:is-editable="true"
|
|
63
|
+
:character-count="20000"
|
|
64
|
+
locale="en-US"
|
|
65
|
+
placeholder="Start typing..."
|
|
66
|
+
@ready="onReady"
|
|
67
|
+
@update="onUpdate"
|
|
68
|
+
/>
|
|
69
|
+
</template>
|
|
70
|
+
|
|
71
|
+
<script setup lang="ts">
|
|
72
|
+
import { ref } from "vue";
|
|
73
|
+
import type { Editor, EditorUpdatePayload } from "tiptap-editor-vue3";
|
|
74
|
+
|
|
75
|
+
const content = ref("<p>Initial content</p>");
|
|
76
|
+
|
|
77
|
+
const onReady = (editor: Editor) => {
|
|
78
|
+
console.log("editor ready", editor);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const onUpdate = ({ html, json }: EditorUpdatePayload) => {
|
|
82
|
+
console.log(html, json);
|
|
83
|
+
};
|
|
84
|
+
</script>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
> Use `v-model` to two-way bind content. Pass an HTML string (default) or a Tiptap JSON object; toggle the format emitted by `update:modelValue` via the `outputFormat` prop.
|
|
88
|
+
|
|
89
|
+
### Option 2: On-demand import
|
|
90
|
+
|
|
91
|
+
```vue
|
|
92
|
+
<template>
|
|
93
|
+
<XddRichEditorVue3 v-model:is-editable="editable" @update="onUpdate" />
|
|
94
|
+
</template>
|
|
95
|
+
|
|
96
|
+
<script setup lang="ts">
|
|
97
|
+
import { ref } from "vue";
|
|
98
|
+
import { XddRichEditorVue3, type EditorUpdatePayload } from "tiptap-editor-vue3";
|
|
99
|
+
import "tiptap-editor-vue3/dist/css/style.css";
|
|
100
|
+
|
|
101
|
+
const editable = ref(true);
|
|
102
|
+
const onUpdate = (p: EditorUpdatePayload) => console.log(p.html);
|
|
103
|
+
</script>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Read-only view
|
|
107
|
+
|
|
108
|
+
```vue
|
|
109
|
+
<template>
|
|
110
|
+
<XddRichEditorView :editor="editor" />
|
|
111
|
+
</template>
|
|
112
|
+
|
|
113
|
+
<script setup lang="ts">
|
|
114
|
+
import { XddRichEditorView, useEditor } from "tiptap-editor-vue3";
|
|
115
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
116
|
+
|
|
117
|
+
const { editor } = useEditor({
|
|
118
|
+
extensions: () => [StarterKit],
|
|
119
|
+
content: () => "<p>Hello</p>",
|
|
120
|
+
editable: () => false
|
|
121
|
+
});
|
|
122
|
+
</script>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Props
|
|
128
|
+
|
|
129
|
+
| Name | Type | Default | Description |
|
|
130
|
+
| ---- | ---- | ------- | ----------- |
|
|
131
|
+
| `modelValue` (`v-model`) | `string \| JSONContent \| null` | `""` | Editor content. HTML string or Tiptap JSON |
|
|
132
|
+
| `outputFormat` | `'html' \| 'json'` | `'html'` | Format of the value emitted by `update:modelValue` |
|
|
133
|
+
| `defaultConfig` | `Record<string, any> \| null` | `null` | Extra options forwarded to the underlying `new Editor(...)` |
|
|
134
|
+
| `extensions` | `AnyExtension[]` | `[]` | Custom extensions; falls back to the built-in full set when empty |
|
|
135
|
+
| `isEditable` | `boolean` | `true` | Whether the editor is editable |
|
|
136
|
+
| `isShowToolbar` | `boolean` | `true` | Show the top toolbar |
|
|
137
|
+
| `isEnabledContent` | `boolean` | `true` | Render the content area (toolbar-only when `false`) |
|
|
138
|
+
| `characterCount` | `number \| string` | `10000` | Maximum character count; further input is blocked |
|
|
139
|
+
| `locale` | `'zh-CN' \| 'en-US'` | `'zh-CN'` | UI locale (toolbar tooltips, table menus, placeholder, etc.) |
|
|
140
|
+
| `theme` | `'system' \| 'light' \| 'dark'` | `'system'` | Color theme; `system` follows OS `prefers-color-scheme` |
|
|
141
|
+
| `placeholder` | `string` | i18n default | Placeholder for empty document; falls back to the current locale |
|
|
142
|
+
| `customDubbleMenus` | `IDubbleMenu[]` | `[]` | Custom buttons for the bubble menu |
|
|
143
|
+
| `editorWrapperClass` | `string \| any[] \| Record<string, any>` | `undefined` | Class for the outermost wrapper |
|
|
144
|
+
| `editorToolkitClass` | `string \| any[] \| Record<string, any>` | `undefined` | Class for the toolbar |
|
|
145
|
+
| `editorContentClass` | `string` | `'custom-tiptap-content'` | Class for the ProseMirror content container |
|
|
146
|
+
| `editorContentsNavClass` | `string \| any[] \| Record<string, any>` | `undefined` | Class for the outline sidebar |
|
|
147
|
+
|
|
148
|
+
> Types like `IDubbleMenu` are exported from the package: `import type { IDubbleMenu, EditorProps } from "tiptap-editor-vue3"`.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Events
|
|
153
|
+
|
|
154
|
+
| Event | Payload | Description |
|
|
155
|
+
| ----- | ----- | ----------- |
|
|
156
|
+
| `update:modelValue` | `string \| JSONContent` | Content changed (`v-model` sync). HTML string by default, or JSON when `outputFormat="json"` |
|
|
157
|
+
| `ready` | `editor: Editor` | Fired once the editor instance is ready (equivalent to Tiptap's `create`) |
|
|
158
|
+
| `update` | `{ editor, html, json }` | Content changed; `html` / `json` are pre-computed |
|
|
159
|
+
| `selection-update` | `{ editor }` | Caret / selection changed |
|
|
160
|
+
| `transaction` | `{ editor, transaction }` | Any ProseMirror transaction |
|
|
161
|
+
| `focus` | `{ editor, event: FocusEvent }` | Editor gained focus |
|
|
162
|
+
| `blur` | `{ editor, event: FocusEvent }` | Editor lost focus |
|
|
163
|
+
| `before-create` | `{ editor }` | Before the instance is created |
|
|
164
|
+
| `content-error` | `{ editor, error }` | Failed to parse initial content |
|
|
165
|
+
| `destroy` | — | Instance destroyed |
|
|
166
|
+
|
|
167
|
+
`update` payload type:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import type { EditorUpdatePayload } from "tiptap-editor-vue3";
|
|
171
|
+
|
|
172
|
+
interface EditorUpdatePayload {
|
|
173
|
+
editor: Editor;
|
|
174
|
+
html: string;
|
|
175
|
+
json: JSONContent;
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Import Documents (TXT, Word)
|
|
182
|
+
|
|
183
|
+
The editor supports importing content from TXT and Word (DOC, DOCX) files via the import button in the toolbar. When clicked, a dropdown menu appears with options to import TXT or Word files. After selecting a file, the content is parsed and inserted into the editor. Success and error messages are displayed using localized notifications.
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
## Image upload
|
|
187
|
+
|
|
188
|
+
All image-upload customisation lives under `defaultConfig.uploadImage`. The rule is simple:
|
|
189
|
+
|
|
190
|
+
- If `imageLink` / `customUpload` are **functions**, the modal hands the URL / files to your code and you call `editor.commands.setImage(...)` yourself.
|
|
191
|
+
- Otherwise the editor falls back to its built-in behaviour (URL → `setImage({ src })`, file → base64 → `setImage`).
|
|
192
|
+
|
|
193
|
+
`accept` / `maxSize` / `maxCount` always run first — invalid files are rejected with a localised toast before your callback is invoked, and clicking "Upload" with nothing selected shows a warning.
|
|
194
|
+
|
|
195
|
+
> Custom image upload — **the structure below must be respected exactly, otherwise the callbacks won't fire**:
|
|
196
|
+
|
|
197
|
+
```vue
|
|
198
|
+
<template>
|
|
199
|
+
<XddRichEditorVue3
|
|
200
|
+
v-model="content"
|
|
201
|
+
:default-config="defaultConfig"
|
|
202
|
+
@ready="onReady"
|
|
203
|
+
/>
|
|
204
|
+
</template>
|
|
205
|
+
|
|
206
|
+
<script setup lang="ts">
|
|
207
|
+
import { shallowRef } from "vue";
|
|
208
|
+
import type { Editor, Tev3DefaultConfig } from "tiptap-editor-vue3";
|
|
209
|
+
|
|
210
|
+
const editors = shallowRef<Editor | null>(null);
|
|
211
|
+
|
|
212
|
+
const onReady = (editor: Editor) => {
|
|
213
|
+
editors.value = editor;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const defaultConfig: Tev3DefaultConfig = {
|
|
217
|
+
uploadImage: {
|
|
218
|
+
accept: "image/png,image/jpeg,image/webp",
|
|
219
|
+
maxSize: 5 * 1024 * 1024, // 5 MB per file
|
|
220
|
+
maxCount: 9,
|
|
221
|
+
imageLink: (link: string) => {
|
|
222
|
+
console.log(link, editors.value, "imageLink");
|
|
223
|
+
editors.value?.commands.setImage({ src: link });
|
|
224
|
+
},
|
|
225
|
+
customUpload: async (files) => {
|
|
226
|
+
console.log(files, editors.value, "customUpload");
|
|
227
|
+
for (let i = 0; i < files.length; i++) {
|
|
228
|
+
if (files[i]) setImageOne(files[i] as File);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const setImageOne = (file: File) => {
|
|
235
|
+
const reader = new FileReader();
|
|
236
|
+
reader.onload = event => {
|
|
237
|
+
const base64 = event.target?.result as string;
|
|
238
|
+
editors.value?.commands.setImage({ src: base64 });
|
|
239
|
+
};
|
|
240
|
+
reader.readAsDataURL(file);
|
|
241
|
+
};
|
|
242
|
+
</script>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
| Field | Type | Default | Description |
|
|
246
|
+
| ----- | ---- | ------- | ----------- |
|
|
247
|
+
| `accept` | `string` | `image/png,image/jpeg,image/gif,image/webp,image/svg+xml` | HTML `accept` syntax; filters the file picker and validates types |
|
|
248
|
+
| `maxSize` | `number` | `10 * 1024 * 1024` | Max bytes per file. Oversized files are rejected with a toast |
|
|
249
|
+
| `maxCount` | `number` | `Infinity` | Max files per upload session |
|
|
250
|
+
| `imageLink` | `(url: string) => void` | — | "Insert by URL" callback. Defined → custom path; otherwise default `setImage({ src })` |
|
|
251
|
+
| `customUpload` | `(files: File[] \| FileList) => void \| Promise<void>` | — | "Upload" callback. Defined → custom path; otherwise files are inlined as base64 |
|
|
252
|
+
|
|
253
|
+
Exported types: `Tev3DefaultConfig`, `Tev3UploadImageConfig`.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Theme (light / dark)
|
|
258
|
+
|
|
259
|
+
Controlled by the `theme` prop:
|
|
260
|
+
|
|
261
|
+
```vue
|
|
262
|
+
<XddRichEditorVue3 theme="system" />
|
|
263
|
+
<XddRichEditorVue3 theme="light" />
|
|
264
|
+
<XddRichEditorVue3 theme="dark" />
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
- `system`: follows the OS `prefers-color-scheme` and reacts in real time;
|
|
268
|
+
- `light` / `dark`: pinned to that theme.
|
|
269
|
+
|
|
270
|
+
Internally a `data-tev3-theme="light|dark"` attribute is applied to the wrapper and all colors are driven by CSS variables. To customise the palette, override variables in your stylesheet:
|
|
271
|
+
|
|
272
|
+
```css
|
|
273
|
+
.vue3-tiptap-editor[data-tev3-theme="light"] {
|
|
274
|
+
--theme-color: #2080f0;
|
|
275
|
+
--tev3-toolbar-bg: #fafbfc;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.vue3-tiptap-editor[data-tev3-theme="dark"] {
|
|
279
|
+
--theme-color: #36ad6a;
|
|
280
|
+
--tev3-editor-bg: #15161a;
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
You can also drive themes programmatically with `useTheme()`:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
import { ref } from "vue";
|
|
288
|
+
import { useTheme, type Tev3Theme } from "tiptap-editor-vue3";
|
|
289
|
+
|
|
290
|
+
const theme = ref<Tev3Theme>("system");
|
|
291
|
+
const { resolvedTheme } = useTheme(() => theme.value); // -> 'light' | 'dark'
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Internationalization
|
|
297
|
+
|
|
298
|
+
Built-in `zh-CN` / `en-US` messages. Switch via the `locale` prop, or call `setLocale()` programmatically.
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
import { setLocale, t } from "tiptap-editor-vue3";
|
|
302
|
+
|
|
303
|
+
setLocale("en-US");
|
|
304
|
+
console.log(t("toolbar.bold")); // "Bold"
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
```vue
|
|
308
|
+
<XddRichEditorVue3 locale="en-US" />
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
> The toolbar, table menus, outline sidebar and placeholder refresh automatically when `locale` changes.
|
|
312
|
+
|
|
313
|
+
To override or extend messages, call `tev3I18n.global.mergeLocaleMessage('en-US', { ... })` from your app.
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Advanced: Compose with Hooks
|
|
318
|
+
|
|
319
|
+
If the built-in toolbar does not fit your needs, build your own UI on top of `useEditor` / `useEditorEvents`:
|
|
320
|
+
|
|
321
|
+
```vue
|
|
322
|
+
<template>
|
|
323
|
+
<div>
|
|
324
|
+
<button @click="editor?.chain().focus().toggleBold().run()">Bold</button>
|
|
325
|
+
<XddRichEditorView :editor="editor" />
|
|
326
|
+
</div>
|
|
327
|
+
</template>
|
|
328
|
+
|
|
329
|
+
<script setup lang="ts">
|
|
330
|
+
import {
|
|
331
|
+
XddRichEditorView,
|
|
332
|
+
useEditor,
|
|
333
|
+
useEditorEvents,
|
|
334
|
+
type EditorUpdatePayload
|
|
335
|
+
} from "tiptap-editor-vue3";
|
|
336
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
337
|
+
|
|
338
|
+
const emit = defineEmits<{ (e: "update", p: EditorUpdatePayload): void }>();
|
|
339
|
+
|
|
340
|
+
const { editor, editable, toggleEditable } = useEditor({
|
|
341
|
+
extensions: () => [StarterKit],
|
|
342
|
+
content: () => "<p>Hello</p>",
|
|
343
|
+
editable: () => true
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
useEditorEvents(editor, { emit });
|
|
347
|
+
</script>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
`useEditor` returns:
|
|
351
|
+
|
|
352
|
+
| Field | Type | Description |
|
|
353
|
+
| ----- | ---- | ----------- |
|
|
354
|
+
| `editor` | `ShallowRef<Editor \| null>` | Instance (ready after `onMounted`) |
|
|
355
|
+
| `editable` | `Ref<boolean>` | Editable state |
|
|
356
|
+
| `rtl` | `Ref<boolean>` | RTL layout flag |
|
|
357
|
+
| `toggleEditable` | `() => void` | Toggle editable state |
|
|
358
|
+
| `toggleRtl` | `() => void` | Toggle RTL |
|
|
359
|
+
| `create` / `destroy` | `() => void` | Manual lifecycle control |
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Public API
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
// Components
|
|
367
|
+
export { XddRichEditorVue3, XddRichEditorView };
|
|
368
|
+
|
|
369
|
+
// Composables
|
|
370
|
+
export { useEditor, useEditorEvents, useTev3I18n };
|
|
371
|
+
|
|
372
|
+
// Props / Types
|
|
373
|
+
export { editorProps };
|
|
374
|
+
export type {
|
|
375
|
+
EditorProps,
|
|
376
|
+
UseEditorOptions,
|
|
377
|
+
UseEditorReturn,
|
|
378
|
+
EditorUpdatePayload,
|
|
379
|
+
EditorEventName,
|
|
380
|
+
Tev3Locale
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// i18n
|
|
384
|
+
export { tev3I18n, t, setLocale, getLocale, installTev3I18n, SUPPORTED_LOCALES, zhCN, enUS };
|
|
385
|
+
|
|
386
|
+
// Theme
|
|
387
|
+
export { SUPPORTED_THEMES, DEFAULT_THEME, resolveTheme, useTheme };
|
|
388
|
+
export type { Tev3Theme, Tev3ResolvedTheme };
|
|
389
|
+
|
|
390
|
+
// Re-exported Tiptap APIs (avoid double installs)
|
|
391
|
+
export { Editor, EditorContent, NodeViewWrapper, mergeAttributes };
|
|
392
|
+
export type { XddRichEditor, JSONContent, Content, AnyExtension };
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Scripts
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
npm run dev # Start the local playground
|
|
401
|
+
npm run build # Build the component library (dist/)
|
|
402
|
+
npm run build:demo # Build the playground demo site (dist-demo/)
|
|
403
|
+
npm run preview:demo # Preview the demo build locally
|
|
404
|
+
npm run fmt # Format (oxfmt)
|
|
405
|
+
npm run lint # Lint (oxlint)
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Live Demo (GitHub Pages)
|
|
411
|
+
|
|
412
|
+
`.github/workflows/deploy-pages.yml` automatically builds and publishes `dist-demo` after every push to `main`.
|
|
413
|
+
|
|
414
|
+
In the GitHub repository, set `Settings → Pages → Source` to **GitHub Actions**. Demo URL:
|
|
415
|
+
|
|
416
|
+
**https://zekofront.github.io/tiptap-editor-vue3/**
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## References
|
|
421
|
+
|
|
422
|
+
- [Tiptap 3 Documentation](https://tiptap.dev/docs)
|
|
423
|
+
- [ProseMirror Reference](https://prosemirror.net/docs/ref/)
|
|
424
|
+
- [Vue 3 + TypeScript Guide](https://vuejs.org/guide/typescript/overview.html)
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## License
|
|
429
|
+
|
|
430
|
+
[MIT](./LICENSE)
|
package/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# tiptap-editor-vue3
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/tiptap-editor-vue3)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
> A high-quality rich text editor component library built on **[Tiptap 3](https://tiptap.dev/) + Vue 3 + TypeScript**.
|
|
7
|
+
>
|
|
8
|
+
> 基于 **Tiptap 3 + Vue 3 + TypeScript** 的高质量富文本编辑器组件库。
|
|
9
|
+
|
|
10
|
+
- Batteries included: toolbar, bubble menu, table context menu, outline sidebar
|
|
11
|
+
- Built-in **i18n** (zh-CN / en-US) and **theme** (light / dark / system)
|
|
12
|
+
- Full TypeScript types, tree-shakable, Hooks-friendly
|
|
13
|
+
- **Live demo**: https://zekofront.github.io/tiptap-editor-vue3/
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
**Text formatting** — Bold · Italic · Underline · Strike · Highlight · Text color · Background color · Subscript · Superscript · Inline code
|
|
18
|
+
|
|
19
|
+
**Blocks** — Heading (H1–H6) · Paragraph · Bullet list · Ordered list · Task list (checkable) · Blockquote · Horizontal rule · Code block (with syntax highlight via lowlight) · Image · Table · Link · Emoji (`:` trigger)
|
|
20
|
+
|
|
21
|
+
**Layout** — Text alignment (left / center / right / justify) · Line height · Drag handle (move blocks) · RTL support
|
|
22
|
+
|
|
23
|
+
**Editor UX** — Top toolbar · Selection bubble menu · Image bubble menu · Table context menu · Outline / table-of-contents sidebar · Placeholder · Character count limit · Undo / redo · Clear document · Export to **DOCX** · **Import TXT and Word documents**
|
|
24
|
+
|
|
25
|
+
**Component-level** — Read-only view (`XddRichEditorView`) · `useEditor` / `useEditorEvents` Hooks for custom UI · Light / dark / system theme · Chinese / English i18n · Customizable bubble menu items · CSS variables for theming
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install tiptap-editor-vue3
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
> Requires **Vue 3** in your project (the only external dependency; all other deps are bundled).
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// main.ts
|
|
39
|
+
import { createApp } from "vue";
|
|
40
|
+
import XddRichEditorVue3 from "tiptap-editor-vue3";
|
|
41
|
+
import "tiptap-editor-vue3/dist/css/style.css";
|
|
42
|
+
import App from "./App.vue";
|
|
43
|
+
|
|
44
|
+
createApp(App).use(XddRichEditorVue3).mount("#app");
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```vue
|
|
48
|
+
<template>
|
|
49
|
+
<XddRichEditorVue3
|
|
50
|
+
v-model="content"
|
|
51
|
+
:is-editable="true"
|
|
52
|
+
:character-count="20000"
|
|
53
|
+
locale="zh-CN"
|
|
54
|
+
theme="system"
|
|
55
|
+
placeholder="Start typing..."
|
|
56
|
+
@ready="onReady"
|
|
57
|
+
@update="onUpdate"
|
|
58
|
+
/>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<script setup lang="ts">
|
|
62
|
+
import { ref } from "vue";
|
|
63
|
+
import type { Editor, EditorUpdatePayload } from "tiptap-editor-vue3";
|
|
64
|
+
|
|
65
|
+
const content = ref("<p>Hello world</p>");
|
|
66
|
+
|
|
67
|
+
const onReady = (editor: Editor) => console.log("ready", editor);
|
|
68
|
+
const onUpdate = ({ html, json }: EditorUpdatePayload) => console.log(html, json);
|
|
69
|
+
</script>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
> Use `v-model` to two-way bind content. Pass an HTML string (default) or a Tiptap JSON object; toggle the format of `update:modelValue` via the `outputFormat` prop.
|
|
73
|
+
|
|
74
|
+
## Props
|
|
75
|
+
|
|
76
|
+
| Name | Type | Default | Description |
|
|
77
|
+
| ---- | ---- | ------- | ----------- |
|
|
78
|
+
| `modelValue` (`v-model`) | `string \| JSONContent \| null` | `""` | Editor content. HTML string or Tiptap JSON |
|
|
79
|
+
| `outputFormat` | `'html' \| 'json'` | `'html'` | Format of the value emitted by `update:modelValue` |
|
|
80
|
+
| `defaultConfig` | `Record<string, any> \| null` | `null` | Extra options forwarded to the underlying `new Editor(...)` |
|
|
81
|
+
| `extensions` | `AnyExtension[]` | `[]` | Custom extensions; falls back to the built-in full set when empty |
|
|
82
|
+
| `isEditable` | `boolean` | `true` | Whether the editor is editable |
|
|
83
|
+
| `isShowToolbar` | `boolean` | `true` | Show the top toolbar |
|
|
84
|
+
| `isEnabledContent` | `boolean` | `true` | Render the content area (toolbar-only when `false`) |
|
|
85
|
+
| `characterCount` | `number \| string` | `10000` | Maximum character count; further input is blocked |
|
|
86
|
+
| `locale` | `'zh-CN' \| 'en-US'` | `'zh-CN'` | UI locale (toolbar tooltips, table menus, placeholder, etc.) |
|
|
87
|
+
| `theme` | `'system' \| 'light' \| 'dark'` | `'system'` | Color theme; `system` follows OS `prefers-color-scheme` |
|
|
88
|
+
| `placeholder` | `string` | i18n default | Placeholder for empty document |
|
|
89
|
+
| `customDubbleMenus` | `IDubbleMenu[]` | `[]` | Custom buttons for the bubble menu |
|
|
90
|
+
| `editorWrapperClass` | `string \| any[] \| Record<string, any>` | `undefined` | Class for the outermost wrapper |
|
|
91
|
+
| `editorToolkitClass` | `string \| any[] \| Record<string, any>` | `undefined` | Class for the toolbar |
|
|
92
|
+
| `editorContentClass` | `string` | `'custom-tiptap-content'` | Class for the ProseMirror content container |
|
|
93
|
+
| `editorContentsNavClass` | `string \| any[] \| Record<string, any>` | `undefined` | Class for the outline sidebar |
|
|
94
|
+
|
|
95
|
+
## Events
|
|
96
|
+
|
|
97
|
+
| Event | Payload | Description |
|
|
98
|
+
| ----- | ------- | ----------- |
|
|
99
|
+
| `update:modelValue` | `string \| JSONContent` | Content changed (`v-model` sync). HTML string by default, or JSON when `outputFormat="json"` |
|
|
100
|
+
| `ready` | `editor: Editor` | Editor instance is ready (Tiptap `create`) |
|
|
101
|
+
| `update` | `{ editor, html, json }` | Content changed; `html` / `json` are pre-computed |
|
|
102
|
+
| `selection-update` | `{ editor }` | Caret / selection changed |
|
|
103
|
+
| `transaction` | `{ editor, transaction }` | Any ProseMirror transaction |
|
|
104
|
+
| `focus` | `{ editor, event: FocusEvent }` | Editor gained focus |
|
|
105
|
+
| `blur` | `{ editor, event: FocusEvent }` | Editor lost focus |
|
|
106
|
+
| `before-create` | `{ editor }` | Before the instance is created |
|
|
107
|
+
| `content-error` | `{ editor, error }` | Failed to parse initial content |
|
|
108
|
+
| `destroy` | — | Instance destroyed |
|
|
109
|
+
|
|
110
|
+
`update` payload type:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import type { EditorUpdatePayload } from "tiptap-editor-vue3";
|
|
114
|
+
|
|
115
|
+
interface EditorUpdatePayload {
|
|
116
|
+
editor: Editor;
|
|
117
|
+
html: string;
|
|
118
|
+
json: JSONContent;
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Import Documents (TXT and Word)
|
|
123
|
+
|
|
124
|
+
The editor supports importing content from TXT and Word documents directly. This feature allows users to easily bring content from external documents into the editor. To use this feature, click on the import button in the toolbar and select either "Import TXT file" or "Import Word file".
|
|
125
|
+
|
|
126
|
+
## Image upload
|
|
127
|
+
|
|
128
|
+
Custom image uploading is configured through `defaultConfig.uploadImage`. The rule is simple:
|
|
129
|
+
|
|
130
|
+
- If `imageLink` / `customUpload` are **functions**, the modal hands the URL / files to your code and you call `editor.commands.setImage(...)` yourself.
|
|
131
|
+
- Otherwise the editor falls back to the built-in behaviour (URL → `setImage({ src })`, file → base64 → `setImage`).
|
|
132
|
+
|
|
133
|
+
`accept` / `maxSize` / `maxCount` always run first — invalid files are rejected with a localised toast before your callback is invoked, and clicking "Upload" with nothing selected shows a warning.
|
|
134
|
+
|
|
135
|
+
```vue
|
|
136
|
+
<template>
|
|
137
|
+
<XddRichEditorVue3
|
|
138
|
+
v-model="content"
|
|
139
|
+
:default-config="defaultConfig"
|
|
140
|
+
@ready="onReady"
|
|
141
|
+
/>
|
|
142
|
+
</template>
|
|
143
|
+
|
|
144
|
+
<script setup lang="ts">
|
|
145
|
+
import { shallowRef } from "vue";
|
|
146
|
+
import type { Editor, Tev3DefaultConfig } from "tiptap-editor-vue3";
|
|
147
|
+
|
|
148
|
+
const editors = shallowRef<Editor | null>(null);
|
|
149
|
+
|
|
150
|
+
const onReady = (editor: Editor) => {
|
|
151
|
+
editors.value = editor;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Custom image upload — must follow this exact shape, otherwise the callbacks won't fire
|
|
155
|
+
const defaultConfig: Tev3DefaultConfig = {
|
|
156
|
+
uploadImage: {
|
|
157
|
+
accept: "image/png,image/jpeg,image/webp",
|
|
158
|
+
maxSize: 5 * 1024 * 1024, // 5 MB per file
|
|
159
|
+
maxCount: 9,
|
|
160
|
+
imageLink: (link: string) => {
|
|
161
|
+
console.log(link, editors.value, "imageLink");
|
|
162
|
+
editors.value?.commands.setImage({ src: link });
|
|
163
|
+
},
|
|
164
|
+
customUpload: async (files) => {
|
|
165
|
+
console.log(files, editors.value, "customUpload");
|
|
166
|
+
for (let i = 0; i < files.length; i++) {
|
|
167
|
+
if (files[i]) setImageOne(files[i] as File);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const setImageOne = (file: File) => {
|
|
174
|
+
const reader = new FileReader();
|
|
175
|
+
reader.onload = event => {
|
|
176
|
+
const base64 = event.target?.result as string;
|
|
177
|
+
editors.value?.commands.setImage({ src: base64 });
|
|
178
|
+
};
|
|
179
|
+
reader.readAsDataURL(file);
|
|
180
|
+
};
|
|
181
|
+
</script>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
| Field | Type | Default | Description |
|
|
185
|
+
| ----- | ---- | ------- | ----------- |
|
|
186
|
+
| `accept` | `string` | `image/png,image/jpeg,image/gif,image/webp,image/svg+xml` | HTML `accept` syntax; filters the file picker and validates types |
|
|
187
|
+
| `maxSize` | `number` | `10 * 1024 * 1024` | Max bytes per file. Oversized files are rejected with a toast |
|
|
188
|
+
| `maxCount` | `number` | `Infinity` | Max files per upload session |
|
|
189
|
+
| `imageLink` | `(url: string) => void` | — | Callback for the "Insert by URL" tab. Defined → custom path; otherwise default `setImage({ src })` |
|
|
190
|
+
| `customUpload` | `(files: File[] \| FileList) => void \| Promise<void>` | — | Callback for the "Upload" tab. Defined → custom path; otherwise files are inlined as base64 |
|
|
191
|
+
|
|
192
|
+
Exported types: `Tev3DefaultConfig`, `Tev3UploadImageConfig`.
|
|
193
|
+
|
|
194
|
+
## Theme & i18n
|
|
195
|
+
|
|
196
|
+
```vue
|
|
197
|
+
<XddRichEditorVue3 theme="dark" locale="en-US" />
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
- `theme`: `'system' | 'light' | 'dark'` — applied via `data-tev3-theme` on the wrapper, all colors driven by CSS variables (override `--theme-color`, `--tev3-editor-bg`, `--tev3-toolbar-bg`, etc.)
|
|
201
|
+
- `locale`: `'zh-CN' | 'en-US'` — toolbar / menus / placeholder switch reactively
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { setLocale, useTheme, type Tev3Theme } from "tiptap-editor-vue3";
|
|
205
|
+
|
|
206
|
+
setLocale("en-US");
|
|
207
|
+
const { resolvedTheme } = useTheme(() => "system"); // -> 'light' | 'dark'
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Documentation
|
|
211
|
+
|
|
212
|
+
Full documentation including theme customisation, Hooks-based composition (`useEditor` / `useEditorEvents`) and the public API surface:
|
|
213
|
+
|
|
214
|
+
- [English](https://github.com/ZekoFront/tiptap-editor-vue3/blob/main/README.en.md)
|
|
215
|
+
- [简体中文](https://github.com/ZekoFront/tiptap-editor-vue3/blob/main/README.zh-CN.md)
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
[MIT](./LICENSE) © ZekoFront
|