在react中使用quill
- 安装所需依赖
yarn add quill yjs y-quill y-websocket quill-cursors
- 初始化配置
import Quill from 'quill';
import 'quill/dist/quill.snow.css'; // 使用了 snow 主题色
const toolbarOptions = {
container: [
[{ header: [1, 2, 3, 4, 5, 6, false] }],
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
[{ list: 'ordered' }, { list: 'bullet' }, { list: 'check' }],
[{ script: 'sub' }, { script: 'super' }], // superscript/subscript
[{ align: [] }],
[{ indent: '-1' }, { indent: '+1' }], // outdent/indent
[{ direction: 'rtl' }], // text direction
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
// [{ font: [] }],
['link', 'image', 'video', 'formula'],
['clean'], // remove formatting button
],
};
useEffect(() => {
if (!container.current) return; // 挂载容器
const quill = new Quill(container.current, {
theme: 'snow',
placeholder: '请输入内容',
modules: {
toolbar: toolbarOptions,
// toolbar: {
// container: '#toolbar',
// },
history: { userOnly: true },
cursors: true, // 光标开启
},
});
quill.on('text-change', () => {
console.log('text change', quill.root.innerHTML);
});
return () => {
};
}, []);
- 使用Yjs、y-quill 进行编辑器绑定(此时编辑器已经具有协同编辑的功能了)
- package.json中配置一个y-websocket启动
"servers": "cross-env HOST=localhost PORT=1234 npx y-websocket"
import * as Yjs from 'yjs';
import { QuillBinding } from 'y-quill';
import { WebsocketProvider } from 'y-websocket';
const doc = new Yjs.Doc();
const text = doc.getText('quill');
// websocket url
// 房间名
// ydoc
const websocketProvider = new WebsocketProvider(wsUrl, 'my-roomname', doc);
// 创建quill绑定
const quillBinding = new QuillBinding(text, quill);
- 接下来使用quill-cursors 实现光标功能
使用之前必须要注册光标插件
Quill.register('modules/cursors', QuillCursors);
// Quill配置项中cursors一定要打开
// 两种方式都可以
cursors: true,
// cursors: {
// selectionChangeSource: 'selection-change',
// transformOnTextChange: false,
// },
import { QuillBinding } from 'y-quill';
import { WebsocketProvider } from 'y-websocket';
const websocketProvider = new WebsocketProvider(wsUrl, 'my-roomname', doc);
const awareness = websocketProvider.awareness;
let color = '#' + Math.random().toString(16).split('.')[1].slice(0, 6);
awareness.setLocalStateField('user', {
name: 'zhangsan',
color,
});
// 创建quill绑定
const quillBinding = new QuillBinding(text, quill, awareness);
// 监听awareness变化
awareness.on('change', (changes: Yjs.Transaction) => {
// 获取所有协同信息 用户列表
const allUsers = Array.from(awareness.getStates().values()).map((item) => item.user);
console.log('changes', changes, allUsers);
});
- 这样就已经实现协同编辑,并且带有光标的效果了。
- 完整代码如下
import Quill from 'quill';
import QuillCursors from 'quill-cursors';
import 'quill/dist/quill.snow.css'; // 使用了 snow 主题色
import React, { useEffect, useRef } from 'react';
import { QuillBinding } from 'y-quill';
import { WebsocketProvider } from 'y-websocket';
import * as Yjs from 'yjs';
import { wsUrl } from './config';
const Editor: React.FC = () => {
const container = useRef<HTMLDivElement | null>(null);
const fontSizeStyle = Quill.import('attributors/style/size'); // 引入这个后会把样式写在style上
fontSizeStyle.whitelist = [
'12px',
'14px',
'16px',
'18px',
'20px',
'24px',
'28px',
'32px',
'36px',
];
Quill.register(fontSizeStyle, true);
Quill.register('modules/cursors', QuillCursors);
const toolbarOptions = {
container: [
[{ header: [1, 2, 3, 4, 5, 6, false] }],
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
[{ list: 'ordered' }, { list: 'bullet' }, { list: 'check' }],
[{ script: 'sub' }, { script: 'super' }], // superscript/subscript
[{ align: [] }],
[{ indent: '-1' }, { indent: '+1' }], // outdent/indent
[{ direction: 'rtl' }], // text direction
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
// [{ font: [] }],
['link', 'image', 'video', 'formula'],
['clean'], // remove formatting button
],
};
useEffect(() => {
if (!container.current) return;
const quill = new Quill(container.current, {
theme: 'snow',
placeholder: '请输入内容',
modules: {
toolbar: toolbarOptions,
// toolbar: {
// container: '#toolbar',
// },
history: { userOnly: true },
cursors: true,
// cursors: {
// selectionChangeSource: 'selection-change',
// transformOnTextChange: false,
// },
},
});
quill.on('text-change', () => {
console.log('text change', quill.root.innerHTML);
});
const doc = new Yjs.Doc();
const text = doc.getText('quill');
// websocket url
// 房间名
// ydoc
const websocketProvider = new WebsocketProvider(wsUrl, 'my-roomname', doc);
const awareness = websocketProvider.awareness;
let color = '#' + Math.random().toString(16).split('.')[1].slice(0, 6);
awareness.setLocalStateField('user', {
name: 'zhangsan',
color,
});
// 创建quill绑定
const quillBinding = new QuillBinding(text, quill, awareness);
// 监听awareness变化
awareness.on('change', (changes: Yjs.Transaction) => {
// 获取所有协同信息 显示光标位置和用户列表
const allUsers = Array.from(awareness.getStates().values()).map((item) => item.user);
console.log('changes', changes, allUsers);
});
// // 链接成功之后删除quill里面的内容
// websocketProvider.on('synced', () => {
// // text.delete(0, text.length);
// // text.insert(0, '<p><br></p>');
// });
return () => {
websocketProvider.destroy();
quillBinding.destroy();
doc.destroy();
};
}, []);
return (
<div>
<div ref={container} />
</div>
);
};
export default Editor;