/* * @Description: quill 编辑器 * @Author: tangjiang * @Github: * @Date: 2019-12-18 08:49:30 * @LastEditors : tangjiang * @LastEditTime : 2020-01-11 13:43:31 */ import './index.scss'; import 'quill/dist/quill.core.css'; // 核心样式 import 'quill/dist/quill.snow.css'; // 有工具栏 import 'quill/dist/quill.bubble.css'; // 无工具栏 import 'katex/dist/katex.min.css'; // katex 表达式样式 import React, { useState, useRef, useEffect } from 'react'; import Quill from 'quill'; import katex from 'katex'; import deepEqual from './deepEqual.js' import { fetchUploadImage } from '../../services/ojService.js'; import { getImageUrl } from 'educoder' import ImageBlot from './ImageBlot'; // import FillBlot from './FillBlot'; const Size = Quill.import('attributors/style/size'); const Font = Quill.import('formats/font'); // const Color = Quill.import('attributes/style/color'); Size.whitelist = ['12px', '14px', '16px', '18px', '20px', false]; Font.whitelist = ['SimSun', 'SimHei','Microsoft-YaHei','KaiTi','FangSong','Arial','Times-New-Roman','sans-serif']; window.Quill = Quill; window.katex = katex; Quill.register(ImageBlot); Quill.register(Size); Quill.register(Font, true); // Quill.register({'modules/toolbar': Toolbar}); // Quill.register({ // 'formats/fill': FillBlot // }); // Quill.register(Color); function QuillForEditor ({ placeholder, readOnly, autoFocus = false, options, value, imgAttrs = {}, // 指定图片的宽高 style = {}, wrapStyle = {}, showUploadImage, onContentChange, // addFill, // 点击填空成功的回调 // getQuillContent }) { // toolbar 默认值 const defaultConfig = [ 'bold', 'italic', 'underline', {size: ['12px', '14px', '16px', '18px', '20px']}, {align: []}, {list: 'ordered'}, {list: 'bullet'}, // 列表 {script: 'sub'}, {script: 'super'}, { 'color': [] }, { 'background': [] }, {header: [1,2,3,4,5,false]}, 'blockquote', 'code-block', 'link', 'image', 'video', 'formula', 'clean' ]; const editorRef = useRef(null); // quill 实例 const [quill, setQuill] = useState(null); const [selection, setSelection] = useState(null); const [fillCount, setFillCount] = useState(0); const [quillCtx, setQuillCtx] = useState({}); // 文本内容变化时 const handleOnChange = content => { // getQuillContent && getQuillContent(quill); onContentChange && onContentChange(content, quill); }; const renderOptions = options || defaultConfig; const bindings = { tab: { key: 9, handler: function () { console.log('调用了tab=====>>>>'); } }, backspace: { key: 'Backspace', /** * @param {*} range * { index, // 删除元素的位置 * length // 删除元素的个数, 当删除一个时, length=0, 其它等于删除的元素的个数 * } * @param {*} context 上下文 */ handler: function (range, context) { // console.log('调用了删除按钮', range, context); /** * 1. 根据range中的index及length值获取删除的起始位置 * length === 0 -> start = index - 1; * length !== 0 -> start = index * 2. 获取删除的元素内容 * ctx = this.quill.getText(start, length === 0 ? 1 : length); * 3. 判断当前删除的下划线是第几个 */ // const {index, length} = range; // const _start = length === 0 ? index - 1 : index; // const _length = length || 1; // let delCtx = this.quill.getText(_start, _length); // aa // console.log(delCtx.match(/▁/g)); // console.log('删除的文本信息=====>>>>', delCtx); // const r = window.confirm('确定要删除吗?') // if (r) { // // 调用传入的删除事件 // return true // } else { // return false; // } return true; } } }; // quill 配置信息 const quillOption = { modules: { toolbar: renderOptions, keyboard: { bindings: bindings } // toolbar: { // container: renderOptions // } }, readOnly, placeholder, theme: readOnly ? 'bubble' : 'snow', }; useEffect(() => { const quillNode = document.createElement('div'); editorRef.current.appendChild(quillNode); const _quill = new Quill(editorRef.current, quillOption); setQuill(_quill); // 处理图片上传功能 _quill.getModule('toolbar').addHandler('image', (e) => { const input = document.createElement('input'); input.setAttribute('type', 'file'); input.setAttribute('accept', 'image/*'); input.click(); input.onchange = async (e) => { const file = input.files[0]; // 获取文件信息 const formData = new FormData(); formData.append('file', file); const range = _quill.getSelection(true); let fileUrl = ''; // 保存上传成功后图片的url // 上传文件 const result = await fetchUploadImage(formData); // 获取上传图片的url if (result.data && result.data.id) { fileUrl = getImageUrl(`api/attachments/${result.data.id}`); } // 根据id获取文件路径 const { width, height } = imgAttrs; // console.log('上传图片的url:', fileUrl); if (fileUrl) { _quill.insertEmbed(range.index, 'image', { url: fileUrl, alt: '图片信息', onClick: showUploadImage, width, height }); } } }); _quill.getModule('toolbar').addHandler('fill', (e) => { // console.log('点击了填空=====>>>>>>', e); setFillCount(fillCount + 1); const range = _quill.getSelection(true); // _quill.insertText(range.index, '▁', { 'data_index': fillCount }); _quill.insertEmbed(range.index, 'fill', { text: '▁', 'data_index': fillCount }); // 点击填空图标时,插入一个下划线 // 1. 获取编辑器内容 }); // TODO /** * 1.获取键盘删除事件 * 2.点击时获取删除的叶子节点 getLeaf(range.index) */ }, []); // 设置值 useEffect(() => { if (!quill) return const previous = quill.getContents() if (value && value.hasOwnProperty('ops')) { // console.log(value.ops); const ops = value.ops || []; ops.forEach((item, i) => { if (item.insert['image']) { item.insert['image'] = Object.assign({}, item.insert['image'], {style: { cursor: 'pointer' }, onclick: (url) => showUploadImage(url)}); } }); } const current = value if (!deepEqual(previous, current)) { setSelection(quill.getSelection()) if (typeof value === 'string') { quill.clipboard.dangerouslyPasteHTML(value, 'api'); if (autoFocus) { quill.focus(); } else { quill.blur(); } } else { quill.setContents(value) if (autoFocus) quill.focus(); } } }, [quill, value, setQuill, autoFocus]); // 清除选择区域 useEffect(() => { if (quill && selection) { quill.setSelection(selection) setSelection(null) } }, [quill, selection, setSelection]); // 设置placeholder值 useEffect(() => { if (!quill || !quill.root) return; quill.root.dataset.placeholder = placeholder; }, [quill, placeholder]); // 处理内容变化 useEffect(() => { if (!quill) return; if (typeof handleOnChange !== 'function') return; let handler; quill.on( 'text-change', (handler = (delta, oldDelta, source) => { const _ctx = quill.getContents(); setQuillCtx(_ctx); handleOnChange(quill.getContents()); // getContents: 检索编辑器内容 }) ); return () => { quill.off('text-change', handler); } }, [quill, handleOnChange]); // 返回结果 return (
); } export default QuillForEditor;