diff --git a/src/components/TextEditor/TextEditor.jsx b/src/components/TextEditor/TextEditor.jsx index 057333506..424e00871 100644 --- a/src/components/TextEditor/TextEditor.jsx +++ b/src/components/TextEditor/TextEditor.jsx @@ -1,5 +1,5 @@ import { Form } from 'antd'; -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import ReactQuill from 'react-quill'; import './textEditor.css'; import 'react-quill/dist/quill.snow.css'; @@ -7,6 +7,11 @@ import { useTranslation } from 'react-i18next'; import { pluralize } from '../../utils/pluralise'; import OutlinedButton from '../Button/Outlined'; import { contentLanguage } from '../../constants/contentLanguage'; +import { useAddImageMutation } from '../../services/image'; +import { useParams } from 'react-router-dom'; +import Quill from 'quill'; +const Delta = Quill.import('delta'); + function TextEditor(props) { const { formName, @@ -26,6 +31,8 @@ function TextEditor(props) { } else { translateTo = 'en'; } + const { calendarId } = useParams(); + const [addImage] = useAddImageMutation(); const { t } = useTranslation(); const [wordCount, setWordCount] = useState( @@ -34,20 +41,88 @@ function TextEditor(props) { .split(' ') ?.filter((n) => n != '').length, ); - const modules = { - toolbar: [ - [{ header: '1' }], - ['bold', 'italic', 'underline'], - [{ align: [] }], - [{ list: 'ordered' }, { list: 'bullet' }], - [{ color: [] }, { background: [] }], // dropdown with defaults from theme - ['link'], - ], - clipboard: { - matchVisual: false, - }, + var formats = [ + 'background', + 'bold', + 'color', + 'font', + 'code', + 'italic', + 'link', + 'size', + 'strike', + 'script', + 'underline', + 'blockquote', + 'header', + 'indent', + 'list', + 'align', + 'direction', + 'code-block', + 'formula', + ]; + + const uploadImage = async (file) => { + const formData = new FormData(); + formData.append('file', file); + + try { + const response = await addImage({ data: formData, calendarId }).unwrap(); + if (response) return response.data?.original?.url?.uri; + } catch (error) { + console.error(error); + return null; + } }; + const removeImagesMatcher = (node, delta) => { + if (node.nodeName === 'IMG') { + return new Delta(); + } + return delta; + }; + + const imageHandler = async () => { + const input = document.createElement('input'); + input.setAttribute('type', 'file'); + input.setAttribute('accept', 'image/*'); + input.click(); + + input.onchange = async () => { + const file = input.files[0]; + const range = currentReactQuillRef.current.getEditor().getSelection(); + const imageUrl = await uploadImage(file); + + if (imageUrl) { + currentReactQuillRef.current.getEditor().insertEmbed(range.index, 'image', imageUrl); + } + }; + }; + + const modules = useMemo( + () => ({ + toolbar: { + container: [ + [{ header: '1' }], + ['bold', 'italic', 'underline'], + [{ align: [] }], + [{ list: 'ordered' }, { list: 'bullet' }], + [{ color: [] }, { background: [] }], + ['link'], + ], + handlers: { + image: imageHandler, + }, + }, + clipboard: { + matchVisual: false, + matchers: [[Node.ELEMENT_NODE, removeImagesMatcher]], + }, + }), + [], + ); + const onChange = () => { const filteredCount = currentReactQuillRef?.current?.unprivilegedEditor ?.getText() @@ -71,6 +146,16 @@ function TextEditor(props) { window.open(`${process.env.REACT_APP_DEEPL_URL}${editorLanguage}/${translateTo}/${newString}`); }; + const onDropHandler = (e) => { + const items = e.dataTransfer.items; + for (let i = 0; i < items.length; i++) { + if ((items[i].kind === 'file' && items[i].type.startsWith('image/')) || items[i].type.startsWith('video/')) { + e.preventDefault(); + return; + } + } + }; + useEffect(() => { const filteredCount = currentReactQuillRef?.current?.unprivilegedEditor ?.getText() @@ -90,13 +175,14 @@ function TextEditor(props) { ]); return ( - <> +
)} - +
); } export default TextEditor; diff --git a/src/components/TextEditor/textEditor.css b/src/components/TextEditor/textEditor.css index 9663f9d05..f9e3ad923 100644 --- a/src/components/TextEditor/textEditor.css +++ b/src/components/TextEditor/textEditor.css @@ -19,6 +19,10 @@ resize: vertical; } +.text-editor .ql-container { + overflow: visible; +} + @media (max-width: 480px) { .text-editor .ql-snow .ql-editor h1 { font: 1.2em 'Roboto';