From 1aede553aa09d2199b8588437e48dbae8f01a0ab Mon Sep 17 00:00:00 2001 From: Abhishek P Anil Date: Thu, 8 Aug 2024 11:31:33 +0530 Subject: [PATCH 1/5] feat: added support for image and videos in editor.Prevent drag drop of image and video --- src/components/TextEditor/TextEditor.jsx | 41 ++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/components/TextEditor/TextEditor.jsx b/src/components/TextEditor/TextEditor.jsx index 057333506..26ff52eab 100644 --- a/src/components/TextEditor/TextEditor.jsx +++ b/src/components/TextEditor/TextEditor.jsx @@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next'; import { pluralize } from '../../utils/pluralise'; import OutlinedButton from '../Button/Outlined'; import { contentLanguage } from '../../constants/contentLanguage'; + function TextEditor(props) { const { formName, @@ -34,6 +35,29 @@ function TextEditor(props) { .split(' ') ?.filter((n) => n != '').length, ); + var formats = [ + 'background', + 'bold', + 'color', + 'font', + 'code', + 'italic', + 'link', + 'size', + 'strike', + 'script', + 'underline', + 'blockquote', + 'header', + 'indent', + 'list', + 'align', + 'direction', + 'code-block', + 'formula', + 'image', + 'video', + ]; const modules = { toolbar: [ [{ header: '1' }], @@ -41,7 +65,7 @@ function TextEditor(props) { [{ align: [] }], [{ list: 'ordered' }, { list: 'bullet' }], [{ color: [] }, { background: [] }], // dropdown with defaults from theme - ['link'], + ['link', 'image', 'video'], ], clipboard: { matchVisual: false, @@ -71,6 +95,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 +124,14 @@ function TextEditor(props) { ]); return ( - <> +
)} - +
); } export default TextEditor; From 186e1fb2382cc242805a4f8b11c819774e152d97 Mon Sep 17 00:00:00 2001 From: Abhishek P Anil Date: Mon, 12 Aug 2024 12:17:55 +0530 Subject: [PATCH 2/5] feat: add intregration and intercept function for images --- src/components/TextEditor/TextEditor.jsx | 69 +++++++++++++++++++----- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/src/components/TextEditor/TextEditor.jsx b/src/components/TextEditor/TextEditor.jsx index 26ff52eab..e4bfbc098 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,8 @@ 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'; function TextEditor(props) { const { @@ -27,6 +29,8 @@ function TextEditor(props) { } else { translateTo = 'en'; } + const { calendarId } = useParams(); + const [addImage] = useAddImageMutation(); const { t } = useTranslation(); const [wordCount, setWordCount] = useState( @@ -58,20 +62,59 @@ function TextEditor(props) { 'image', 'video', ]; - const modules = { - toolbar: [ - [{ header: '1' }], - ['bold', 'italic', 'underline'], - [{ align: [] }], - [{ list: 'ordered' }, { list: 'bullet' }], - [{ color: [] }, { background: [] }], // dropdown with defaults from theme - ['link', 'image', 'video'], - ], - clipboard: { - matchVisual: false, - }, + + 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?.uri; + } catch (error) { + console.error(error); + return null; + } + }; + + 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', 'image', 'video'], + ], + handlers: { + image: imageHandler, + }, + }, + clipboard: { + matchVisual: false, + }, + }), + [], + ); + const onChange = () => { const filteredCount = currentReactQuillRef?.current?.unprivilegedEditor ?.getText() From f021cf742a1774eec2609674c360de9dac051942 Mon Sep 17 00:00:00 2001 From: Abhishek P Anil Date: Mon, 12 Aug 2024 15:40:44 +0530 Subject: [PATCH 3/5] feat: corrected the uri object --- src/components/TextEditor/TextEditor.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TextEditor/TextEditor.jsx b/src/components/TextEditor/TextEditor.jsx index e4bfbc098..ac26637a1 100644 --- a/src/components/TextEditor/TextEditor.jsx +++ b/src/components/TextEditor/TextEditor.jsx @@ -69,7 +69,7 @@ function TextEditor(props) { try { const response = await addImage({ data: formData, calendarId }).unwrap(); - if (response) return response.data?.original?.uri; + if (response) return response.data?.original?.url?.uri; } catch (error) { console.error(error); return null; From 12e5edf3f97cf0d1972fcebb8d3909bbce9679f1 Mon Sep 17 00:00:00 2001 From: Abhishek P Anil Date: Mon, 12 Aug 2024 15:42:09 +0530 Subject: [PATCH 4/5] chore: closes #1270 From 58af56d223327b0edd1f87ca2cc3a1f3aa5c0296 Mon Sep 17 00:00:00 2001 From: Abhishek P Anil Date: Fri, 16 Aug 2024 13:19:35 +0530 Subject: [PATCH 5/5] feat: removed option to add image video.prevent users from copy paste image --- src/components/TextEditor/TextEditor.jsx | 14 +++++++++++--- src/components/TextEditor/textEditor.css | 4 ++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/components/TextEditor/TextEditor.jsx b/src/components/TextEditor/TextEditor.jsx index ac26637a1..424e00871 100644 --- a/src/components/TextEditor/TextEditor.jsx +++ b/src/components/TextEditor/TextEditor.jsx @@ -9,6 +9,8 @@ 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 { @@ -59,8 +61,6 @@ function TextEditor(props) { 'direction', 'code-block', 'formula', - 'image', - 'video', ]; const uploadImage = async (file) => { @@ -76,6 +76,13 @@ function TextEditor(props) { } }; + 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'); @@ -102,7 +109,7 @@ function TextEditor(props) { [{ align: [] }], [{ list: 'ordered' }, { list: 'bullet' }], [{ color: [] }, { background: [] }], - ['link', 'image', 'video'], + ['link'], ], handlers: { image: imageHandler, @@ -110,6 +117,7 @@ function TextEditor(props) { }, clipboard: { matchVisual: false, + matchers: [[Node.ELEMENT_NODE, removeImagesMatcher]], }, }), [], 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';