Skip to content

Commit

Permalink
GH-69: Add WYSIWYG Editor component with quill
Browse files Browse the repository at this point in the history
  • Loading branch information
SetZero committed Jun 28, 2023
1 parent 7d27961 commit 07c7a8d
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 8 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@tauri-apps/api": "^1.2.0",
"@types/dompurify": "^3.0.1",
"@types/marked": "^5.0.0",
"@types/quill": "^2.0.10",
"@types/react-color": "^3.0.6",
"@types/tinycolor2": "^1.4.3",
"dayjs": "^1.11.7",
Expand All @@ -30,6 +31,7 @@
"lru-cache": "^10.0.0",
"marked": "^5.0.2",
"marked-highlight": "^2.0.0",
"quill": "1.3.6",
"react": "^18.2.0",
"react-color": "^2.19.3",
"react-dom": "^18.2.0",
Expand Down
4 changes: 2 additions & 2 deletions src/components/LightBoxImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { UsersState } from "../store/features/users/userSlice";
import "./styles/UserInfo.css";
import "./styles/common.css"
import UserInfo from "./UserInfo";
import { useState } from "react";
import React, { useState } from "react";
import { openInBrowser } from "../helper/BrowserUtils";

interface LightBoxImageProps {
Expand All @@ -19,7 +19,7 @@ function LightBoxImage(props: LightBoxImageProps) {

return (
<Box>
<img src={props.src} onClick={() => setOpen(true)} style={{ maxWidth: '100%', maxHeight: '100%', cursor: 'pointer' }} />
<img src={props.src} onClick={() => setOpen(true)} style={{ maxWidth: '100%', maxHeight: '600px', cursor: 'pointer' }} />
<Backdrop
sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1, backdropFilter: 'blur(5px)', padding: '50px 10px 10px 10px' }}
open={open}
Expand Down
119 changes: 119 additions & 0 deletions src/components/QuillEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React, { useEffect, useRef, useState } from 'react';
import Quill, { TextChangeHandler } from 'quill';
import 'quill/dist/quill.snow.css';

interface QuillEditorProps {
theme?: 'bubble' | 'snow' | string;
placeholder?: string;
readOnly?: boolean;
bounds?: string;
debug?: 'error' | 'warn' | 'log' | boolean;
formats?: string[];
modules?: any;
scrollingContainer?: string | HTMLElement | undefined;
style?: React.CSSProperties;
onKeyDown?: (this: HTMLDivElement, ev: KeyboardEvent) => any;
onChange?: (content: string) => void;
onPaste?: (this: HTMLDivElement, ev: ClipboardEvent) => any;
multiline?: boolean;
value?: string;
}

export const QuillEditor: React.FC<QuillEditorProps> = ({
theme = 'snow',
placeholder = 'Compose an epic...',
readOnly = false,
bounds = 'document.body',
debug = 'warn',
formats = [],
modules = {},
scrollingContainer = undefined,
style = {},
onKeyDown,
onChange,
onPaste,
multiline = false,
value = ''
}) => {
const editorRef = useRef<HTMLDivElement | null>(null);
const quillRef = useRef(); // This will hold the Quill instance
const [toolbarVisible, setToolbarVisible] = useState(multiline);
let quill: Quill | undefined = undefined;
let textChangeListener: TextChangeHandler | undefined = undefined;

// This runs once on component mount
useEffect(() => {
if (editorRef.current) {
quill = new Quill(editorRef.current, {
theme,
placeholder,
readOnly,
bounds,
debug,
formats,
modules,
scrollingContainer
});

// Set the initial value
quill.root.innerHTML = value;

// Add event listeners
if (onKeyDown)
quill.root.addEventListener('keydown', onKeyDown);
if (onPaste)
quill.root.addEventListener('paste', onPaste);

if (onChange) {
textChangeListener = () => {
onChange(quill?.root.innerHTML || '');
}

quill.on('text-change', textChangeListener);
}


// Check for multiline to show/hide toolbar
if (multiline) {
quill.on('text-change', () => {
const text = quill?.getText();
const lineCount = (text?.match(/\n/g) || []).length;
setToolbarVisible(lineCount > 1);
});
}
}

// Clean up on component unmount
return () => {
if (quill) {
if (onKeyDown) {
quill.root.removeEventListener('keydown', onKeyDown);
}

if (textChangeListener) {
quill?.off('text-change', textChangeListener);
}

if (onPaste) {
quill.root.removeEventListener('paste', onPaste);
}
}
};
}, []); // Empty array means this effect runs once on mount and clean up on unmount


useEffect(() => {
if (editorRef.current) {


/*if (quillRef) {
quillRef.current = quill;
}*/
}
}, [theme, placeholder, readOnly, bounds, debug, formats, modules, scrollingContainer, quillRef]);

return <div className="quill-wrapper" style={style}>
{toolbarVisible && <div id="toolbar">/* Add toolbar contents here */</div>}
<div ref={editorRef} />
</div>;
};
5 changes: 4 additions & 1 deletion src/helper/ChatMessage.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { invoke } from "@tauri-apps/api";
import { TextMessage, addChatMessage } from "../store/features/users/chatMessageSlice";
import { TextMessage, addChatMessage, deleteAllMessages } from "../store/features/users/chatMessageSlice";
import { UsersState } from "../store/features/users/userSlice";
import MessageParser from "./MessageParser";
import { Dispatch } from "react";
import { AnyAction } from "@reduxjs/toolkit";

export class ChatMessageHandler {
deleteMessages() {
this.dispatch(deleteAllMessages());
}

constructor(private dispatch: Dispatch<AnyAction>, private setChatMessage: any) {

Expand Down
14 changes: 10 additions & 4 deletions src/routes/Chat.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Box, Divider, IconButton, InputBase, Paper } from '@mui/material';
import { Box, Divider, IconButton, InputBase, Paper, Tooltip } from '@mui/material';
import { invoke } from '@tauri-apps/api';
import { useEffect, useState } from 'react';
import SendIcon from '@mui/icons-material/Send';
import AddToPhotosIcon from '@mui/icons-material/AddToPhotos';
import DeleteIcon from '@mui/icons-material/Delete';
import ChatMessageContainer from '../components/ChatMessageContainer';
import GifIcon from '@mui/icons-material/Gif';
import GifSearch from '../components/GifSearch';
Expand Down Expand Up @@ -50,6 +50,10 @@ function Chat() {
}
}

function deleteMessages() {
chatMessageHandler.deleteMessages();
}



function keyDownHandler(e: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) {
Expand Down Expand Up @@ -98,9 +102,11 @@ function Chat() {
component="form"
sx={{ p: '2px 4px', display: 'flex', alignItems: 'center', width: 400, flexGrow: 1 }}
>
<IconButton sx={{ p: '10px' }} aria-label="menu" onClick={uploadFile}>
<AddToPhotosIcon />
<Tooltip title="Delete all messages">
<IconButton sx={{ p: '10px' }} aria-label="menu" onClick={deleteMessages}>
<DeleteIcon />
</IconButton>
</Tooltip>
<InputBase
sx={{ ml: 1, flex: 1 }}
placeholder={"Send Message to " + currentChannel}
Expand Down
5 changes: 4 additions & 1 deletion src/store/features/users/chatMessageSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@ export const chatMessageSlice = createSlice({
let messageId = action.payload;
let messageIndex = state.findIndex(e => e.timestamp === messageId);
state.splice(messageIndex, 1);
},
deleteAllMessages: (state, action: PayloadAction<void>) => {
state.length = 0;
}
},
})

export const { addChatMessage, deleteChatMessage } = chatMessageSlice.actions
export const { addChatMessage, deleteChatMessage, deleteAllMessages } = chatMessageSlice.actions

export default chatMessageSlice.reducer
Loading

0 comments on commit 07c7a8d

Please sign in to comment.