OpenTiny:快速搭建实时协作文本编辑器:基于 TinyEditor 的实践指南

OpenTiny:快速搭建实时协作文本编辑器:基于 TinyEditor 的实践指南

大家好,我是你们的老朋友木斯佳,今天给大家分享一篇TinyEditor组件的使用案例,TinyEditor 是一个框架无关的富文本编辑器,既可以在原生 JavaScript 项目中使用,也可以在 Vue、React 等前端框架中使用。在阅读了曹里林的原创文章后,我决定按照教程亲自实践一遍,并将过程中遇到的问题和心得分享给大家。

简介

实践背景

原教程简介:

本篇文章带来的是如何使用 TinyEditor 最新的协同编辑模块快速部署多人实时协作编辑。

前端集成实战

环境准备与安装

按照教程,我首先创建了一个新的 Vue 3 项目,然后开始安装 TinyEditor:

bash 复制代码
# 创建新项目
npm create vue@latest my-collab-editor

# 进入项目并安装 TinyEditor
cd my-collab-editor
pnpm add @opentiny/fluent-editor

基础编辑器集成

App.vue 中,我按照教程添加了以下代码:

vue 复制代码
<template>
  <div id="app">
    <div id="editor-container">
      <div id="editor">
        <p>让我们开始协同编辑吧!</p>
      </div>
    </div>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import FluentEditor from '@opentiny/fluent-editor'
import '@opentiny/fluent-editor/style.css'

onMounted(() => {
  const editor = new FluentEditor('#editor', {
    theme: 'snow',
    placeholder: '输入内容...',
    modules: {
      toolbar: [
        ['bold', 'italic', 'underline', 'strike'],
        [{ 'header': [1, 2, 3, false] }],
        [{ 'list': 'ordered'}, { 'list': 'bullet' }],
        ['link', 'image'],
        ['clean']
      ]
    }
  })
})
</script>

<style scoped>
#editor-container {
  max-width: 1200px;
  margin: 40px auto;
  padding: 20px;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}

#editor {
  height: 500px;
}
</style>

实践心得: 样式引入很重要!如果没有正确导入 CSS,编辑器的工具栏和内容区域样式会完全错乱。

协同编辑功能添加

这是最核心的部分。按照教程,我需要安装额外的依赖:

bash 复制代码
npm install quill-cursors y-protocols y-quill yjs y-indexeddb y-websocket

然后修改编辑器初始化代码:

vue 复制代码
<script setup>
import { onMounted } from 'vue'
import FluentEditor, { CollaborationModule } from '@opentiny/fluent-editor'
import '@opentiny/fluent-editor/style.css'

// 注册协同编辑模块
FluentEditor.register('modules/collaborative-editing', CollaborationModule, true)

onMounted(() => {
  const editor = new FluentEditor('#editor', {
    theme: 'snow',
    placeholder: '等待其他用户加入...',
    modules: {
      toolbar: [
        ['bold', 'italic', 'underline', 'strike'],
        [{ 'header': [1, 2, 3, false] }],
        [{ 'list': 'ordered'}, { 'list': 'bullet' }],
        ['link', 'image'],
        ['clean']
      ],
      'collaborative-editing': {
        provider: {
          type: 'websocket',
          options: {
            // 使用官方演示服务器进行测试
            serverUrl: 'wss://demos.yjs.dev/ws',
            // 房间名需要唯一,我用时间戳确保唯一性
            roomName: `tiny-editor-test-${Date.now()}`,
          },
        },
      },
    },
  })
  
  // 为了方便测试,在控制台输出房间信息
  console.log('编辑器已初始化,房间号:', editor.modules['collaborative-editing'].provider.room)
})
</script>

遇到的坑: 初始化时我遇到了一个错误:"CollaborationModule is not defined"。经过排查发现,需要从正确的路径导入:

javascript 复制代码
// 正确的导入方式
import FluentEditor from '@opentiny/fluent-editor'
import { CollaborationModule } from '@opentiny/fluent-editor/dist/collaboration'

配置用户感知(Awareness)

