[address] (www.yuque.com/docs/share/… React-quill Rich Text Editor & Mining
Due to the service function, it is necessary to implement when users can format and paste content in rich text. If users copy pictures, they need to upload the pictures to the server and insert them into the text. Seemingly reasonable request, but there are many holes.
Begin to use
Install the react – quill
npm install react-quill @ beta
Resources:
Making the address
Quill Chinese document address
Quill document address
Get hands-on with Codeopen
1, custom rich text format brush function
Quill does not have the function of format brush, so there is no corresponding icon. In this case, you need to customize the toolbar. The Quill toolbar control can be defined with an array of format names, or can be defined with a normal HTML container. The HTML container definition is used here.
First define a format brush icon. The icon here uses the Unicode reference of Iconfont, which supports dynamic adjustment of icon size, color and so on according to font. You can highlight it and keep it consistent with the original toolbar interaction.
const CustomButton = () => <i className="iconfont"> </i>;Copy the code
Customize the HTML container CustomToolbar
const CustomToolbar = () => (
<div id="toolbar">
<select className="ql-header" defaultValue={''} onChange={(e) => e.persist()}>
<option value="1" />
<option value="2" />
<option value="3" />
<option value="4" />
<option value="5" />
<option selected />
</select>
<select className="ql-size" defaultValue={''} onChange={(e) => e.persist()}>
<option value="small" />
<option selected />
<option value="large" />
<option value="huge" />
</select>
<button className="ql-formatBrush">
<CustomButton />
</button>
<button className="ql-bold" />
<button className="ql-italic" />
<button className="ql-underline" />
<button className="ql-strike" />
<select className="ql-color" />
<select className="ql-background" />
<select className="ql-align" />
<button className="ql-list" value="ordered" />
<button className="ql-list" value="bullet" />
<button className="ql-indent" value="+1" />
<button className="ql-indent" value="-1" />
<button className="ql-image" />
<button className="ql-clean" />
</div>
);
Copy the code
CustomToolbar is to add the original toolbar with a format brush toolbar. Put custom toolbars in return.
return ( <div className={styles['rich-text']}> <CustomToolbar /> <ReactQuill ref={editorRef} placeholder={placeholder || 'Please input content '} modules={modules} theme="snow" {... props} value={value} /> </div> );Copy the code
Effect:
Format brush function
Next comes formatting, which is done in Modules. FormatBrush is consistent with the toolbar className=” qL-FormatBrush “.
// remember to declare the variable let quillEditor = null; const copyFormatting = { value: 'un-active', format: {}, }; //modules method const modules = useMemo(() => {return {toolbar: {// container: toolbarContainer, container: '#toolbar', handlers: { image: imageHandler, formatBrush: () => { copyFormatBrush(quillEditor, copyFormatting); }},}}; } []);Copy the code
Key format setting style method, click Format brush, if the selected area has a style, then save the style, the format brush function is selected, click Delete style again, deselect; Set the format style. These three methods can be kept in a separate file export management.
/* eslint-disable @typescript-eslint/no-use-before-define */ export const copyFormatBrush = (quillEditor, // Click format brush, if there is a selected area and style, then save the style, key state changed to Selected. // Click again, delete style, key to deselect. if (copyFormatting.value === 'un-active') { const range = quillEditor.getSelection(true); if (range == null || range.length === 0) return; const format = quillEditor.getFormat(range); if (Object.keys(format).length === 0) return; setCopyFormatting(quillEditor, copyFormatting, 'active', format); } else { setCopyFormatting(quillEditor, copyFormatting, 'un-active', null); }}; / / set copyFormatting: Export const setCopyFormatting = (quill, copyFormatting, value, format) => { copyFormatting.value = value; copyFormatting.format = format; const toolbar = quill.getModule('toolbar').container; const brushBtn = toolbar.querySelector('.ql-formatBrush'); if (value === 'active') { brushBtn.classList.add('ql-formatBrushactive'); quill.on('selection-change', pasteFormatHandler); } else { brushBtn.classList.remove('ql-formatBrushactive'); quill.off('selection-change', pasteFormatHandler); } function pasteFormatHandler(range, oldRange, source) { return pasteFormat(range, oldRange, source, quill, copyFormatting); }}; // Paste style handler: Paste the style if the range is selected and there is a save style, Initialize copyFormatting export const pasteFormat = (range, oldRange, source, quill, copyFormatting) => { if (range && copyFormatting.format) { if (range.length === 0) { } else { quill.formatText(range.index, range.length + 1, copyFormatting.format); setCopyFormatting(quill, copyFormatting, 'un-active', null); } } else { // console.log('Cursor not in the editor') } };Copy the code
I tested using the default QL-active highlighting style, it doesn’t work, only custom format brush highlighting style:
.ql-toolbar.ql-snow .ql-formatBrushactive {
color: #06c;
}
Copy the code
Effect:
The above format brush function has been implemented.
2, content paste operation – picture preview directly
The content copied by users contains cross-domain images, which need to be uploaded to the server and inserted into the text for display.
At present, the image function is customized encapsulation. When users insert images, they Upload them through ANTD Upload, and then insert the URL images of our server after uploading image resources to the server.
Customize Quill’s behavior and functionality
The main method is imageHandler, which uses the Quill. Register module to customize the behavior and functionality of Quill.
const modules = useMemo(() => { return { toolbar: { // container: toolbarContainer, container: '#toolbar', handlers: { image: imageHandler, }, }, }; } []);Copy the code
The logic of the popover is not much to show you. After you have uploaded the fileList data, insert the image in the rich text.
JSX import './ImageBlot'; Const insertImages = (fileList = []) => {if (quillEditor && typeof quillEditor. InsertEmbed === 'function') { quillEditor.focus(); const unprivilegedEditor = editorRef.current.makeUnprivilegedEditor(quillEditor); fileList.forEach((file) => { setTimeout(() => { const range = unprivilegedEditor.getSelection(); const position = range ? range.index : 0; quillEditor.insertEmbed(position, 'image', { ossid: file.ossId, url: file.url, }); quillEditor.setSelection(position + 1, 0); }, 0); }); }}; // return <ImageUploadModal onCancel={() => { setShowImageUpload(false); }} onCreate={(fileList) => { setShowImageUpload(false); insertImages(fileList); }} / >Copy the code
The upload of copy content and pictures to get the URL data of the new server is also realized in the custom image module.
import { Quill } from 'react-quill'; import { getTmpUrl } from '@/components/AliyunOSSUpload/service'; import { isExpired } from '@/components/AliyunOSSUpload/utils'; import { saveAliOssImg } from '@/services/global'; import errorImg from './error-img.png'; const BlockEmbed = Quill.import('blots/block/embed'); const getExpireFromUrl = (url) => { const index = url.indexOf('? '); if (index ! == -1) { const reg = new RegExp('(^|&)Expires=([^&]*)(&|$)', 'i'); const str = url.substring(index + 1, url.length); const expire = str.match(reg)[2]; return expire || 0; } return 0; }; const replacedImage = (ossId, node) => { getTmpUrl({ ossIds: [ossId] }).then((data) => { if (data && data.length > 0 && data[0].tmpUrl) { const url = data[0].tmpUrl; node.setAttribute('src', url); }}); }; /** * custom image tags, Add ossId */ class ImageBlot extends BlockEmbed {static create(value) {console.log('ImageBlot :>> ', value); const node = super.create(); if (value.ossid && value.url) { node.setAttribute('ossid', value.ossid); node.setAttribute('src', value.url); const expire = getExpireFromUrl(value.url); if (expire && isExpired(parseInt(expire, 10))) { replacedImage(value.ossid, node); If (value.url) {let sourceType = 'URL'; let content = value.url; if (value.url.indexOf('base64') > -1) { sourceType = 'BASE64'; content = value.url.split('base64,')[1]; } saveAliOssImg({sourceType, content }) .then((data) => { if (data && data.url) { node.setAttribute('src', data.url); } }) .catch(() => { node.setAttribute('src', errorImg); }); } console.log('ImageBlot --node-:>> ', node); return node; } static value(node) { const ossId = node.getAttribute('ossid'); const url = node.getAttribute('src'); return { ossid: ossId, url, }; } } ImageBlot.blotName = 'image'; ImageBlot.tagName = 'img'; Quill.register(ImageBlot);Copy the code
The actual operation
1. Copy resources outside the domain:
2, paste rich text effect:
From the output, the final result of the substitution is no problem
Add image loading failure, local path image resource loading failure processing, using image placeholder.
Known issues
1. “Directly copy the pictures in the local folder, ok under MAC, invalid under Windows”, check the original Copy under Windows is BOLB stream (back-end server failed to accept), but under MAC is File stream. Further exploration found that in the Windows folder system, copy text class things, is in the clipboard, can be obtained; However, the copied image file, no matter right click copy, or Ctrl + C copy will not work. At present, Windows copy pictures can choose rich text insert picture function implementation. (Please leave comments and discuss)
2. Roll the page to the bottom after pasting. The solution is as follows:
Add the following styles to the less file.
.ql-clipboard {
position: fixed;
// display: none;
left: 50%;
top: 50%;
}
Copy the code
Welcome a lot of message discussion, a good plan can leave valuable comments.