neweditor
v1.3.9
Published
Rich text editor for Testfox
Downloads
829
Readme
TestFox RichText Editor
基于 TipTap 的富文本编辑器,专为 TestFox 项目设计,支持完整的主题系统和亮色/暗色模式切换。
特性
- ✅ 完整的富文本编辑功能
- ✅ 支持 40+ 种编程语言语法高亮
- ✅ Slash Command(斜杠命令)快捷操作
- ✅ 图片粘贴上传(Ctrl+V)
- ✅ 文件拖拽上传(图片、视频、音频)
- ✅ 自动上传到服务器(支持自定义上传处理器)
- ✅ 拖拽排序功能
- ✅ 表格编辑(合并单元格、调整列宽等)
- ✅ 多语言支持(中文/英文)
- ✅ 主题系统(亮色/暗色模式)
- ✅ 响应式设计
安装
npm install neweditor
# 或
pnpm install neweditor
# 或
yarn add neweditor使用
基础使用
<template>
<RichTextEditor :editor="editor" locale="zh-CN" />
</template>
<script setup>
import { useEditor, RichTextEditor, allExtensions } from 'neweditor'
import 'neweditor/dist/style.css'
const editor = useEditor({
extensions: allExtensions,
content: '<p>Hello World!</p>',
})
</script>完整示例
<template>
<div class="editor-wrapper">
<RichTextEditor
:editor="editor"
locale="zh-CN"
class="my-editor"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useEditor, RichTextEditor, allExtensions } from 'neweditor'
import 'neweditor/dist/style.css'
const content = ref('<p>初始内容</p>')
const editor = useEditor({
extensions: allExtensions,
content: content.value,
editable: true,
autofocus: false,
editorProps: {
attributes: {
'data-placeholder': '请输入内容...',
},
},
onUpdate: ({ editor }) => {
content.value = editor.getHTML()
},
onFocus: ({ editor }) => {
console.log('编辑器获得焦点')
},
onBlur: ({ editor }) => {
console.log('编辑器失去焦点')
},
})
</script>
<style>
.editor-wrapper {
border: 1px solid #d9d9d9;
border-radius: 4px;
}
</style>带文件上传功能的示例
<template>
<div class="editor-wrapper">
<RichTextEditor
:editor="editor"
locale="zh-CN"
class="my-editor"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useEditor, RichTextEditor, allExtensions } from 'testfox-richtext-editor'
import 'testfox-richtext-editor/dist/style.css'
const content = ref('<p>初始内容</p>')
// 自定义文件上传处理器
const handleFileUpload = async (file) => {
try {
// 1. 验证文件大小(例如限制 100MB)
const maxSize = 100 * 1024 * 1024
if (file.size > maxSize) {
console.error('文件大小不能超过100MB')
return null
}
// 2. 创建 FormData
const formData = new FormData()
formData.append('file', file)
// 3. 调用你的上传 API
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
})
const result = await response.json()
// 4. 返回文件 URL(成功)或 null(失败)
if (result.success) {
return result.fileUrl // 返回文件的访问 URL
} else {
console.error('上传失败:', result.message)
return null
}
} catch (error) {
console.error('上传异常:', error)
return null
}
}
// 配置扩展并传递上传处理器
const configuredExtensions = allExtensions.map(ext => {
// 为图片、视频、音频扩展配置上传处理器
if (ext.name === 'image' || ext.name === 'video' || ext.name === 'audio') {
return ext.configure({
uploadHandler: handleFileUpload
})
}
return ext
})
const editor = useEditor({
extensions: configuredExtensions, // 使用配置好的扩展
content: content.value,
editable: true,
onUpdate: ({ editor }) => {
content.value = editor.getHTML()
},
})
</script>
<style>
.editor-wrapper {
border: 1px solid #d9d9d9;
border-radius: 4px;
}
</style>API
useEditor(options)
创建编辑器实例的 Hook。
参数:
interface EditorOptions {
extensions: Extension[] // 扩展列表
content?: string // 初始内容(HTML)
editable?: boolean // 是否可编辑
autofocus?: boolean // 是否自动聚焦
editorProps?: EditorProps // 编辑器属性
onUpdate?: (props) => void // 内容更新回调
onFocus?: (props) => void // 获得焦点回调
onBlur?: (props) => void // 失去焦点回调
onCreate?: (props) => void // 创建完成回调
}返回值:
返回一个响应式的编辑器实例。
文件上传配置
uploadHandler
文件上传处理器函数,用于处理图片、视频、音频的上传。
类型签名:
type UploadHandler = (file: File) => Promise<string | null>参数:
file: File 对象,包含要上传的文件
返回值:
- 成功:返回文件的访问 URL(字符串)
- 失败:返回
null
配置方式:
// 为需要上传功能的扩展配置 uploadHandler
const configuredExtensions = allExtensions.map(ext => {
if (ext.name === 'image' || ext.name === 'video' || ext.name === 'audio') {
return ext.configure({
uploadHandler: async (file) => {
// 你的上传逻辑
const url = await uploadToServer(file)
return url // 返回 URL 或 null
}
})
}
return ext
})工作流程:
- 用户粘贴图片或拖拽文件到编辑器
- 编辑器创建临时预览(Blob URL)
- 立即显示预览,用户无需等待
- 后台调用
uploadHandler上传文件 - 上传成功后,自动替换为真实 URL
- 上传失败则删除临时预览
示例实现:
const handleFileUpload = async (file) => {
try {
// 验证文件大小
const maxSize = 100 * 1024 * 1024 // 100MB
if (file.size > maxSize) {
console.error('文件过大')
return null
}
// 上传到服务器
const formData = new FormData()
formData.append('file', file)
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
})
const result = await response.json()
// 返回文件 URL
return result.success ? result.fileUrl : null
} catch (error) {
console.error('上传失败:', error)
return null
}
}RichTextEditor 组件
编辑器 UI 组件。
Props:
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| editor | Editor | - | 编辑器实例(必需) |
| locale | String | 'zh-CN' | 语言设置(zh-CN/en) |
allExtensions
包含所有内置扩展的数组,包括:
- 文本格式化(粗体、斜体、下划线、删除线等)
- 标题(H1-H6)
- 列表(有序、无序、任务列表)
- 代码(行内代码、代码块)
- 表格
- 图片、视频、音频
- 链接
- 引用块
- 分栏布局
- 折叠面板
- Slash Command
- 拖拽排序
- 搜索和替换
- 等等...
编辑器功能
文本格式化
- 粗体、斜体、下划线、删除线、上标、下标
- 标题(H1-H6)
- 文本颜色和背景色
- 字体大小
- 文本对齐
- 清除格式
- 格式刷
列表和引用
- 无序列表
- 有序列表
- 任务列表
- 引用块
- 缩进
代码
- 行内代码
- 代码块(支持 40+ 种语言语法高亮)
媒体内容
- 图片(支持粘贴、拖拽上传、调整大小)
- 视频(支持拖拽上传)
- 音频(支持拖拽上传)
- iframe
表格
- 创建表格
- 添加/删除行和列
- 合并/拆分单元格
- 调整列宽
- 表格拖拽排序
快捷功能
- Slash Command(输入
/或、触发) - 拖拽排序
- 搜索和替换
- 撤销/重做
- 字符统计
快捷键
| 快捷键 | 功能 |
|--------|------|
| Ctrl/Cmd + B | 粗体 |
| Ctrl/Cmd + I | 斜体 |
| Ctrl/Cmd + U | 下划线 |
| Ctrl/Cmd + Shift + X | 删除线 |
| Ctrl/Cmd + E | 行内代码 |
| Ctrl/Cmd + Z | 撤销 |
| Ctrl/Cmd + Shift + Z | 重做 |
| Ctrl/Cmd + K | 插入链接 |
| Ctrl/Cmd + V | 粘贴图片 |
| / 或 、 | Slash Command |
| Tab | 增加缩进 |
| Shift + Tab | 减少缩进 |
主题系统
编辑器支持亮色和暗色两种主题模式,通过 CSS 变量控制。
主题变量
/* 亮色主题 */
.theme-light .testfox-rich-text-editor {
--editor-primary-color: #9373ee;
--editor-text-color: rgba(0, 0, 0, 0.85);
--editor-background-color: #ffffff;
--editor-border-color: #f0f0f0;
/* ... 更多变量 */
}
/* 暗色主题 */
.theme-dark .testfox-rich-text-editor {
--editor-primary-color: #9373ee;
--editor-text-color: rgba(255, 255, 255, 0.85);
--editor-background-color: #1f1f1f;
--editor-border-color: #303030;
/* ... 更多变量 */
}自定义主题
可以通过覆盖 CSS 变量来自定义主题:
.my-editor {
--editor-primary-color: #your-color;
--editor-background-color: #your-bg;
}开发
安装依赖
npm install开发模式
npm run dev构建
npm run build类型检查
npm run typecheck扩展开发
如需为编辑器添加新功能,可以创建自定义扩展。详细的扩展开发指南请参考:
文件上传功能
功能特性
编辑器支持以下文件上传方式:
- ✅ 图片粘贴上传:按
Ctrl+V粘贴图片,自动上传 - ✅ 图片拖拽上传:拖拽图片文件到编辑器
- ✅ 视频拖拽上传:拖拽视频文件到编辑器
- ✅ 音频拖拽上传:拖拽音频文件到编辑器
- ✅ 即时预览:上传前立即显示预览
- ✅ 自动替换:上传成功后自动替换为真实 URL
- ✅ 失败处理:上传失败自动删除预览
支持的文件类型
| 类型 | 扩展名 | 操作方式 | |------|--------|---------| | 图片 | jpg, jpeg, png, gif, webp, svg, bmp | 粘贴、拖拽 | | 视频 | mp4, webm, ogg | 拖拽 | | 音频 | mp3, wav, ogg | 拖拽 |
实现步骤
1. 定义上传处理器
const handleFileUpload = async (file) => {
try {
// 验证文件大小
const maxSize = 100 * 1024 * 1024 // 100MB
if (file.size > maxSize) {
console.error('文件大小不能超过100MB')
return null
}
// 创建 FormData
const formData = new FormData()
formData.append('file', file)
// 调用上传 API
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
})
const result = await response.json()
// 返回文件 URL(成功)或 null(失败)
if (result.success) {
return result.fileUrl
} else {
console.error('上传失败:', result.message)
return null
}
} catch (error) {
console.error('上传异常:', error)
return null
}
}2. 配置扩展
import { allExtensions } from 'testfox-richtext-editor'
// 为需要上传功能的扩展配置 uploadHandler
const configuredExtensions = allExtensions.map(ext => {
if (ext.name === 'image' || ext.name === 'video' || ext.name === 'audio') {
return ext.configure({
uploadHandler: handleFileUpload
})
}
return ext
})3. 创建编辑器
const editor = useEditor({
extensions: configuredExtensions, // 使用配置好的扩展
content: '<p>Hello World!</p>',
})完整示例
<template>
<RichTextEditor :editor="editor" locale="zh-CN" />
</template>
<script setup>
import { useEditor, RichTextEditor, allExtensions } from 'testfox-richtext-editor'
import 'testfox-richtext-editor/dist/style.css'
// 上传处理器
const handleFileUpload = async (file) => {
try {
const maxSize = 100 * 1024 * 1024
if (file.size > maxSize) {
alert('文件大小不能超过100MB')
return null
}
const formData = new FormData()
formData.append('file', file)
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
})
const result = await response.json()
return result.success ? result.fileUrl : null
} catch (error) {
console.error('上传失败:', error)
return null
}
}
// 配置扩展
const configuredExtensions = allExtensions.map(ext => {
if (ext.name === 'image' || ext.name === 'video' || ext.name === 'audio') {
return ext.configure({ uploadHandler: handleFileUpload })
}
return ext
})
// 创建编辑器
const editor = useEditor({
extensions: configuredExtensions,
content: '<p>试试粘贴图片或拖拽文件到这里...</p>',
})
</script>上传流程
1. 用户粘贴图片或拖拽文件
↓
2. 创建临时预览(Blob URL)
↓
3. 立即显示预览(用户无需等待)
↓
4. 后台调用 uploadHandler 上传
↓
5. 上传成功
├─ 替换为真实 URL
└─ 释放临时 Blob URL
↓
6. 上传失败
├─ 删除临时预览
└─ 释放临时 Blob URL注意事项
返回值约定
- 成功:必须返回文件的完整访问 URL(字符串)
- 失败:必须返回
null
文件大小限制
- 建议在前端验证文件大小
- 避免上传超大文件导致失败
错误处理
- 捕获所有异常并返回
null - 提供友好的错误提示
- 捕获所有异常并返回
URL 格式
- 返回的 URL 必须是可访问的完整 URL
- 例如:
https://example.com/uploads/image.jpg
异步处理
- uploadHandler 必须是异步函数
- 使用
async/await或返回 Promise
常见问题
Q: 粘贴后图片消失了?
A: 检查 uploadHandler 是否返回了正确的 URL。如果返回 null,图片会被删除。
Q: 如何禁用上传功能?
A: 不配置 uploadHandler 即可。图片会保持为 Blob URL(仅本地预览)。
Q: 支持哪些文件类型?
A: 图片(粘贴+拖拽)、视频(拖拽)、音频(拖拽)。可以通过扩展支持更多类型。
Q: 如何自定义文件大小限制?
A: 在 uploadHandler 中添加验证逻辑:
const maxSize = 50 * 1024 * 1024 // 50MB
if (file.size > maxSize) {
alert('文件过大')
return null
}Q: 如何添加上传进度提示?
A: 在 uploadHandler 中使用 UI 库显示进度:
const handleFileUpload = async (file) => {
// 显示加载提示
showLoading('正在上传...')
try {
const url = await uploadToServer(file)
hideLoading()
return url
} catch (error) {
hideLoading()
showError('上传失败')
return null
}
}浏览器兼容性
- Chrome/Edge: ✅ 完全支持
- Firefox: ✅ 完全支持
- Safari: ✅ 完全支持
- IE: ❌ 不支持
相关链接
许可证
GPL-3.0
贡献
欢迎提交 Issue 和 Pull Request!