Awareness 模块负责同步用户的在线状态、光标位置和选区。通过配置,你可以自定义用户的显示信息。Awareness 实现用户在线状态、光标位置等信息的实时同步。每个用户的在线状态、名称、颜色、光标位置等会自动广播给其他协作者,实现多人编辑时的身份和操作可视化。

为了让协作者能看到彼此,我添加了 Awareness 配置:

javascript 复制代码
const editor = new FluentEditor('#editor', {
  // ... 其他配置
  modules: {
    // ... 工具栏配置
    'collaborative-editing': {
      provider: {
        type: 'websocket',
        options: {
          serverUrl: 'wss://demos.yjs.dev/ws',
          roomName: `collab-room-${Math.random().toString(36).substr(2, 9)}`,
        },
      },
      awareness: {
        state: {
          // 为测试方便,使用固定用户名
          name: `用户-${Math.floor(Math.random() * 1000)}`,
          color: `#${Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0')}`,
          avatar: `https://i.pravatar.cc/50?img=${Math.floor(Math.random() * 70)}`
        },
        timeout: 30000, // 30秒超时
      },
    },
  },
})

自定义光标样式

按照教程,我可以自定义光标的外观:

javascript 复制代码
const CURSOR_CLASSES = {
  SELECTION_CLASS: 'ql-cursor-selections',
  CARET_CONTAINER_CLASS: 'ql-cursor-caret-container',
  CARET_CLASS: 'ql-cursor-caret',
  FLAG_CLASS: 'ql-cursor-flag',
  NAME_CLASS: 'ql-cursor-name',
}

// 在协同编辑配置中添加
'collaborative-editing': {
  // ... provider 配置
  cursors: {
    template: `
      <span class="${CURSOR_CLASSES.SELECTION_CLASS}"></span>
      <span class="${CURSOR_CLASSES.CARET_CONTAINER_CLASS}">
        <span class="${CURSOR_CLASSES.CARET_CLASS}"></span>
      </span>
      <div class="${CURSOR_CLASSES.FLAG_CLASS}">
        <small class="${CURSOR_CLASSES.NAME_CLASS}"></small>
      </div>
    `,
    hideDelayMs: 500,  // 稍微延长显示时间
    hideSpeedMs: 200,
    transformOnTextChange: true,
  },
}

后端部署实操

Docker 环境准备

我的开发环境是 Windows 11,已经安装了 Docker Desktop。按照教程,我在项目根目录创建了 docker-compose.yml 文件:

yaml 复制代码
version: '3.8'

services:
  # 使用官方 MongoDB 镜像
  mongodb:
    image: mongo:6.0
    container_name: tinyeditor-mongodb
    restart: unless-stopped
    ports:
      - "27017:27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: admin123
    volumes:
      - mongodb_data:/data/db
      - ./init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro
    
  # 协同编辑后端服务
  websocket-server:
    image: yinlin124/collaborative-editor-backend:latest
    container_name: tinyeditor-websocket
    restart: unless-stopped
    ports:
      - "1234:1234"
    environment:
      HOST: 0.0.0.0
      PORT: 1234
      MONGODB_URL: mongodb://admin:admin123@mongodb:27017/tinyeditor?authSource=admin
      MONGODB_DB: tinyeditor
      MONGODB_COLLECTION: documents
    depends_on:
      - mongodb
    # 添加健康检查
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:1234/health"]
      interval: 30s
      timeout: 10s
      retries: 3

volumes:
  mongodb_data:

补充说明: 我添加了一个 MongoDB 初始化脚本 init-mongo.js

javascript 复制代码
// init-mongo.js
db = db.getSiblingDB('tinyeditor');

db.createCollection('documents');
db.documents.createIndex({ room: 1 }, { unique: true });
db.documents.createIndex({ updatedAt: 1 }, { expireAfterSeconds: 604800 }); // 7天过期

print('MongoDB initialized for TinyEditor');

环境变量配置

创建 .env 文件用于灵活配置:

env 复制代码
# .env
COMPOSE_PROJECT_NAME=tinyeditor-collab

# MongoDB 配置
MONGO_PORT=27017
MONGO_USER=admin
MONGO_PASSWORD=admin123
MONGO_DATABASE=tinyeditor

