diff --git a/docs/.vitepress/config/en.mts b/docs/.vitepress/config/en.mts index 8bac04ceb..e3c7213df 100644 --- a/docs/.vitepress/config/en.mts +++ b/docs/.vitepress/config/en.mts @@ -198,6 +198,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] { link: 'common-ui/vben-ellipsis-text', text: 'EllipsisText', }, + { + link: 'common-ui/vben-tiptap', + text: 'Tiptap RichTextEditor', + }, ], }, ]; diff --git a/docs/.vitepress/config/zh.mts b/docs/.vitepress/config/zh.mts index 2c3753deb..74e564ee4 100644 --- a/docs/.vitepress/config/zh.mts +++ b/docs/.vitepress/config/zh.mts @@ -196,6 +196,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] { link: 'common-ui/vben-ellipsis-text', text: 'EllipsisText 省略文本', }, + { + link: 'common-ui/vben-tiptap', + text: 'Tiptap 富文本编辑器', + }, ], }, ]; diff --git a/docs/src/components/common-ui/vben-tiptap.md b/docs/src/components/common-ui/vben-tiptap.md new file mode 100644 index 000000000..84b84ad1d --- /dev/null +++ b/docs/src/components/common-ui/vben-tiptap.md @@ -0,0 +1,199 @@ +--- +outline: deep +--- + +# Vben Tiptap 富文本编辑器 + +基于 [Tiptap](https://tiptap.dev/) 构建的富文本编辑器组件,支持丰富的文本格式化、图片插入、图片上传等功能。 + +> 如果文档内没有参数说明,可以尝试在在线示例内寻找 + +::: info 写在前面 + +如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。 + +::: + +## 基础用法 + + + +## 组件列表 + +### VbenTiptap + +富文本编辑器主组件。 + +### VbenTiptapPreview + +富文本内容预览组件,用于只读展示编辑器内容。 + +## API + +### Props + +| 属性名 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| `modelValue` (v-model) | 编辑器内容(HTML字符串) | `string` | `''` | +| `editable` | 是否可编辑 | `boolean` | `true` | +| `toolbar` | 是否显示工具栏 | `boolean` | `true` | +| `previewable` | 是否显示预览按钮 | `boolean` | `true` | +| `placeholder` | 占位提示文本 | `string` | - | +| `minHeight` | 最小高度 | `number \| string` | `240` | +| `maxHeight` | 最大高度 | `number \| string` | `400` | +| `extensions` | 自定义 Tiptap 扩展配置 | `Extensions` | - | +| `imageUpload` | 图片上传配置 | `ImageUploadOptions` | - | + +### Events + +| 事件名 | 说明 | 参数类型 | +| -------- | -------------- | ----------------------- | +| `change` | 内容变化时触发 | `VbenTiptapChangeEvent` | + +#### VbenTiptapChangeEvent + +```ts +interface VbenTiptapChangeEvent { + html: string; // HTML 内容 + json: JSONContent; // JSON 结构内容 + text: string; // 纯文本内容 +} +``` + +### ImageUploadOptions + +图片上传配置项: + +```ts +interface ImageUploadOptions { + /** 允许的文件类型,默认 'image/*' */ + accept?: string; + /** 最大文件大小(字节),默认 5MB */ + maxSize?: number; + /** 上传失败回调,未提供时使用 alert 弹窗提示 */ + onUploadError?: (error: unknown) => void; + /** 上传函数,返回图片 URL */ + upload: ( + file: File, + onProgress?: (percent: number) => void, + ) => Promise; +} +``` + +### VbenTiptapPreview Props + +| 属性名 | 说明 | 类型 | 默认值 | +| ----------- | ------------------ | ------------------ | ------ | +| `content` | 要预览的 HTML 内容 | `string` | `''` | +| `minHeight` | 最小高度 | `number \| string` | `160` | +| `class` | 自定义类名 | `any` | - | + +## 工具栏功能 + +编辑器工具栏提供以下功能: + +### 格式化 + +- **撤销/重做** - 撤销或重做编辑操作 +- **清除格式** - 清除选中文本的所有格式 +- **粗体** - 加粗文本 +- **斜体** - 斜体文本 +- **下划线** - 下划线文本 +- **删除线** - 删除线文本 +- **行内代码** - 行内代码标记 + +### 结构 + +- **标题** - 段落、H1-H4 标题切换 +- **有序列表** - 有序编号列表 +- **无序列表** - 无序符号列表 +- **引用块** - 引用块样式 +- **代码块** - 多行代码块 + +### 链接与图片 + +- **插入链接** - 插入或编辑超链接 +- **移除链接** - 移除选中文本的链接 +- **插入图片** - 通过 URL 插入图片 + +### 样式 + +- **文字颜色** - 设置文字颜色(预设色板) +- **背景颜色** - 设置文字背景高亮颜色 + +### 对齐 + +- **左对齐** - 文本左对齐 +- **居中对齐** - 文本居中对齐 +- **右对齐** - 文本右对齐 + +### 其他 + +- **预览** - 在弹窗中预览编辑内容 + +## 图片上传 + + + +当配置 `imageUpload` 时,工具栏的图片按钮会变为下拉菜单,包含「本地上传」和「URL 插入」两个选项。 + +### 上传方式 + +支持三种图片上传方式: + +1. **文件选择** - 点击工具栏本地上传按钮 +2. **拖拽上传** - 直接拖拽图片到编辑器区域 +3. **粘贴上传** - 粘贴图片到编辑器 + +### 上传进度显示 + +上传过程中会显示: + +- **加载指示器** - 旋转动画指示上传进行中 +- **进度条** - 当上传函数提供 `onProgress` 回调时,显示进度条 + +### 文件校验 + +- `accept` - 指定允许的文件类型(MIME类型) +- `maxSize` - 最大文件大小限制(字节) +- 校验失败时会触发 `onUploadError` 回调或默认 alert 提示 + +::: warning 注意事项 + +- 仅支持单张图片上传,多图拖拽/粘贴时会提示并仅处理第一张 +- 上传中不要保存编辑器内容(`getHTML()`),因为此时图片 URL 为临时 blob URL +- 自定义 `extensions` 时,图片上传功能将不显示(因为可能缺少 uploadImage 命令) + +::: + +## 自定义扩展 + +通过 `extensions` 属性可以传入自定义的 Tiptap 扩展配置: + +```vue + + + +``` + +::: warning 自定义扩展注意事项 + +使用自定义 `extensions` 时: + +- 默认扩展配置将不会生效 +- 图片上传功能不可用(工具栏不显示上传选项) +- 需自行配置所需的编辑器功能 + +::: diff --git a/docs/src/demos/vben-tiptap/basic/index.vue b/docs/src/demos/vben-tiptap/basic/index.vue new file mode 100644 index 000000000..ab301ec7c --- /dev/null +++ b/docs/src/demos/vben-tiptap/basic/index.vue @@ -0,0 +1,20 @@ + + + diff --git a/docs/src/demos/vben-tiptap/image-upload/index.vue b/docs/src/demos/vben-tiptap/image-upload/index.vue new file mode 100644 index 000000000..db2bdf13d --- /dev/null +++ b/docs/src/demos/vben-tiptap/image-upload/index.vue @@ -0,0 +1,45 @@ + + + diff --git a/docs/src/en/components/common-ui/vben-tiptap.md b/docs/src/en/components/common-ui/vben-tiptap.md new file mode 100644 index 000000000..e9dfe8b88 --- /dev/null +++ b/docs/src/en/components/common-ui/vben-tiptap.md @@ -0,0 +1,199 @@ +--- +outline: deep +--- + +# Vben Tiptap Rich Text Editor + +A rich text editor component built on [Tiptap](https://tiptap.dev/), supporting rich text formatting, image insertion, and image upload features. + +> If some details are not obvious from the docs, check the live demos as well. + +::: info Note + +If you feel the current component implementation doesn't meet your needs, you can use native components directly or create your own component. The components provided by the framework are not constraints - use them at your discretion. + +::: + +## Basic Usage + + + +## Component List + +### VbenTiptap + +Main rich text editor component. + +### VbenTiptapPreview + +Read-only preview component for displaying editor content. + +## API + +### Props + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| `modelValue` (v-model) | Editor content (HTML string) | `string` | `''` | +| `editable` | Whether the editor is editable | `boolean` | `true` | +| `toolbar` | Whether to show the toolbar | `boolean` | `true` | +| `previewable` | Whether to show the preview button | `boolean` | `true` | +| `placeholder` | Placeholder text | `string` | - | +| `minHeight` | Minimum height | `number \| string` | `240` | +| `maxHeight` | Maximum height | `number \| string` | `400` | +| `extensions` | Custom Tiptap extensions | `Extensions` | - | +| `imageUpload` | Image upload configuration | `ImageUploadOptions` | - | + +### Events + +| Event | Description | Parameters | +| -------- | ------------------------------ | ----------------------- | +| `change` | Triggered when content changes | `VbenTiptapChangeEvent` | + +#### VbenTiptapChangeEvent + +```ts +interface VbenTiptapChangeEvent { + html: string; // HTML content + json: JSONContent; // JSON structure + text: string; // Plain text content +} +``` + +### ImageUploadOptions + +Image upload configuration: + +```ts +interface ImageUploadOptions { + /** Allowed file types, default 'image/*' */ + accept?: string; + /** Max file size in bytes, default 5MB */ + maxSize?: number; + /** Upload error callback, uses alert if not provided */ + onUploadError?: (error: unknown) => void; + /** Upload function, returns image URL */ + upload: ( + file: File, + onProgress?: (percent: number) => void, + ) => Promise; +} +``` + +### VbenTiptapPreview Props + +| Property | Description | Type | Default | +| ----------- | ----------------------- | ------------------ | ------- | +| `content` | HTML content to preview | `string` | `''` | +| `minHeight` | Minimum height | `number \| string` | `160` | +| `class` | Custom class name | `any` | - | + +## Toolbar Features + +The editor toolbar provides the following features: + +### Formatting + +- **Undo/Redo** - Undo or redo editing operations +- **Clear Formatting** - Remove all formatting from selected text +- **Bold** - Bold text +- **Italic** - Italic text +- **Underline** - Underline text +- **Strikethrough** - Strikethrough text +- **Inline Code** - Inline code mark + +### Structure + +- **Headings** - Paragraph, H1-H4 heading switching +- **Ordered List** - Numbered list +- **Bullet List** - Bulleted list +- **Blockquote** - Quote block style +- **Code Block** - Multi-line code block + +### Links & Images + +- **Insert Link** - Insert or edit hyperlinks +- **Remove Link** - Remove link from selected text +- **Insert Image** - Insert image via URL + +### Style + +- **Text Color** - Set text color (preset palette) +- **Highlight Color** - Set text background highlight color + +### Alignment + +- **Align Left** - Left align text +- **Align Center** - Center align text +- **Align Right** - Right align text + +### Other + +- **Preview** - Preview content in a modal + +## Image Upload + + + +When `imageUpload` is configured, the toolbar image button becomes a dropdown menu with "Upload" and "URL" options. + +### Upload Methods + +Three image upload methods are supported: + +1. **File Selection** - Click the upload button in toolbar +2. **Drag & Drop** - Drag images directly into the editor +3. **Paste** - Paste images into the editor + +### Upload Progress Display + +During upload: + +- **Loading Indicator** - Spinner animation indicating upload in progress +- **Progress Bar** - Shows progress bar when upload function provides `onProgress` callback + +### File Validation + +- `accept` - Specify allowed file types (MIME types) +- `maxSize` - Maximum file size limit (bytes) +- Validation failure triggers `onUploadError` callback or default alert + +::: warning Important Notes + +- Only single image upload is supported; multi-image drag/paste will show a prompt and process only the first image +- Do not save editor content (`getHTML()`) during upload as image URLs are temporary blob URLs +- When using custom `extensions`, the image upload feature will not be available (toolbar won't show upload option) + +::: + +## Custom Extensions + +Pass custom Tiptap extension configurations via the `extensions` property: + +```vue + + + +``` + +::: warning Custom Extensions Note + +When using custom `extensions`: + +- Default extension configuration will not take effect +- Image upload feature is not available (toolbar won't show upload option) +- You need to configure all required editor features yourself + +::: diff --git a/packages/effects/plugins/src/tiptap/style.css b/packages/effects/plugins/src/tiptap/style.css index 2d8052c1d..3f5f6cc8f 100644 --- a/packages/effects/plugins/src/tiptap/style.css +++ b/packages/effects/plugins/src/tiptap/style.css @@ -1,9 +1,5 @@ @reference "@vben/tailwind-config/theme"; -.vben-tiptap-content > * + * { - @apply mt-3; -} - .vben-tiptap-content h1 { @apply text-2xl font-bold leading-[1.4]; } @@ -50,8 +46,6 @@ .vben-tiptap-content img, .vben-tiptap-content .vben-tiptap__image { - @apply my-4 block h-auto rounded-2xl border border-border; - max-width: min(100%, 640px); } diff --git a/packages/effects/plugins/src/tiptap/tiptap.vue b/packages/effects/plugins/src/tiptap/tiptap.vue index 6dd99d3d8..2c4da366b 100644 --- a/packages/effects/plugins/src/tiptap/tiptap.vue +++ b/packages/effects/plugins/src/tiptap/tiptap.vue @@ -28,6 +28,7 @@ const props = withDefaults(defineProps(), { extensions: undefined, imageUpload: undefined, minHeight: 240, + maxHeight: 400, placeholder: $t('ui.tiptap.placeholder'), previewable: true, toolbar: true, @@ -41,9 +42,14 @@ const contentMinHeight = computed(() => ? `${props.minHeight}px` : props.minHeight, ); +const contentMaxHeight = computed(() => + typeof props.maxHeight === 'number' + ? `${props.maxHeight}px` + : props.maxHeight, +); const tiptapContentClass = cn( 'vben-tiptap-content vben-tiptap__content', - 'text-foreground min-h-(--vben-tiptap-min-height) leading-7 outline-none', + 'text-foreground max-h-(--vben-tiptap-max-height) min-h-(--vben-tiptap-min-height) overflow-auto leading-7 outline-none', ); const blobUrlTracker = new Set(); const editor = useEditor({ @@ -154,7 +160,10 @@ onBeforeUnmount(() => {