You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求类似Megadraft/TipTap的无工具栏React文本编辑器

定制符合需求的React文本编辑器(基于TipTap)

TipTap本身具备极强的可扩展性,完全可以定制出你需要的无工具栏、左侧带加号插入面板、支持选中文本修改样式的编辑器,以下是具体实现方案:

1. 基础依赖安装

首先安装TipTap核心包及所需扩展:

npm install @tiptap/react @tiptap/extension-image @tiptap/extension-video @tiptap/extension-table @tiptap/extension-list-item @tiptap/extension-ordered-list @tiptap/extension-bullet-list @tiptap/extension-document @tiptap/extension-text

2. 核心实现代码

import React, { useState } from 'react';
import { EditorProvider, useEditor, EditorContent } from '@tiptap/react';
import Document from '@tiptap/extension-document';
import Text from '@tiptap/extension-text';
import Image from '@tiptap/extension-image';
import Video from '@tiptap/extension-video';
import Table from '@tiptap/extension-table';
import TableRow from '@tiptap/extension-table-row';
import TableCell from '@tiptap/extension-table-cell';
import BulletList from '@tiptap/extension-bullet-list';
import OrderedList from '@tiptap/extension-ordered-list';
import ListItem from '@tiptap/extension-list-item';

const EditorComponent = () => {
  const [showInsertMenu, setShowInsertMenu] = useState(false);
  
  const editor = useEditor({
    extensions: [
      Document,
      Text,
      Image.configure({ inline: false }),
      Video.configure({ inline: false }),
      Table.configure({ resizable: true }),
      TableRow,
      TableCell,
      BulletList,
      OrderedList,
      ListItem,
    ],
    content: '<p>开始编辑...</p>',
  });

  // 插入内容逻辑
  const insertContent = (type) => {
    if (!editor) return;
    
    switch(type) {
      case 'image':
        const imageUrl = prompt('输入图片URL:');
        imageUrl && editor.chain().focus().insertContent(`<img src="${imageUrl}" />`).run();
        break;
      case 'video':
        const videoUrl = prompt('输入视频URL:');
        videoUrl && editor.chain().focus().insertContent(`<video src="${videoUrl}" controls></video>`).run();
        break;
      case 'table':
        editor.chain().focus().insertTable({ rows: 3, cols: 3 }).run();
        break;
      default:
        break;
    }
    setShowInsertMenu(false);
  };

  // 切换列表样式
  const toggleList = (type) => {
    if (!editor) return;
    type === 'bullet' 
      ? editor.chain().focus().toggleBulletList().run()
      : editor.chain().focus().toggleOrderedList().run();
  };

  return (
    <div style={{ display: 'flex', position: 'relative', width: '100%', height: '600px', border: '1px solid #eee' }}>
      {/* 左侧加号按钮及插入菜单 */}
      <div style={{ position: 'absolute', left: '10px', top: '10px', zIndex: 10 }}>
        <button 
          onClick={() => setShowInsertMenu(!showInsertMenu)}
          style={{ width: '40px', height: '40px', borderRadius: '50%', border: 'none', backgroundColor: '#007bff', color: 'white', fontSize: '24px', cursor: 'pointer' }}
        >
          +
        </button>
        {showInsertMenu && (
          <div style={{ marginTop: '8px', backgroundColor: 'white', border: '1px solid #ddd', borderRadius: '4px', padding: '8px', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}>
            <button onClick={() => insertContent('image')} style={{ display: 'block', width: '100%', padding: '8px', border: 'none', backgroundColor: 'transparent', cursor: 'pointer', textAlign: 'left' }}>插入图片</button>
            <button onClick={() => insertContent('video')} style={{ display: 'block', width: '100%', padding: '8px', border: 'none', backgroundColor: 'transparent', cursor: 'pointer', textAlign: 'left' }}>插入视频</button>
            <button onClick={() => insertContent('table')} style={{ display: 'block', width: '100%', padding: '8px', border: 'none', backgroundColor: 'transparent', cursor: 'pointer', textAlign: 'left' }}>插入表格</button>
          </div>
        )}
      </div>

      {/* 编辑器内容区域 */}
      <EditorContent editor={editor} style={{ flex: 1, padding: '20px', paddingLeft: '60px' }} />

      {/* 选中文本后的列表切换快捷按钮 */}
      {editor && editor.isActive('listItem') && (
        <div style={{ position: 'absolute', right: '10px', top: '10px', zIndex: 10 }}>
          <button onClick={() => toggleList('bullet')} style={{ marginRight: '8px', padding: '6px 12px', border: '1px solid #ddd', borderRadius: '4px', cursor: 'pointer' }}>项目符号</button>
          <button onClick={() => toggleList('ordered')} style={{ padding: '6px 12px', border: '1px solid #ddd', borderRadius: '4px', cursor: 'pointer' }}>编号列表</button>
        </div>
      )}
    </div>
  );
};

const App = () => {
  return (
    <EditorProvider>
      <EditorComponent />
    </EditorProvider>
  );
};

export default App;

3. 关键功能说明

  • 左侧加号插入面板:点击加号弹出菜单,支持插入图片、视频、表格,可按需扩展更多内容类型(如代码块、引用块)。
  • 选中文本样式修改:示例实现了列表样式切换,若需添加加粗、斜体等样式,只需引入对应TipTap扩展(如Bold、Italic)并调用toggleBold()这类方法即可。
  • 无工具栏设计:完全摒弃传统顶部工具栏,所有操作通过左侧插入按钮和选中文本后的快捷按钮完成。

4. 扩展优化建议

  • 可结合React UI组件库(如Ant Design、Material UI)将插入菜单改为更美观的下拉面板。
  • 选中文本的样式修改可改为右键上下文菜单,通过监听编辑器selectionUpdate事件判断选中状态后触发。
  • 图片/视频插入可替换为文件上传逻辑,对接后端接口实现本地文件上传。

内容的提问来源于stack exchange,提问作者op po

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 05:23:13