# WebSocket 服务配置
WS_PORT=1234
WS_HOST=0.0.0.0

# 数据保留时间(秒)
DATA_TTL=604800

启动服务

在终端中运行:

bash 复制代码
# 启动所有服务
docker-compose up -d

# 查看服务状态
docker-compose ps

# 查看日志
docker-compose logs -f websocket-server

实践发现: 第一次启动时,需要下载镜像,可能会比较慢。可以使用阿里云镜像加速。

测试与验证

多窗口测试

为了测试协同编辑功能,我同时打开了三个浏览器窗口:

  1. Chrome 正常窗口 - 作为用户A
  2. Chrome 无痕窗口 - 作为用户B
  3. Firefox 浏览器 - 作为用户C

每个窗口都访问相同的本地开发地址,我可以看到:

  • 每个用户都有不同颜色的光标
  • 输入内容实时同步到所有窗口
  • 用户离开时,光标会在一段时间后消失
  • 可以看到其他用户的名称和选择区域

功能测试清单

测试项目 预期结果 实际结果 备注
文本输入同步 实时同步 ✅ 通过 延迟在100ms内
格式编辑同步 同步格式 ✅ 通过 加粗、斜体等
多人同时编辑 无冲突 ✅ 通过 Yjs处理得很好
用户状态感知 显示在线 ✅ 通过 颜色和名称正确
离线重连 数据恢复 ✅ 通过 自动同步历史
大文档性能 流畅编辑 ⚠️ 中等 超过1000行略有延迟

进阶配置尝试

WebRTC 连接方式

按照教程,我尝试了 WebRTC 连接方式:

bash 复制代码
# 安装 WebRTC 依赖
npm install y-webrtc
javascript 复制代码
const editor = new FluentEditor('#editor', {
  modules: {
    'collaborative-editing': {
      provider: {
        type: 'webrtc',
        options: {
          roomName: 'tiny-editor-webrtc-test',
          signaling: [
            'wss://signaling.yjs.dev',
            'wss://y-webrtc-signaling-eu.herokuapp.com',
            'wss://y-webrtc-signaling-us.herokuapp.com'
          ],
          password: null,
          // 高级配置
          maxConns: 20 + Math.floor(Math.random() * 15), // 随机连接数
          filterBcConns: true,
          peerOpts: {}
        },
      },
    },
  },
})

使用体验: WebRTC 在局域网内延迟更低,但需要更多配置,且对网络环境要求更高。

自定义事件处理

我添加了一些事件监听来增强用户体验:

javascript 复制代码
onMounted(() => {
  const editor = new FluentEditor('#editor', {
    // ... 配置
  })
  
  // 获取协同编辑模块实例
  const collaboration = editor.modules['collaborative-editing']
  
  // 监听用户状态变化
  collaboration.awareness.on('change', ({ added, updated, removed }) => {
    console.log('用户状态变化:', { added, updated, removed })
    
    // 显示在线用户数
    const states = collaboration.awareness.getStates()
    const userCount = Array.from(states.keys()).length
    console.log(`当前在线用户: ${userCount}人`)
  })
  
  // 监听同步状态
  collaboration.provider.on('sync', (isSynced) => {
    console.log('同步状态:', isSynced ? '已同步' : '同步中...')
  })
  
  // 监听连接状态
  collaboration.provider.on('status', ({ status }) => {
    console.log('连接状态:', status)
  })
})

部署到生产环境

前端部署配置

对于生产环境,我做了以下调整:

javascript 复制代码
// 生产环境配置
const getServerUrl = () => {
  if (process.env.NODE_ENV === 'development') {
    return 'ws://localhost:1234'
  }
  // 生产环境使用安全的 WebSocket
  return window.location.protocol === 'https:' 
    ? `wss://${window.location.host}/collab`
    : `ws://${window.location.host}/collab`
}

const editor = new FluentEditor('#editor', {
  modules: {
    'collaborative-editing': {
      provider: {
        type: 'websocket',
        options: {
          serverUrl: getServerUrl(),
          roomName: window.location.pathname.split('/').pop() || 'default-room',
          // 生产环境优化
          connect: true,
          resyncInterval: 5000,
          maxBackoffTime: 10000,
          disableBc: true
        },
      },
    },
  },
})

