build: + Quill editor
parent
c5c5eacf62
commit
89acb08340
@ -0,0 +1,14 @@
|
||||
.quill-editor .ql-container{
|
||||
font-size: 14px;
|
||||
}
|
||||
.quill-editor .ql-container.ql-snow {
|
||||
min-height: 12rem;
|
||||
border-color: #d9d9d9;
|
||||
}
|
||||
.quill-editor .ql-toolbar.ql-snow {
|
||||
border-radius: 6px 6px 0 0;
|
||||
border-color: #d9d9d9;
|
||||
}
|
||||
.quill-editor .ql-editor{
|
||||
min-height: 12rem;
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
import React, { forwardRef, useEffect, useLayoutEffect, useRef } from 'react';
|
||||
import Quill from 'quill';
|
||||
import 'quill/dist/quill.snow.css';
|
||||
// import 'quill/dist/quill.bubble.css';
|
||||
import './custom.snow.css';
|
||||
|
||||
const toolbarOptions = [
|
||||
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
|
||||
// [{ 'font': [] }],
|
||||
['bold', 'italic', 'underline', 'strike'], // toggled buttons
|
||||
// ['blockquote'], // 'code-block'
|
||||
['link', ], // 'image' 'video', 'formula'
|
||||
|
||||
[{ 'list': 'ordered' }, { 'list': 'bullet' }], // { 'list': 'check' }
|
||||
[{ 'color': [] }, { 'background': [] }], // dropdown with defaults from theme
|
||||
|
||||
// [{ 'size': ['small', false, 'large', 'huge'] }], // custom dropdown
|
||||
// [{ 'indent': '-1' }, { 'indent': '+1' }], // outdent/indent
|
||||
// [{ 'align': [] }],
|
||||
|
||||
['clean'], // remove formatting button
|
||||
];
|
||||
|
||||
// Editor is an uncontrolled React component
|
||||
const Editor = forwardRef(({ readOnly, defaultValue, value, onChange, onTextChange, onSelectionChange }, ref) => {
|
||||
const containerRef = useRef(null);
|
||||
const cntContainerRef = useRef(null);
|
||||
const defaultValueRef = useRef(defaultValue);
|
||||
const valueRef = useRef(value);
|
||||
const onTextChangeRef = useRef(onTextChange);
|
||||
const onChangeRef = useRef(onChange);
|
||||
const onSelectionChangeRef = useRef(onSelectionChange);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
onTextChangeRef.current = onTextChange;
|
||||
onChangeRef.current = onChange;
|
||||
onSelectionChangeRef.current = onSelectionChange;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const editor = ref.current;
|
||||
if (!editor) return;
|
||||
|
||||
valueRef.current = value;
|
||||
const current = editor.root.innerHTML;
|
||||
const incoming = value || defaultValue || '';
|
||||
// treat quill empty markup as empty string
|
||||
const currentNormalized = current === '<p><br></p>' ? '' : current;
|
||||
|
||||
// if (incoming !== currentNormalized) {
|
||||
if (!currentNormalized) { // ! 只设置一次value
|
||||
const value = valueRef.current;
|
||||
const delta = editor.clipboard.convert({ html: value });
|
||||
editor.setContents(delta, 'silent');
|
||||
|
||||
const sel = editor.getSelection && editor.getSelection();
|
||||
// restore selection if possible
|
||||
if (sel && sel.index != null) {
|
||||
try { editor.setSelection(Math.min(sel.index, editor.getLength() - 1), sel.length || 0); } catch {}
|
||||
}
|
||||
}
|
||||
}, [ref, defaultValue, value]);
|
||||
|
||||
useEffect(() => {
|
||||
ref.current?.enable(!readOnly);
|
||||
}, [ref, readOnly]);
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
const cntContainer = cntContainerRef.current;
|
||||
const editorContainer = container.appendChild(container.ownerDocument.createElement('div'));
|
||||
|
||||
// Add fonts to whitelist
|
||||
// const Font = Quill.import('formats/font');
|
||||
// Font.whitelist = ['mirza', 'roboto'];
|
||||
// Quill.register(Font, true);
|
||||
|
||||
Quill.register('modules/counter', function (quill, options) {
|
||||
// quill.on('text-change', function() {
|
||||
quill.on(Quill.events.TEXT_CHANGE, () => {
|
||||
// const text = quill.getText();
|
||||
const length = quill.getLength();
|
||||
cntContainer.innerText = length; // text.length; // text.split(/\s+/).length;
|
||||
});
|
||||
});
|
||||
|
||||
const quill = new Quill(editorContainer, {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: toolbarOptions,
|
||||
counter: true,
|
||||
},
|
||||
});
|
||||
|
||||
ref.current = quill;
|
||||
|
||||
// if (defaultValueRef.current) {
|
||||
// quill.setContents(defaultValueRef.current);
|
||||
// }
|
||||
|
||||
quill.on(Quill.events.TEXT_CHANGE, (...args) => {
|
||||
// const text = quill.getText();
|
||||
onTextChangeRef.current?.(...args);
|
||||
const html = quill.getSemanticHTML();
|
||||
onChangeRef.current?.(html);
|
||||
});
|
||||
|
||||
quill.on(Quill.events.SELECTION_CHANGE, (...args) => {
|
||||
onSelectionChangeRef.current?.(...args);
|
||||
});
|
||||
|
||||
return () => {
|
||||
ref.current = null;
|
||||
container.innerHTML = '';
|
||||
};
|
||||
}, [ref]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={containerRef} style={{ minHeight: '12rem' }} className='quill-editor'></div>
|
||||
<div style={{ border: '1px solid #ccc', borderWidth: '0px 1px 1px 1px', color: '#aaa', padding: '0 3px', textAlign: 'right', borderRadius: '0 0 6px 6px' }}>
|
||||
<span ref={cntContainerRef}>0</span> / 4000
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
Editor.displayName = 'Editor';
|
||||
|
||||
export default Editor;
|
||||
Loading…
Reference in New Issue