build: + Quill editor

main
Lei OT 5 months ago
parent c5c5eacf62
commit 89acb08340

@ -20,6 +20,7 @@
"i18next": "^23.11.5",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.5.2",
"quill": "^2.0.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^14.1.2",

@ -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>&nbsp;/&nbsp;4000
</div>
</>
);
});
Editor.displayName = 'Editor';
export default Editor;
Loading…
Cancel
Save