Nginx 反向代理配置

对于生产部署,我配置了 Nginx 来处理 WebSocket:

nginx 复制代码
# nginx.conf 部分配置
server {
    listen 443 ssl http2;
    server_name your-domain.com;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    location / {
        root /var/www/tinyeditor;
        index index.html;
        try_files $uri $uri/ /index.html;
    }
    
    location /collab {
        proxy_pass http://localhost:1234;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # 超时设置
        proxy_connect_timeout 7d;
        proxy_send_timeout 7d;
        proxy_read_timeout 7d;
    }
}

遇到的问题与解决方案

问题1:跨域访问

现象: 前端访问本地 Docker 服务时出现 CORS 错误。
解决: 在 docker-compose.yml 中添加 CORS 配置:

yaml 复制代码
environment:
  - CORS_ORIGIN=http://localhost:3000,http://localhost:5173

问题2:连接不稳定

现象: WebSocket 连接频繁断开重连。
解决: 调整重连策略:

javascript 复制代码
provider: {
  type: 'websocket',
  options: {
    // ... 其他配置
    // 优化重连策略
    maxRetries: 10,
    retryDelay: attempt => Math.min(1000 * 2 ** attempt, 30000),
    // 心跳检测
    params: { heartbeat: 30000 }
  }
}

问题3:移动端兼容性

现象: 在 iOS Safari 上协同编辑异常。
解决: 添加移动端优化:

css 复制代码
/* 移动端优化 */
@media (max-width: 768px) {
  #editor {
    height: 300px;
  }
  
  .ql-toolbar {
    flex-wrap: wrap;
  }
  
  .ql-toolbar .ql-formats {
    margin: 2px;
  }
}

总结与建议

通过这次基于官方教程的实践,我成功搭建了一个功能完整的协同编辑器。以下是我的几点总结:

成功经验

  1. 文档质量高 - 原教程步骤清晰,配置详细
  2. 组件成熟度好 - TinyEditor 协同编辑功能稳定
  3. 部署简单 - Docker 镜像大大简化了后端部署

改进建议

  1. 提供更多示例 - 希望有更多实际应用场景的代码示例
  2. 错误处理指南 - 常见错误及解决方案的文档
  3. 性能优化建议 - 大规模协同时的性能调优指南

适用场景

经过测试,TinyEditor 协同编辑功能非常适合:

  • 团队文档协作
  • 在线代码评审
  • 远程教学白板
  • 多人内容编辑

资源链接

结语

通过这次实践,我深刻体会到了 TinyEditor 协同编辑功能的强大和易用性。从安装配置到生产部署,整个过程相对顺畅。希望我的实践分享能够帮助更多开发者快速上手这个优秀的工具。

如果你在实践过程中遇到任何问题,或者有更好的配置建议,欢迎在评论区交流讨论。让我们一起推动开源项目的发展!

相关推荐
K·Herbert3 小时前
OpenClaw 私人电脑部署风险
人工智能·安全·编辑器
德育处主任Pro5 小时前
『NAS』Typora平替,一款所见即所得的MD编辑器-Haptic
docker·编辑器·群晖·nas·绿联
速易达网络5 小时前
360°全景图片交互展示系统
编辑器
史丹利复合田1 天前
【无标题】vscode远程连接,服务器端配置
ide·vscode·编辑器
wtsolutions1 天前
图片GPS数据编辑器批量处理功能详解 - 高效管理大量图片的位置信息
编辑器·gps·图片·照片
cooldream20091 天前
Vim 报错 E325:swap 文件冲突的原理、处理流程与彻底避免方案
linux·编辑器·vim
wtsolutions1 天前
如何用图片GPS数据编辑器解决批量图片位置信息管理问题
编辑器·gps·图片·照片
山峰哥1 天前
SQL调优实战密码:索引策略与Explain工具深度破局之道
java·开发语言·数据库·sql·编辑器·深度优先
AI分享6661 天前
VSCode如何使用claude code(VS Code + Claude API 详细教程)(API 配置图文全攻略)
ide·vscode·编辑器