- 官方git: https://github.com/quilljs/quill/
- 官方文档:https://quilljs.com/
- 中文文档:https://kang-bing-kui.gitbook.io/quill/
编辑器是个正经编辑器,就是文档太不正经了
基本的一些使用文档还是可以的,基础用例代码如下
import React, { useEffect } from 'react';
import Quill, { QuillOptionsStatic } from 'quill';
import 'quill/dist/quill.snow.css';
import { TranslatorWrapper } from './translator';
const QuillEditor = () => {
useEffect(() => {
const toolbarOptions = [
[{ header: [false, 1, 2, 3, 4, 5, 6] }],
['bold', 'italic', 'underline', 'strike'],
[{ indent: '-1' }, { indent: '+1' }],
[{ align: '' }, { align: 'center' }, { align: 'right' }],
['link'],
['image'],
];
const options: QuillOptionsStatic = {
// boundsDOM元素或者一个DOM元素的css选择器,其中编辑器的UI元素(例如:tooltips)应该被包含其中。目前,只考虑左右边界
// 这个属性很管用,特别是在一些布局中,不设置边界的话,ql-tooltip会溢出编辑器,导致被遮挡,并且有些时候,布局已经确定,z-index都不能这解决遮挡问题
bounds: document.getElementById('quill_editor') as HTMLElement,
debug: 'false',
modules: {
imageUpload: true,
toolbar: toolbarOptions,
},
placeholder: '',
theme: 'snow',
};
const editor = new Quill('#quill_editor', options);
setQuillEditor(editor);
}, []);
return (
<TranslatorWrapper>
<div id="quill_editor"></div>
</TranslatorWrapper>
);
};
export default QuillEditor;
基本样例展示:
关于quill Toolbar中header中文展示处理
基于styled-components给quill编辑器的主容器增加一个包裹 TranslatorWrapper,通过修改::before属性,达到修改中文的目的;
那如果需要国际化,就可以通过参数处理来完成了
<TranslatorWrapper>
<div id="quill_editor"></div>
</TranslatorWrapper>
样式代码:translator.js
export const TranslatorWrapper = styled.div`
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: '正文';
font-size: 14px;
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before {
content: '标题 1';
font-size: 14px;
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
content: '标题 2';
font-size: 14px;
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
content: '标题 3';
font-size: 14px;
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
content: '标题 4';
font-size: 14px;
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
content: '标题 5';
font-size: 14px;
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
content: '标题 6';
font-size: 14px;
}
`;
关于React自定义组件的插入使用
注:这里的场景只是上传图片,所以对原来的上传图片按钮进行了替换,直接替换成一个React组件,其他新增一个按钮的自定义场景这次没有涉及
在前面基础应用代码中新增
import React, { useEffect } from 'react';
...
import ImageUploader from './ImageUploader.tsx';
const Image = Quill.import('formats/image');
Image.className = 'img-fluid';
Quill.register(Image, true);
Quill.register('modules/imageUpload', ImageUploader);
const QuillEditor = () => {
...
图片上传模块 ImageUploader.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import Quill, { QuillOptionsStatic } from 'quill';
import _ from 'lodash';
// 需要注入的React组件
import ImgButton from './ImgButton';
interface ImageUploaderInterface {
quill: Quill;
options: QuillOptionsStatic;
}
class ImageUploader implements ImageUploaderInterface {
quill: Quill;
options: QuillOptionsStatic;
constructor(quill: Quill, options: QuillOptionsStatic) {
this.quill = quill;
this.options = options;
// 获取editor的toolbar
const toolbar = this.quill.getModule('toolbar');
// 获取toolbar中原来 上传图片按钮
const imageParent = _.toArray(toolbar.container?.children).find(
(child) => child?.children[0]?.className === 'ql-image'
);
// 覆盖原有按钮 DOM结构
ReactDOM.render(<ImgButton editor={this.quill} onChange={this.onImageChange} />, imageParent);
}
// 传入图片上传ImgButton组件的回调方法,与editor通信
onImageChange = (src: string) => {
this.quill.getSelection(true);
const index = (this.quill.getSelection() || {}).index || this.quill.getLength();
this.quill.insertEmbed(index, 'image', src);
};
}
export default ImageUploader;
ql-tooltip被遮挡 z-index设置也无效时
注意检查是否设置了bounds属性
bounds: document.getElementById('quill_editor') as HTMLElement,
hanlder添加
文档提供方式:
var quill = new Quill('#editor', {
modules: {
toolbar: {
container: '#toolbar', // Selector for toolbar container
handlers: {
'bold': customBoldHandler
}
}
}
});
toolbar使用数组方式设置的话,增加handler方式如下
var toolbar = quill.getModule('toolbar');
toolbar.addHandler('bold', customBoldHandler);
修改添加链接的placeholder
/**
* 修改添加链接的placeholder
*/
useEffect(() => {
document
.querySelector('input[data-link]')
?.setAttribute('data-link', '请输入链接 http(s)://...');
}, [quillEditor]);
quill snow link tooltip 中输入框对输入的链接进行校验(validation)
utils.js
import Emitter from 'quill/core/emitter';
import { message } from 'antd';
/**
* 覆写 Snow 的tooltip的save
*/
export function SnowTooltipSave() {
const { value } = this.textbox;
const linkValidityRegex = /^(http|https)/;
switch (this.root.getAttribute('data-mode')) {
case 'link': {
if (!linkValidityRegex.test(value)) {
message.error('链接格式错误,请输入链接 http(s)://...');
return;
}
const { scrollTop } = this.quill.root;
if (this.linkRange) {
this.quill.formatText(this.linkRange, 'link', value, Emitter.sources.USER);
delete this.linkRange;
} else {
this.restoreFocus();
this.quill.format('link', value, Emitter.sources.USER);
}
this.quill.root.scrollTop = scrollTop;
break;
}
default:
}
this.textbox.value = '';
this.hide();
}
export function SnowThemeLinkHandler(value) {
if (value) {
const range = this.quill.getSelection();
if (range == null || range.length === 0) return;
let preview = this.quill.getText(range);
if (/^\S+@\S+\.\S+$/.test(preview) && preview.indexOf('mailto:') !== 0) {
preview = `mailto:${preview}`;
}
const { tooltip } = this.quill.theme;
tooltip.save = SnowTooltipSave;
tooltip.edit('link', preview);
} else {
this.quill.format('link', false);
}
}
用例,基于 基础用例 代码
import React, { useEffect } from 'react';
import Quill, { QuillOptionsStatic } from 'quill';
...
const SnowTheme = Quill.import('themes/snow');
SnowTheme.DEFAULTS.modules.toolbar.handlers.link = SnowThemeLinkHandler;
...
const QuillEditor = () => {
...