From 75eda0e54c3415b936ba7c24fcf592362a606c33 Mon Sep 17 00:00:00 2001 From: dolf321 Date: Mon, 22 May 2023 17:37:36 +0400 Subject: [PATCH 01/38] ADD docker column with on/off --- web/src/pages/core/super/Playgrounds.jsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/web/src/pages/core/super/Playgrounds.jsx b/web/src/pages/core/super/Playgrounds.jsx index 6d4ce192..e09ac7ec 100644 --- a/web/src/pages/core/super/Playgrounds.jsx +++ b/web/src/pages/core/super/Playgrounds.jsx @@ -21,6 +21,7 @@ import standardErrorHandler from '../../../utils/standardErrorHandler'; import KillAllSessions from '../../../components/shared/KillAllSessions'; + const useStyles = makeStyles((theme) => ({ paper: { height: 700, @@ -92,6 +93,13 @@ const useColumns = (state, enqueueSnackbar) => ([ ), }, + { + field: 'docker', headerName: 'Docker', width: 100, renderCell: ({row}) => ( + + {row.docker ? : } + + ), + }, { field: 'kill', headerName: 'Kill Session', width: 150, renderCell: ({row}) => ( - - - - - - - ( - - { - !row.hidden ? - : - - } - - )}, - {field: 'release_date', headerName: 'Release Date', width: 170}, - {field: 'due_date', headerName: 'Due Date', width: 170}, - ]} rows={assignments} onRowClick={({row}) => history.push(`/admin/assignment/edit/${row.id}`)}/> - +
+ + + + Anubis + + + Assignment Management + + + + + + + + + + + + + + + + ( + + { + !row.hidden ? + : + + } + + )}, + {field: 'release_date', headerName: 'Release Date', width: 170}, + {field: 'due_date', headerName: 'Due Date', width: 170}, + ]} rows={assignments} onRowClick={({row}) => history.push(`/admin/assignment/edit/${row.id}`)}/> + + - + + Autograde Student + + + Select student to autograde: + + + { + setValue(newValue); + }} + inputValue={inputValue} + onInputChange={(event, newInputValue) => { + setInputValue(newInputValue); + }} + options={students} + renderInput={(params) => } + /> + + + + + +
); } From 750acc924124ba52a40e47a48c7ac21276dda414 Mon Sep 17 00:00:00 2001 From: dolf321 Date: Mon, 22 May 2023 17:37:55 +0400 Subject: [PATCH 11/38] FIX netid param name --- api/anubis/views/admin/regrade.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/anubis/views/admin/regrade.py b/api/anubis/views/admin/regrade.py index 4e010f13..544d7786 100644 --- a/api/anubis/views/admin/regrade.py +++ b/api/anubis/views/admin/regrade.py @@ -200,14 +200,14 @@ def private_regrade_assignment(assignment_id): } ) -@regrade.route("/student/") +@regrade.route("/student/") @require_admin() @json_response def private_regrade_student_netid(netid: str): """ - :param assignment_id: - :param netid: + + :param net_id: :return: """ From 427c3cd414970b162571eebdfa8b6b7cac6bb554 Mon Sep 17 00:00:00 2001 From: dolf321 Date: Mon, 22 May 2023 17:37:55 +0400 Subject: [PATCH 12/38] ADD assignment submission & autofill --- .../core/admin/Assignment/Assignments.jsx | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/web/src/pages/core/admin/Assignment/Assignments.jsx b/web/src/pages/core/admin/Assignment/Assignments.jsx index 03cf1e54..3a280fbb 100644 --- a/web/src/pages/core/admin/Assignment/Assignments.jsx +++ b/web/src/pages/core/admin/Assignment/Assignments.jsx @@ -78,6 +78,19 @@ export default function Assignments() { }).catch(standardErrorHandler(enqueueSnackbar)); }, [reset]); + React.useEffect(() => { + axios.get('/api/admin/students/list').then((response) => { + const data = standardStatusHandler(response, enqueueSnackbar); + if (data?.students) { + setStudents(data.students.map((student) => student.netid)); + } else { + enqueueSnackbar('Unable to fetch students', {variant: 'error'}); + } + }).catch((error) => { + enqueueSnackbar(error.toString(), {variant: 'error'}); + }); + }, [open]); + const addAssignment = () => { axios.post('/api/admin/assignments/add').then((response) => { const data = standardStatusHandler(response, enqueueSnackbar); @@ -87,20 +100,22 @@ export default function Assignments() { }).catch(standardErrorHandler(enqueueSnackbar)); }; - // const autogradeAssignments = () => { - // axios.post('/api/admin/autograde').then((response) => { - // const data = standardStatusHandler(response, enqueueSnackbar); - // // if (data.assignment) { - // // setReset((prev) => ++prev); - // // } - - // if (data.session?.state === 'autograding') { - // setLoading(true); - // pollSession(data.session.id, state, enqueueSnackbar)(); - // } - // }).catch(standardErrorHandler(enqueueSnackbar)); - // }; + const autogradeStudent = (net_id) => (params = {}) => { + axios.get(`/api/admin/regrade/student/${net_id}`, {params}).then((response) => { + const data = standardStatusHandler(response, enqueueSnackbar); + if (data.session?.state === 'autograding') { + enqueueSnackbar('Autograding student', {variant: 'success'}); + history.push(`/admin/autograde/${data.session.id}`); + } + }).catch((error) => { + console.log(error); standardErrorHandler(enqueueSnackbar); + }); + }; + const autograde = () => { + autogradeStudent(value)(); + setOpen(false); + }; return (
@@ -113,7 +128,7 @@ export default function Assignments() { Assignment Management - + - +
From 5a1f90030fe907e7f130c9cdc84414e87f0bb83d Mon Sep 17 00:00:00 2001 From: dolf321 Date: Mon, 22 May 2023 17:37:55 +0400 Subject: [PATCH 13/38] ADD status for student regrade --- api/anubis/views/admin/regrade.py | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/api/anubis/views/admin/regrade.py b/api/anubis/views/admin/regrade.py index 544d7786..ac306477 100644 --- a/api/anubis/views/admin/regrade.py +++ b/api/anubis/views/admin/regrade.py @@ -60,6 +60,51 @@ def admin_regrade_status(assignment: Assignment): } ) +# +@regrade.route("/status/student/") +@require_admin() +@json_response +def admin_regrade_status(netid: str): + """ + Get the autograde status for astudent. The status + is some high level stats the proportion of submissions + within the assignments for that student that have been processed + + :param netid: + :return: + """ + # Get the student + student: User = User.query.filter(User.netid == netid).first() + + # Verify the student exists + req_assert(student is not None, message="student does not exist") + + # Assert that the course exists + assert_course_context(student) + + # Get the number of submissions that are being processed + processing = Submission.query.filter( + Submission.owner_id == student.id, + Submission.processed == False + ).count() + + # Get the total number of submissions + total = Submission.query.filter( + Submission.owner_id == student.id, + ).count() + + # Calculate the percent of submissions that have been processed + percent = math.ceil(((total - processing) / total) * 100) if total > 0 else 0 + + # Return the status + return success_response( + { + "percent": f"{percent}% of submissions processed", + "processing": processing, + "processed": total - processing, + "total": total, + } + ) @regrade.route("/submission/") @require_admin() From 42c6dd21562d26a1aa3ecab64ba4df8617bc7121 Mon Sep 17 00:00:00 2001 From: dolf321 Date: Mon, 22 May 2023 17:38:42 +0400 Subject: [PATCH 14/38] ADD pytest-timeout to requirements for testing ``` --- api/requirements/dev.in | 1 + api/requirements/dev.txt | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/api/requirements/dev.in b/api/requirements/dev.in index 5126d475..eb0625d6 100644 --- a/api/requirements/dev.in +++ b/api/requirements/dev.in @@ -11,6 +11,7 @@ pip-tools # Python testing framework pytest +pytest-timeout coverage requests diff --git a/api/requirements/dev.txt b/api/requirements/dev.txt index ab974a8a..577e734f 100644 --- a/api/requirements/dev.txt +++ b/api/requirements/dev.txt @@ -299,6 +299,10 @@ pyparsing==3.0.9 pyproject-hooks==1.0.0 # via build pytest==7.3.1 + # via + # -r requirements/dev.in + # pytest-timeout +pytest-timeout==2.1.0 # via -r requirements/dev.in python-dateutil==2.8.2 # via From d98b974ccd88fdb0a46f7d3549d1c040dbb9beb0 Mon Sep 17 00:00:00 2001 From: dolf321 Date: Mon, 22 May 2023 17:38:42 +0400 Subject: [PATCH 15/38] ADD regrading tests --- api/anubis/views/admin/regrade.py | 4 ++-- api/tests/test_regrade_admin.py | 30 ++++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/api/anubis/views/admin/regrade.py b/api/anubis/views/admin/regrade.py index ac306477..4509ca1f 100644 --- a/api/anubis/views/admin/regrade.py +++ b/api/anubis/views/admin/regrade.py @@ -64,9 +64,9 @@ def admin_regrade_status(assignment: Assignment): @regrade.route("/status/student/") @require_admin() @json_response -def admin_regrade_status(netid: str): +def admin_regrade_status_student(netid: str): """ - Get the autograde status for astudent. The status + Get the autograde status for a student. The status is some high level stats the proportion of submissions within the assignments for that student that have been processed diff --git a/api/tests/test_regrade_admin.py b/api/tests/test_regrade_admin.py index c740aaf6..95fe7261 100644 --- a/api/tests/test_regrade_admin.py +++ b/api/tests/test_regrade_admin.py @@ -1,8 +1,8 @@ from utils import Session, permission_test, with_context +import pytest, time from anubis.models import Submission - @with_context def get_student_submission_commit(assignment_ids): for assignment_id in assignment_ids: @@ -13,7 +13,6 @@ def get_student_submission_commit(assignment_ids): if submission is not None: return submission.commit, assignment_id - def test_regrade_admin(): superuser = Session("superuser") assignments = superuser.get("/admin/assignments/list")["assignments"] @@ -23,3 +22,30 @@ def test_regrade_admin(): permission_test(f"/admin/regrade/status/{assignment_id}") permission_test(f"/admin/regrade/submission/{commit}") permission_test(f"/admin/regrade/assignment/{assignment_id}") + +@pytest.mark.timeout(40) +def test_regrade_admin_student(): + superuser = Session("superuser") + # Get list of students + students = superuser.get("/admin/students/list")["students"] + assert len(students) > 0 + student_netid = students[0]["netid"] + + # Permission tests + permission_test(f"/admin/regrade/student/{student_netid}") + permission_test(f"/admin/regrade/status/student/{student_netid}") + + # Test Submissions pipeline + resp = superuser.get(f"/admin/regrade/student/{student_netid}") + assert resp["status"] == "Regrade enqueued." + + # get the status of the regrade for a student and make sure they get fully procesed + status = superuser.get(f"/admin/regrade/status/student/{student_netid}") + + # make sure total is greater than 0 (meaning it has actually been enqueued) + assert status["total"] > 0 + + while (status["processed"] !=status["total"]): + time.sleep(5) + status = superuser.get(f"/admin/regrade/status/student/{student_netid}") + assert status["processed"] == status["total"] From 35ca641c8424e65e92e6c35a4c3fefeb45f71780 Mon Sep 17 00:00:00 2001 From: dolf321 Date: Sun, 16 Jul 2023 14:23:52 +0300 Subject: [PATCH 16/38] ADD Writing a post functionality --- .../forums/CreateDialog/CreateDialog.jsx | 87 +++++++--- .../CreateDialog/CreateDialog.styles.jsx | 29 +++- .../forums/CreateDialog/TextEditor.css | 22 +++ .../CreateDialog/Toolbar/EditorToolbar.jsx | 154 ++++++++++++++++++ web/src/pages/forums/Forum/Forum.jsx | 5 +- 5 files changed, 271 insertions(+), 26 deletions(-) create mode 100644 web/src/components/forums/CreateDialog/TextEditor.css create mode 100644 web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx diff --git a/web/src/components/forums/CreateDialog/CreateDialog.jsx b/web/src/components/forums/CreateDialog/CreateDialog.jsx index 43bd5ab2..5b5d7a2c 100644 --- a/web/src/components/forums/CreateDialog/CreateDialog.jsx +++ b/web/src/components/forums/CreateDialog/CreateDialog.jsx @@ -1,61 +1,102 @@ -import React, {useState} from 'react'; +import React, {useState, useRef} from 'react'; import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; import Box from '@mui/material/Box'; import Input from '@mui/material/Input'; -import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import Button from '@mui/material/Button'; import Switch from '@mui/material/Switch'; +import EditorToolbar from './Toolbar/EditorToolbar'; +import {Editor, EditorState, RichUtils, convertToRaw} from 'draft-js'; +import CloseIcon from '@mui/icons-material/Close'; +import IconButton from '@mui/material/IconButton'; -import {Editor, EditorState} from 'draft-js'; - +import 'draft-js/dist/Draft.css'; +import './TextEditor.css'; import {useStyles} from './CreateDialog.styles'; export default function CreateDialog({ mode = 'post', - isOpen = false, + open = false, + setOpen, handleCreatePost, }) { + // MUI theme-based css styles const classes = useStyles(); + + // Form Data const [title, setTitle] = useState(''); - const [content, setContent] = useState(''); const [isVisibleToStudents, setIsVisisbleToStudents] = useState(true); const [isAnonymous, setIsAnonymous] = useState(false); - const [error, setError] = useState(''); + const [editorState, setEditorState] = useState(EditorState.createEmpty()); + const editor = useRef(null); + const [error, setError] = useState(''); const validatePost = () => { if (title && content) { handleCreatePost({ title: title, - content: content, + content: JSON.stringify(convertToRaw(editorState.getCurrentContent())), visible_to_students: isVisibleToStudents, anonymous: isAnonymous, }); }; }; + const handleKeyCommand = (command) => { + const newState = RichUtils.handleKeyCommand(editorState, command); + if (newState) { + setEditorState(newState); + return true; + } + return false; + }; + return ( {error} - - {mode === 'post' ? 'Create a new post' : 'Create a new comment'} - - setTitle(e.target.value)} placeholder={'Post Title'} /> - setContent(e.target.value)}/> -
-

Visibile to Students ?

- setIsVisisbleToStudents(!isVisibleToStudents)}/> -
-
-

Anonymous ?

- setIsAnonymous(!isAnonymous)}/> -
- + + + {mode === 'post' ? 'Create A New Post' : 'Create A New Comment'} + + setOpen(false)}> + + + + + + + setTitle(e.target.value)} placeholder={'Put Title Here'} /> +
+ +
+ { + setEditorState(editorState); + }} + /> + +
+

Visibile to Students?

+ setIsVisisbleToStudents(!isVisibleToStudents)}/> +
+
+

Anonymous?

+ setIsAnonymous(!isAnonymous)}/> +
+
+
+
); } diff --git a/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx b/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx index c5962e32..3656c7ef 100644 --- a/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx +++ b/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx @@ -1,6 +1,6 @@ import makeStyles from '@mui/styles/makeStyles'; -export const useStyles = makeStyles(() => ({ +export const useStyles = makeStyles((theme) => ({ root: { width: '800px', }, @@ -8,5 +8,32 @@ export const useStyles = makeStyles(() => ({ display: 'flex', alignItems: 'center', }, + editorContainer: { + display: 'block', + height: '400px', + margin: theme.spacing(10), + padding: theme.spacing(1), + }, + buttonIcon: { + color: theme.palette.primary.main, + }, + toolbarContainer: { + borderColor: theme.palette.white, + borderTop: theme.spacing(0.1) + ' solid', + borderBottom: theme.spacing(0.1) + ' solid', + }, + submit: { + borderTopLeftRadius: 0, + borderTopRightRadius: 0, + }, + titleContainer: { + backgroundColor: `${theme.palette.primary.main}80`, + padding: theme.spacing(1.5), + }, + inputTitle: { + padding: theme.spacing(1.5, 1, 1.5, 1), + fontSize: '1rem', + borderBottom: '0', // Not working + }, })); diff --git a/web/src/components/forums/CreateDialog/TextEditor.css b/web/src/components/forums/CreateDialog/TextEditor.css new file mode 100644 index 00000000..c35038eb --- /dev/null +++ b/web/src/components/forums/CreateDialog/TextEditor.css @@ -0,0 +1,22 @@ +.DraftEditor-editorContainer { + height: 15rem; + padding: 0.8rem; + margin-bottom: 10px; + overflow-y: auto; + overflow-x: hidden; +} + +::-webkit-scrollbar { + height: 4px; + width: 6px; + scrollbar-color: white white; +} + +::-webkit-scrollbar-thumb { + background: white; + border-radius: 10px; +} + +::-webkit-scrollbar-track { + background: gray; +} \ No newline at end of file diff --git a/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx b/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx new file mode 100644 index 00000000..d4633784 --- /dev/null +++ b/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx @@ -0,0 +1,154 @@ +import React, {useState} from 'react'; +import Box from '@mui/material/Box'; +import {makeStyles} from '@mui/styles'; +import {CssBaseline} from '@mui/material'; +import FormatBoldIcon from '@mui/icons-material/FormatBold'; +import FormatItalicIcon from '@mui/icons-material/FormatItalic'; +import FormatUnderlinedIcon from '@mui/icons-material/FormatUnderlined'; +import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted'; +import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; +import CodeIcon from '@mui/icons-material/Code'; +import ImageIcon from '@mui/icons-material/Image'; // Need draft-js plugin +import IconButton from '@mui/material/IconButton'; + + +import {RichUtils} from 'draft-js'; + +const useStyles = makeStyles((theme) => ({ + root: { + flex: 1, + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + padding: theme.spacing(0), + margin: theme.spacing(0.5, 0.25), + }, + p: { + margin: theme.spacing(0), + }, + buttonIcon: { + // margin: theme.spacing(1, 0, 0, 0), + }, + IconLogo: { + // margin: theme.spacing(0, 0, 0, 0), + translate: 'transform(-50%, -50%)', + padding: theme.spacing(0.1), + }, + // inline: { + // display: 'inline', + // }, + // datePicker: { + // width: 300, + // marginRight: theme.spacing(2), + // }, + // button: { + // marginRight: theme.spacing(1), + // }, +})); + +export default function EditorToolbar({editorState, setEditorState}) { + const classes = useStyles(); + // Create JSON array of the icon and their functions FIX THIS + const tools = [ + {label: 'H1', style: 'header-one', method: 'block'}, + {label: 'H2', style: 'header-two', method: 'block'}, + {label: 'H3', style: 'header-three', method: 'block'}, + { + label: 'bold', + style: 'BOLD', + icon: , + method: 'inline', // Convert to enum? + }, + { + label: 'italic', + style: 'ITALIC', + icon: , + method: 'inline', + }, + { + label: 'underline', + style: 'UNDERLINE', + icon: , + method: 'inline', + }, + // { + // label: 'Blockquote', + // style: 'blockQuote', + // icon: , + // method: 'block', + // }, + { + label: 'Unordered-List', + style: 'unordered-list-item', + method: 'block', + icon: , + }, + { + label: 'Ordered-List', + style: 'ordered-list-item', + method: 'block', + icon: , + }, + // { + // label: 'Code Block', + // style: 'CODEBLOCK', + // icon: , + // method: 'inline', + // }, + // { + // label: 'Uppercase', + // style: 'UPPERCASE', + // method: 'inline', + // }, + // { + // label: 'lowercase', + // style: 'LOWERCASE', + // method: 'inline', + // }, + ]; + const applyStyle = (e, style, method) => { + e.preventDefault(); + (method === 'block') ? setEditorState(RichUtils.toggleBlockType(editorState, style)) : + setEditorState(RichUtils.toggleInlineStyle(editorState, style)); + }; + + const isActive = (style, method) => { + if (method === 'block') { + const selection = editorState.getSelection(); + const blockType = editorState + .getCurrentContent() + .getBlockForKey(selection.getStartKey()) + .getType(); + return blockType === style; + } else { + const currentStyle = editorState.getCurrentInlineStyle(); + return currentStyle.has(style); + } + }; + + return ( + <> + + + {tools.map((tool, id) => ( + + applyStyle(e, tool.style, tool.method)} + onMouseDown={(e) => e.preventDefault()} + > + {/* If the tool has an icon, render it if not render the label */} + {tool.icon || tool.label} + + + ))} + + + ); +}; diff --git a/web/src/pages/forums/Forum/Forum.jsx b/web/src/pages/forums/Forum/Forum.jsx index 0e422e19..1ae5dfb7 100644 --- a/web/src/pages/forums/Forum/Forum.jsx +++ b/web/src/pages/forums/Forum/Forum.jsx @@ -32,7 +32,7 @@ export default function Forum({user}) { const [selectedPost, setSelectedPost] = useState(undefined); const [selectedContent, setSelectedContent] = useState(undefined); - const [isDialogOpen, setIsDialogOpen] = useState(undefined); + const [isDialogOpen, setIsDialogOpen] = useState(true); const [dialogMode, setDialogMode] = useState('post'); const [refreshPosts, setRefreshPosts] = useState(0); @@ -108,7 +108,8 @@ export default function Forum({user}) { return ( From b7cb9a3cac120a939376aac53eb001c4d85efe0b Mon Sep 17 00:00:00 2001 From: dolf321 Date: Sun, 16 Jul 2023 19:23:50 +0300 Subject: [PATCH 17/38] ADD image embed functionality --- web/package.json | 4 + .../forums/CreateDialog/CreateDialog.jsx | 35 +++-- .../CreateDialog/CreateDialog.styles.jsx | 12 +- .../forums/CreateDialog/TextEditor.css | 5 +- .../CreateDialog/Toolbar/EditorToolbar.jsx | 138 +++++++----------- .../Toolbar/EditorToolbar.styles.jsx | 19 +++ .../CreateDialog/Toolbar/Embed/EmbedImage.jsx | 110 ++++++++++++++ web/src/pages/forums/Forum/Forum.styles.jsx | 4 + web/yarn.lock | 104 +++++++++++-- 9 files changed, 308 insertions(+), 123 deletions(-) create mode 100644 web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.styles.jsx create mode 100644 web/src/components/forums/CreateDialog/Toolbar/Embed/EmbedImage.jsx diff --git a/web/package.json b/web/package.json index 957ad9df..250a0b0a 100644 --- a/web/package.json +++ b/web/package.json @@ -5,6 +5,9 @@ "dependencies": { "@date-io/core": "^1.3.6", "@date-io/date-fns": "^1.3.13", + "@draft-js-plugins/editor": "^4.1.3", + "@draft-js-plugins/image": "^4.1.3", + "@draft-js-plugins/resizeable": "^5.0.3", "@emotion/react": "^11.9.3", "@emotion/styled": "^11.9.3", "@mui/icons-material": "^5.8.4", @@ -31,6 +34,7 @@ "react-device-detect": "^2.2.2", "react-diff-view": "^2.4.10", "react-dom": "^18.2.0", + "react-draft-wysiwyg": "^1.15.0", "react-markdown": "^5.0.2", "react-router-dom": "^5.1.2", "react-scripts": "^5.0.1", diff --git a/web/src/components/forums/CreateDialog/CreateDialog.jsx b/web/src/components/forums/CreateDialog/CreateDialog.jsx index 5b5d7a2c..b8fbe7dd 100644 --- a/web/src/components/forums/CreateDialog/CreateDialog.jsx +++ b/web/src/components/forums/CreateDialog/CreateDialog.jsx @@ -1,21 +1,21 @@ import React, {useState, useRef} from 'react'; - -import Dialog from '@mui/material/Dialog'; -import DialogTitle from '@mui/material/DialogTitle'; -import DialogContent from '@mui/material/DialogContent'; -import Box from '@mui/material/Box'; -import Input from '@mui/material/Input'; -import Typography from '@mui/material/Typography'; -import Button from '@mui/material/Button'; -import Switch from '@mui/material/Switch'; +import {Dialog, DialogTitle, DialogContent, Box, Input, Typography, Button, Switch, IconButton} from '@mui/material'; import EditorToolbar from './Toolbar/EditorToolbar'; -import {Editor, EditorState, RichUtils, convertToRaw} from 'draft-js'; +import {EditorState, RichUtils, convertToRaw} from 'draft-js'; +import Editor, {composeDecorators} from '@draft-js-plugins/editor'; +import createResizablePlugin from '@draft-js-plugins/resizeable'; +import createImagePlugin from '@draft-js-plugins/image'; import CloseIcon from '@mui/icons-material/Close'; -import IconButton from '@mui/material/IconButton'; +import {useStyles} from './CreateDialog.styles'; import 'draft-js/dist/Draft.css'; import './TextEditor.css'; -import {useStyles} from './CreateDialog.styles'; + +const resizeablePlugin = createResizablePlugin(); +const decorator = composeDecorators( + resizeablePlugin.decorator, +); +const imagePlugin = createImagePlugin({decorator}); export default function CreateDialog({ mode = 'post', @@ -35,10 +35,11 @@ export default function CreateDialog({ const [error, setError] = useState(''); const validatePost = () => { + const content = JSON.stringify(convertToRaw(editorState.getCurrentContent())); if (title && content) { handleCreatePost({ title: title, - content: JSON.stringify(convertToRaw(editorState.getCurrentContent())), + content: content, visible_to_students: isVisibleToStudents, anonymous: isAnonymous, }); @@ -59,6 +60,7 @@ export default function CreateDialog({ isFullScreen open={open} classes={{paper: classes.root}} + onClose={() => setOpen(false)} > {error} @@ -72,10 +74,10 @@ export default function CreateDialog({ - setTitle(e.target.value)} placeholder={'Put Title Here'} />
- +
{ setEditorState(editorState); }} + plugins={[imagePlugin, resizeablePlugin]} /> - +

Visibile to Students?

setIsVisisbleToStudents(!isVisibleToStudents)}/> diff --git a/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx b/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx index 3656c7ef..09102638 100644 --- a/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx +++ b/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx @@ -7,12 +7,8 @@ export const useStyles = makeStyles((theme) => ({ switchContainer: { display: 'flex', alignItems: 'center', - }, - editorContainer: { - display: 'block', - height: '400px', - margin: theme.spacing(10), - padding: theme.spacing(1), + margin: theme.spacing(0), + padding: theme.spacing(0), }, buttonIcon: { color: theme.palette.primary.main, @@ -29,11 +25,11 @@ export const useStyles = makeStyles((theme) => ({ titleContainer: { backgroundColor: `${theme.palette.primary.main}80`, padding: theme.spacing(1.5), + fontSize: '1rem', }, inputTitle: { padding: theme.spacing(1.5, 1, 1.5, 1), - fontSize: '1rem', - borderBottom: '0', // Not working + fontSize: '2.25rem', }, })); diff --git a/web/src/components/forums/CreateDialog/TextEditor.css b/web/src/components/forums/CreateDialog/TextEditor.css index c35038eb..7cf6acf8 100644 --- a/web/src/components/forums/CreateDialog/TextEditor.css +++ b/web/src/components/forums/CreateDialog/TextEditor.css @@ -1,9 +1,10 @@ .DraftEditor-editorContainer { - height: 15rem; - padding: 0.8rem; + height: 20rem; + padding: 1rem; margin-bottom: 10px; overflow-y: auto; overflow-x: hidden; + background-color: rgb(0, 0, 0, 0.1); } ::-webkit-scrollbar { diff --git a/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx b/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx index d4633784..d411aeea 100644 --- a/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx +++ b/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx @@ -1,132 +1,101 @@ -import React, {useState} from 'react'; -import Box from '@mui/material/Box'; -import {makeStyles} from '@mui/styles'; -import {CssBaseline} from '@mui/material'; -import FormatBoldIcon from '@mui/icons-material/FormatBold'; -import FormatItalicIcon from '@mui/icons-material/FormatItalic'; -import FormatUnderlinedIcon from '@mui/icons-material/FormatUnderlined'; -import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted'; -import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; -import CodeIcon from '@mui/icons-material/Code'; -import ImageIcon from '@mui/icons-material/Image'; // Need draft-js plugin -import IconButton from '@mui/material/IconButton'; - - +import React from 'react'; +import {Box, CssBaseline, IconButton} from '@mui/material'; +import { + FormatBold as FormatBoldIcon, + FormatItalic as FormatItalicIcon, + FormatUnderlined as FormatUnderlinedIcon, + FormatListBulleted as FormatListBulletedIcon, + FormatListNumbered as FormatListNumberedIcon, +} from '@mui/icons-material'; +import EmbedImage from './Embed/EmbedImage'; +import {useStyles} from './EditorToolbar.styles'; import {RichUtils} from 'draft-js'; -const useStyles = makeStyles((theme) => ({ - root: { - flex: 1, - display: 'flex', - flexDirection: 'row', - justifyContent: 'flex-start', - alignItems: 'center', - padding: theme.spacing(0), - margin: theme.spacing(0.5, 0.25), - }, - p: { - margin: theme.spacing(0), - }, - buttonIcon: { - // margin: theme.spacing(1, 0, 0, 0), - }, - IconLogo: { - // margin: theme.spacing(0, 0, 0, 0), - translate: 'transform(-50%, -50%)', - padding: theme.spacing(0.1), - }, - // inline: { - // display: 'inline', - // }, - // datePicker: { - // width: 300, - // marginRight: theme.spacing(2), - // }, - // button: { - // marginRight: theme.spacing(1), - // }, -})); +const MODES = { + INLINE: 'inline', + BLOCK: 'block', +}; -export default function EditorToolbar({editorState, setEditorState}) { +export default function EditorToolbar({editorState, setEditorState, imagePlugin}) { const classes = useStyles(); - // Create JSON array of the icon and their functions FIX THIS + + // Create JSON array of the icon and their functions const tools = [ - {label: 'H1', style: 'header-one', method: 'block'}, - {label: 'H2', style: 'header-two', method: 'block'}, - {label: 'H3', style: 'header-three', method: 'block'}, + { + label: 'H1', + style: 'header-one', + method: MODES.BLOCK, + }, + { + label: 'H2', + style: 'header-two', + method: MODES.BLOCK, + }, + { + label: 'H3', + style: 'header-three', + method: MODES.BLOCK, + }, { label: 'bold', style: 'BOLD', icon: , - method: 'inline', // Convert to enum? + method: MODES.INLINE, }, { label: 'italic', style: 'ITALIC', icon: , - method: 'inline', + method: MODES.INLINE, }, { label: 'underline', style: 'UNDERLINE', icon: , - method: 'inline', + method: MODES.INLINE, }, - // { - // label: 'Blockquote', - // style: 'blockQuote', - // icon: , - // method: 'block', - // }, { - label: 'Unordered-List', + label: 'unordered-list', style: 'unordered-list-item', - method: 'block', + method: MODES.BLOCK, icon: , }, { - label: 'Ordered-List', + label: 'ordered-list', style: 'ordered-list-item', - method: 'block', + method: MODES.BLOCK, icon: , }, - // { - // label: 'Code Block', - // style: 'CODEBLOCK', - // icon: , - // method: 'inline', - // }, - // { - // label: 'Uppercase', - // style: 'UPPERCASE', - // method: 'inline', - // }, - // { - // label: 'lowercase', - // style: 'LOWERCASE', - // method: 'inline', - // }, ]; + const applyStyle = (e, style, method) => { e.preventDefault(); - (method === 'block') ? setEditorState(RichUtils.toggleBlockType(editorState, style)) : + if (method === MODES.BLOCK) { + setEditorState(RichUtils.toggleBlockType(editorState, style)); + } else if (method === MODES.INLINE) { setEditorState(RichUtils.toggleInlineStyle(editorState, style)); + } }; const isActive = (style, method) => { - if (method === 'block') { + if (method === MODES.BLOCK) { const selection = editorState.getSelection(); const blockType = editorState .getCurrentContent() .getBlockForKey(selection.getStartKey()) .getType(); return blockType === style; - } else { + } else if (method === MODES.INLINE) { const currentStyle = editorState.getCurrentInlineStyle(); return currentStyle.has(style); } }; + const insertImage = (url) => { + console.log('inserting image'); + setEditorState(imagePlugin.addImage(editorState, url)); + }; + return ( <> @@ -140,7 +109,7 @@ export default function EditorToolbar({editorState, setEditorState}) { className={classes.buttonIcon} key={id} size="small" - onClick={(e) => applyStyle(e, tool.style, tool.method)} + onClick={(e) => applyStyle(e, tool.style, tool.method)} // Open modal for image onMouseDown={(e) => e.preventDefault()} > {/* If the tool has an icon, render it if not render the label */} @@ -148,6 +117,7 @@ export default function EditorToolbar({editorState, setEditorState}) { ))} + ); diff --git a/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.styles.jsx b/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.styles.jsx new file mode 100644 index 00000000..f24dd612 --- /dev/null +++ b/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.styles.jsx @@ -0,0 +1,19 @@ +import makeStyles from '@mui/styles/makeStyles'; +export const useStyles = makeStyles((theme) => ({ + root: { + flex: 1, + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + padding: theme.spacing(0), + margin: theme.spacing(0.5, 0.25), + }, + p: { + margin: theme.spacing(0), + }, + IconLogo: { + translate: 'transform(-50%, -50%)', + padding: theme.spacing(0.1), + }, +})); diff --git a/web/src/components/forums/CreateDialog/Toolbar/Embed/EmbedImage.jsx b/web/src/components/forums/CreateDialog/Toolbar/Embed/EmbedImage.jsx new file mode 100644 index 00000000..18dbd93a --- /dev/null +++ b/web/src/components/forums/CreateDialog/Toolbar/Embed/EmbedImage.jsx @@ -0,0 +1,110 @@ +import React, {useState} from 'react'; +import {Box, Button, DialogTitle, DialogContent, Input, IconButton} from '@mui/material'; +import ImageIcon from '@mui/icons-material/Image'; +import {makeStyles} from '@mui/styles'; +import Popover from '@mui/material/Popover'; + +export const useStyles = makeStyles((theme) => ({ + button: { + margin: theme.spacing(0.5), + }, +})); + +export default function EmbedImage({embedImage}) { + const [clicked, setClicked] = useState(false); + const [imageURL, setImageURL] = useState(''); + const [anchorEl, setAnchorEl] = React.useState(null); + const classes = useStyles(); + + const closeModal = () => { + setClicked(false); + setImageURL(''); + setAnchorEl(null); + }; + + const openModal = (e) => { + setClicked(true); + setAnchorEl(e.currentTarget); + }; + + const handleEmbedImage = () => { + if (!imageURL) { + return; + } + embedImage(imageURL); + closeModal(); + }; + + const uploadImage = (file) => { + const data = new FormData(); + data.append('file', file); + axios.post(`/api/public/forums/post/image`, data) + .then((image_url) => { + // Wait for image url of uploaded image to be returned + return image_url.data; + }) + .catch(standardErrorHandler(enqueueSnackbar)); + }; + + const handleFileUpload = (e) => { + const file = e.target.files[0]; + if (!file) { + return; + } + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onloadend = () => { + // const image_url = uploadImage(file); + embedImage(image_url); + }; + closeModal(); + }; + + return ( + + e.preventDefault()} + > + + + + + {'Embed Image'} + + + + setImageURL(e.target.value)} + /> + + +

OR

+ +
+
+
+
+ ); +}; diff --git a/web/src/pages/forums/Forum/Forum.styles.jsx b/web/src/pages/forums/Forum/Forum.styles.jsx index ec54d43b..bc0548b8 100644 --- a/web/src/pages/forums/Forum/Forum.styles.jsx +++ b/web/src/pages/forums/Forum/Forum.styles.jsx @@ -38,6 +38,10 @@ export const useStyles = makeStyles((theme) => ({ paddingTop: theme.spacing(1), paddingBottom: theme.spacing(1), borderRadius: '2px', + color: theme.palette.white, + '&:hover': { + color: theme.palette.primary.main, + }, }, postsContainer: { marginTop: theme.spacing(2), diff --git a/web/yarn.lock b/web/yarn.lock index 8a84e751..83186b60 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -1250,6 +1250,46 @@ dependencies: "@date-io/core" "^2.14.0" +"@draft-js-plugins/alignment@^5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@draft-js-plugins/alignment/-/alignment-5.0.3.tgz#25e133375accf3b34f9470a355568de95173d25f" + integrity sha512-Zw6AqI5mjqKLXPez+e9sKPQgsr+OZ9GJzJl4gsf02QLBzh5JHyzCRzcfB/eSVgJl4JJnudbG4cvdiWJny9AyCw== + dependencies: + "@draft-js-plugins/buttons" "^4.0.0" + "@draft-js-plugins/utils" "^4.0.0" + +"@draft-js-plugins/buttons@^4.0.0": + version "4.3.2" + resolved "https://registry.yarnpkg.com/@draft-js-plugins/buttons/-/buttons-4.3.2.tgz#053e3ca3be345034f22c4ba040f97cf1a41f3aef" + integrity sha512-KEBp37yCfJDZamAcUHJE0Q1gH2voqt+DMPvzmI2Th6Xz0El/cdcOoAAgX3JPqDzWfd4sc7dDhm/77r9HmO8wIA== + dependencies: + clsx "^1.2.1" + +"@draft-js-plugins/editor@^4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@draft-js-plugins/editor/-/editor-4.1.3.tgz#460d70d3b639d17f4e1f15b7d180b8c8698b1ec1" + integrity sha512-Mxr1GUFzN0VcVVP6XClvWS2o33Rh8ZG3l41h9l27hJoLRYVywpuzI6TXD0UFYDIHX50fz6n70sQCdoHmkBsfKQ== + dependencies: + immutable "~3.7.4" + prop-types "^15.8.1" + +"@draft-js-plugins/image@^4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@draft-js-plugins/image/-/image-4.1.3.tgz#2a90f9629b5784fb01ad4bc92c2b8125598dc5b5" + integrity sha512-/+E/dx/6GvcWiw7ViIaeKkVTLJm9cDejZu1tpinb53Cl4iwMT7hN3pwLE270MYcctj5ZdwjleRuMDnx4l/mDtw== + dependencies: + clsx "^1.0.4" + +"@draft-js-plugins/resizeable@^5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@draft-js-plugins/resizeable/-/resizeable-5.0.3.tgz#e4ff0a1b733bb1f6ce9a9aa32b5f2fca7c756f8e" + integrity sha512-aqhIRIL17C+MqMrZJgNakVrbNTeVsiNtIQszP6VcoPiVPm9RUO12aQAX1c0MfVeKbh9qk0JwNpHglSGd9LIUeA== + +"@draft-js-plugins/utils@^4.0.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@draft-js-plugins/utils/-/utils-4.2.0.tgz#ee3c643c94d548fd9460541881a7554c5b077988" + integrity sha512-tQhf7a0S9ZDtOb0Mje5a+xTOT6LVDRv2LueEqqVdX4aI+oh0jjkAuP0oTiZBv6h7K9XSPgsQ08q9+kwHc3EyUg== + "@emotion/babel-plugin@^11.7.1": version "11.9.2" resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz#723b6d394c89fb2ef782229d92ba95a740576e95" @@ -3660,7 +3700,7 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -clsx@^1.1.0, clsx@^1.1.1, clsx@^1.2.0, clsx@^1.2.1: +clsx@^1.0.4, clsx@^1.1.0, clsx@^1.1.1, clsx@^1.2.0, clsx@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== @@ -3857,11 +3897,16 @@ core-js-pure@^3.23.3, core-js-pure@^3.25.1: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.26.0.tgz#7ad8a5dd7d910756f3124374b50026e23265ca9a" integrity sha512-LiN6fylpVBVwT8twhhluD9TzXmZQQsr2I2eIKtWNbZI1XMfBT7CV18itaN6RA7EtQd/SDdRx/wzvAShX2HvhQA== -core-js@^3.19.2, core-js@^3.6.4: +core-js@^3.19.2: version "3.26.0" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.26.0.tgz#a516db0ed0811be10eac5d94f3b8463d03faccfe" integrity sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw== +core-js@^3.6.4: + version "3.31.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.31.1.tgz#f2b0eea9be9da0def2c5fece71064a7e5d687653" + integrity sha512-2sKLtfq1eFST7l7v62zaqXacPc7uG8ZAya8ogijLhTtaKNcpzpB4TMoTw2Si+8GYKRwFPMMtUT0263QFWFfqyQ== + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -3890,11 +3935,11 @@ cosmiconfig@^7.0.0: yaml "^1.10.0" cross-fetch@^3.0.4: - version "3.1.5" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" - integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + version "3.1.8" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" + integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== dependencies: - node-fetch "2.6.7" + node-fetch "^2.6.12" cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" @@ -4610,6 +4655,11 @@ draft-js@^0.11.7: immutable "~3.7.4" object-assign "^4.1.1" +draftjs-utils@^0.10.2: + version "0.10.2" + resolved "https://registry.yarnpkg.com/draftjs-utils/-/draftjs-utils-0.10.2.tgz#a7f16d2c1c174ac38ba3bbf700c256f176b2699c" + integrity sha512-EstHqr3R3JVcilJrBaO/A+01GvwwKmC7e4TCjC7S94ZeMh4IVmf60OuQXtHHpwItK8C2JCi3iljgN5KHkJboUg== + duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -5912,6 +5962,11 @@ html-tags@1: resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-1.2.0.tgz#c78de65b5663aa597989dd2b7ab49200d7e4db98" integrity sha512-uVteDXUCs08M7QJx0eY6ue7qQztwIfknap81vAtNob2sdEPKa8PjPinx0vxbs2JONPamovZjMvKZWNW44/PBKg== +html-to-draftjs@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/html-to-draftjs/-/html-to-draftjs-1.5.0.tgz#0df0eabf429deaedb63f5c859889e2c983606e86" + integrity sha512-kggLXBNciKDwKf+KYsuE+V5gw4dZ7nHyGMX9m0wy7urzWjKGWyNFetmArRLvRV0VrxKN70WylFsJvMTJx02OBQ== + html-to-react@^1.3.4: version "1.5.0" resolved "https://registry.yarnpkg.com/html-to-react/-/html-to-react-1.5.0.tgz#6e0cf47ae1b091ba2f28a3832389fbce4d199ccc" @@ -7358,6 +7413,13 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +linkify-it@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf" + integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw== + dependencies: + uc.micro "^1.0.1" + loader-runner@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" @@ -8189,10 +8251,10 @@ node-dir@^0.1.17: dependencies: minimatch "^3.0.2" -node-fetch@2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== +node-fetch@^2.6.12: + version "2.6.12" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba" + integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g== dependencies: whatwg-url "^5.0.0" @@ -9470,6 +9532,17 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-draft-wysiwyg@^1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/react-draft-wysiwyg/-/react-draft-wysiwyg-1.15.0.tgz#d5b4173991033859b9e161c883889ddc00909a57" + integrity sha512-p1cYZcWc6/ALFBVksbFoCM3b29fGQDlZLIMrXng0TU/UElxIOF2/AWWo4L5auIYVhmqKTZ0NkNjnXOzGGuxyeA== + dependencies: + classnames "^2.2.6" + draftjs-utils "^0.10.2" + html-to-draftjs "^1.5.0" + linkify-it "^2.2.0" + prop-types "^15.7.2" + react-error-overlay@^6.0.11: version "6.0.11" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" @@ -10988,15 +11061,20 @@ typedarray-to-buffer@^3.1.5: is-typedarray "^1.0.0" ua-parser-js@^0.7.18: - version "0.7.31" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6" - integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ== + version "0.7.35" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.35.tgz#8bda4827be4f0b1dda91699a29499575a1f1d307" + integrity sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g== ua-parser-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.2.tgz#e2976c34dbfb30b15d2c300b2a53eac87c57a775" integrity sha512-00y/AXhx0/SsnI51fTc0rLRmafiGOM4/O+ny10Ps7f+j/b8p/ZY11ytMgznXkOVo4GQ+KwQG5UQLkLGirsACRg== +uc.micro@^1.0.1: + version "1.0.6" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" From e0a1e58cebd702932fbf1e451dec3461b8da2a0f Mon Sep 17 00:00:00 2001 From: dolf321 Date: Sun, 16 Jul 2023 14:23:52 +0300 Subject: [PATCH 18/38] ADD Writing a post functionality --- .../forums/CreateDialog/CreateDialog.jsx | 87 +++++++--- .../CreateDialog/CreateDialog.styles.jsx | 29 +++- .../forums/CreateDialog/TextEditor.css | 22 +++ .../CreateDialog/Toolbar/EditorToolbar.jsx | 154 ++++++++++++++++++ web/src/pages/forums/Forum/Forum.jsx | 5 +- 5 files changed, 271 insertions(+), 26 deletions(-) create mode 100644 web/src/components/forums/CreateDialog/TextEditor.css create mode 100644 web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx diff --git a/web/src/components/forums/CreateDialog/CreateDialog.jsx b/web/src/components/forums/CreateDialog/CreateDialog.jsx index 43bd5ab2..5b5d7a2c 100644 --- a/web/src/components/forums/CreateDialog/CreateDialog.jsx +++ b/web/src/components/forums/CreateDialog/CreateDialog.jsx @@ -1,61 +1,102 @@ -import React, {useState} from 'react'; +import React, {useState, useRef} from 'react'; import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; import Box from '@mui/material/Box'; import Input from '@mui/material/Input'; -import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import Button from '@mui/material/Button'; import Switch from '@mui/material/Switch'; +import EditorToolbar from './Toolbar/EditorToolbar'; +import {Editor, EditorState, RichUtils, convertToRaw} from 'draft-js'; +import CloseIcon from '@mui/icons-material/Close'; +import IconButton from '@mui/material/IconButton'; -import {Editor, EditorState} from 'draft-js'; - +import 'draft-js/dist/Draft.css'; +import './TextEditor.css'; import {useStyles} from './CreateDialog.styles'; export default function CreateDialog({ mode = 'post', - isOpen = false, + open = false, + setOpen, handleCreatePost, }) { + // MUI theme-based css styles const classes = useStyles(); + + // Form Data const [title, setTitle] = useState(''); - const [content, setContent] = useState(''); const [isVisibleToStudents, setIsVisisbleToStudents] = useState(true); const [isAnonymous, setIsAnonymous] = useState(false); - const [error, setError] = useState(''); + const [editorState, setEditorState] = useState(EditorState.createEmpty()); + const editor = useRef(null); + const [error, setError] = useState(''); const validatePost = () => { if (title && content) { handleCreatePost({ title: title, - content: content, + content: JSON.stringify(convertToRaw(editorState.getCurrentContent())), visible_to_students: isVisibleToStudents, anonymous: isAnonymous, }); }; }; + const handleKeyCommand = (command) => { + const newState = RichUtils.handleKeyCommand(editorState, command); + if (newState) { + setEditorState(newState); + return true; + } + return false; + }; + return ( {error} - - {mode === 'post' ? 'Create a new post' : 'Create a new comment'} - - setTitle(e.target.value)} placeholder={'Post Title'} /> - setContent(e.target.value)}/> -
-

Visibile to Students ?

- setIsVisisbleToStudents(!isVisibleToStudents)}/> -
-
-

Anonymous ?

- setIsAnonymous(!isAnonymous)}/> -
- + + + {mode === 'post' ? 'Create A New Post' : 'Create A New Comment'} + + setOpen(false)}> + + + + + + + setTitle(e.target.value)} placeholder={'Put Title Here'} /> +
+ +
+ { + setEditorState(editorState); + }} + /> + +
+

Visibile to Students?

+ setIsVisisbleToStudents(!isVisibleToStudents)}/> +
+
+

Anonymous?

+ setIsAnonymous(!isAnonymous)}/> +
+
+
+
); } diff --git a/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx b/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx index c5962e32..3656c7ef 100644 --- a/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx +++ b/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx @@ -1,6 +1,6 @@ import makeStyles from '@mui/styles/makeStyles'; -export const useStyles = makeStyles(() => ({ +export const useStyles = makeStyles((theme) => ({ root: { width: '800px', }, @@ -8,5 +8,32 @@ export const useStyles = makeStyles(() => ({ display: 'flex', alignItems: 'center', }, + editorContainer: { + display: 'block', + height: '400px', + margin: theme.spacing(10), + padding: theme.spacing(1), + }, + buttonIcon: { + color: theme.palette.primary.main, + }, + toolbarContainer: { + borderColor: theme.palette.white, + borderTop: theme.spacing(0.1) + ' solid', + borderBottom: theme.spacing(0.1) + ' solid', + }, + submit: { + borderTopLeftRadius: 0, + borderTopRightRadius: 0, + }, + titleContainer: { + backgroundColor: `${theme.palette.primary.main}80`, + padding: theme.spacing(1.5), + }, + inputTitle: { + padding: theme.spacing(1.5, 1, 1.5, 1), + fontSize: '1rem', + borderBottom: '0', // Not working + }, })); diff --git a/web/src/components/forums/CreateDialog/TextEditor.css b/web/src/components/forums/CreateDialog/TextEditor.css new file mode 100644 index 00000000..c35038eb --- /dev/null +++ b/web/src/components/forums/CreateDialog/TextEditor.css @@ -0,0 +1,22 @@ +.DraftEditor-editorContainer { + height: 15rem; + padding: 0.8rem; + margin-bottom: 10px; + overflow-y: auto; + overflow-x: hidden; +} + +::-webkit-scrollbar { + height: 4px; + width: 6px; + scrollbar-color: white white; +} + +::-webkit-scrollbar-thumb { + background: white; + border-radius: 10px; +} + +::-webkit-scrollbar-track { + background: gray; +} \ No newline at end of file diff --git a/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx b/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx new file mode 100644 index 00000000..d4633784 --- /dev/null +++ b/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx @@ -0,0 +1,154 @@ +import React, {useState} from 'react'; +import Box from '@mui/material/Box'; +import {makeStyles} from '@mui/styles'; +import {CssBaseline} from '@mui/material'; +import FormatBoldIcon from '@mui/icons-material/FormatBold'; +import FormatItalicIcon from '@mui/icons-material/FormatItalic'; +import FormatUnderlinedIcon from '@mui/icons-material/FormatUnderlined'; +import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted'; +import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; +import CodeIcon from '@mui/icons-material/Code'; +import ImageIcon from '@mui/icons-material/Image'; // Need draft-js plugin +import IconButton from '@mui/material/IconButton'; + + +import {RichUtils} from 'draft-js'; + +const useStyles = makeStyles((theme) => ({ + root: { + flex: 1, + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + padding: theme.spacing(0), + margin: theme.spacing(0.5, 0.25), + }, + p: { + margin: theme.spacing(0), + }, + buttonIcon: { + // margin: theme.spacing(1, 0, 0, 0), + }, + IconLogo: { + // margin: theme.spacing(0, 0, 0, 0), + translate: 'transform(-50%, -50%)', + padding: theme.spacing(0.1), + }, + // inline: { + // display: 'inline', + // }, + // datePicker: { + // width: 300, + // marginRight: theme.spacing(2), + // }, + // button: { + // marginRight: theme.spacing(1), + // }, +})); + +export default function EditorToolbar({editorState, setEditorState}) { + const classes = useStyles(); + // Create JSON array of the icon and their functions FIX THIS + const tools = [ + {label: 'H1', style: 'header-one', method: 'block'}, + {label: 'H2', style: 'header-two', method: 'block'}, + {label: 'H3', style: 'header-three', method: 'block'}, + { + label: 'bold', + style: 'BOLD', + icon: , + method: 'inline', // Convert to enum? + }, + { + label: 'italic', + style: 'ITALIC', + icon: , + method: 'inline', + }, + { + label: 'underline', + style: 'UNDERLINE', + icon: , + method: 'inline', + }, + // { + // label: 'Blockquote', + // style: 'blockQuote', + // icon: , + // method: 'block', + // }, + { + label: 'Unordered-List', + style: 'unordered-list-item', + method: 'block', + icon: , + }, + { + label: 'Ordered-List', + style: 'ordered-list-item', + method: 'block', + icon: , + }, + // { + // label: 'Code Block', + // style: 'CODEBLOCK', + // icon: , + // method: 'inline', + // }, + // { + // label: 'Uppercase', + // style: 'UPPERCASE', + // method: 'inline', + // }, + // { + // label: 'lowercase', + // style: 'LOWERCASE', + // method: 'inline', + // }, + ]; + const applyStyle = (e, style, method) => { + e.preventDefault(); + (method === 'block') ? setEditorState(RichUtils.toggleBlockType(editorState, style)) : + setEditorState(RichUtils.toggleInlineStyle(editorState, style)); + }; + + const isActive = (style, method) => { + if (method === 'block') { + const selection = editorState.getSelection(); + const blockType = editorState + .getCurrentContent() + .getBlockForKey(selection.getStartKey()) + .getType(); + return blockType === style; + } else { + const currentStyle = editorState.getCurrentInlineStyle(); + return currentStyle.has(style); + } + }; + + return ( + <> + + + {tools.map((tool, id) => ( + + applyStyle(e, tool.style, tool.method)} + onMouseDown={(e) => e.preventDefault()} + > + {/* If the tool has an icon, render it if not render the label */} + {tool.icon || tool.label} + + + ))} + + + ); +}; diff --git a/web/src/pages/forums/Forum/Forum.jsx b/web/src/pages/forums/Forum/Forum.jsx index 0e422e19..1ae5dfb7 100644 --- a/web/src/pages/forums/Forum/Forum.jsx +++ b/web/src/pages/forums/Forum/Forum.jsx @@ -32,7 +32,7 @@ export default function Forum({user}) { const [selectedPost, setSelectedPost] = useState(undefined); const [selectedContent, setSelectedContent] = useState(undefined); - const [isDialogOpen, setIsDialogOpen] = useState(undefined); + const [isDialogOpen, setIsDialogOpen] = useState(true); const [dialogMode, setDialogMode] = useState('post'); const [refreshPosts, setRefreshPosts] = useState(0); @@ -108,7 +108,8 @@ export default function Forum({user}) { return ( From 81ca3f7d7b94e05e28537482946defc4a3480e29 Mon Sep 17 00:00:00 2001 From: dolf321 Date: Sun, 16 Jul 2023 19:23:50 +0300 Subject: [PATCH 19/38] ADD image embed functionality --- web/package.json | 4 + .../forums/CreateDialog/CreateDialog.jsx | 35 +++-- .../CreateDialog/CreateDialog.styles.jsx | 12 +- .../forums/CreateDialog/TextEditor.css | 5 +- .../CreateDialog/Toolbar/EditorToolbar.jsx | 138 +++++++----------- .../Toolbar/EditorToolbar.styles.jsx | 19 +++ .../CreateDialog/Toolbar/Embed/EmbedImage.jsx | 110 ++++++++++++++ web/src/pages/forums/Forum/Forum.styles.jsx | 4 + web/yarn.lock | 104 +++++++++++-- 9 files changed, 308 insertions(+), 123 deletions(-) create mode 100644 web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.styles.jsx create mode 100644 web/src/components/forums/CreateDialog/Toolbar/Embed/EmbedImage.jsx diff --git a/web/package.json b/web/package.json index 957ad9df..250a0b0a 100644 --- a/web/package.json +++ b/web/package.json @@ -5,6 +5,9 @@ "dependencies": { "@date-io/core": "^1.3.6", "@date-io/date-fns": "^1.3.13", + "@draft-js-plugins/editor": "^4.1.3", + "@draft-js-plugins/image": "^4.1.3", + "@draft-js-plugins/resizeable": "^5.0.3", "@emotion/react": "^11.9.3", "@emotion/styled": "^11.9.3", "@mui/icons-material": "^5.8.4", @@ -31,6 +34,7 @@ "react-device-detect": "^2.2.2", "react-diff-view": "^2.4.10", "react-dom": "^18.2.0", + "react-draft-wysiwyg": "^1.15.0", "react-markdown": "^5.0.2", "react-router-dom": "^5.1.2", "react-scripts": "^5.0.1", diff --git a/web/src/components/forums/CreateDialog/CreateDialog.jsx b/web/src/components/forums/CreateDialog/CreateDialog.jsx index 5b5d7a2c..b8fbe7dd 100644 --- a/web/src/components/forums/CreateDialog/CreateDialog.jsx +++ b/web/src/components/forums/CreateDialog/CreateDialog.jsx @@ -1,21 +1,21 @@ import React, {useState, useRef} from 'react'; - -import Dialog from '@mui/material/Dialog'; -import DialogTitle from '@mui/material/DialogTitle'; -import DialogContent from '@mui/material/DialogContent'; -import Box from '@mui/material/Box'; -import Input from '@mui/material/Input'; -import Typography from '@mui/material/Typography'; -import Button from '@mui/material/Button'; -import Switch from '@mui/material/Switch'; +import {Dialog, DialogTitle, DialogContent, Box, Input, Typography, Button, Switch, IconButton} from '@mui/material'; import EditorToolbar from './Toolbar/EditorToolbar'; -import {Editor, EditorState, RichUtils, convertToRaw} from 'draft-js'; +import {EditorState, RichUtils, convertToRaw} from 'draft-js'; +import Editor, {composeDecorators} from '@draft-js-plugins/editor'; +import createResizablePlugin from '@draft-js-plugins/resizeable'; +import createImagePlugin from '@draft-js-plugins/image'; import CloseIcon from '@mui/icons-material/Close'; -import IconButton from '@mui/material/IconButton'; +import {useStyles} from './CreateDialog.styles'; import 'draft-js/dist/Draft.css'; import './TextEditor.css'; -import {useStyles} from './CreateDialog.styles'; + +const resizeablePlugin = createResizablePlugin(); +const decorator = composeDecorators( + resizeablePlugin.decorator, +); +const imagePlugin = createImagePlugin({decorator}); export default function CreateDialog({ mode = 'post', @@ -35,10 +35,11 @@ export default function CreateDialog({ const [error, setError] = useState(''); const validatePost = () => { + const content = JSON.stringify(convertToRaw(editorState.getCurrentContent())); if (title && content) { handleCreatePost({ title: title, - content: JSON.stringify(convertToRaw(editorState.getCurrentContent())), + content: content, visible_to_students: isVisibleToStudents, anonymous: isAnonymous, }); @@ -59,6 +60,7 @@ export default function CreateDialog({ isFullScreen open={open} classes={{paper: classes.root}} + onClose={() => setOpen(false)} > {error} @@ -72,10 +74,10 @@ export default function CreateDialog({ - setTitle(e.target.value)} placeholder={'Put Title Here'} />
- +
{ setEditorState(editorState); }} + plugins={[imagePlugin, resizeablePlugin]} /> - +

Visibile to Students?

setIsVisisbleToStudents(!isVisibleToStudents)}/> diff --git a/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx b/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx index 3656c7ef..09102638 100644 --- a/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx +++ b/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx @@ -7,12 +7,8 @@ export const useStyles = makeStyles((theme) => ({ switchContainer: { display: 'flex', alignItems: 'center', - }, - editorContainer: { - display: 'block', - height: '400px', - margin: theme.spacing(10), - padding: theme.spacing(1), + margin: theme.spacing(0), + padding: theme.spacing(0), }, buttonIcon: { color: theme.palette.primary.main, @@ -29,11 +25,11 @@ export const useStyles = makeStyles((theme) => ({ titleContainer: { backgroundColor: `${theme.palette.primary.main}80`, padding: theme.spacing(1.5), + fontSize: '1rem', }, inputTitle: { padding: theme.spacing(1.5, 1, 1.5, 1), - fontSize: '1rem', - borderBottom: '0', // Not working + fontSize: '2.25rem', }, })); diff --git a/web/src/components/forums/CreateDialog/TextEditor.css b/web/src/components/forums/CreateDialog/TextEditor.css index c35038eb..7cf6acf8 100644 --- a/web/src/components/forums/CreateDialog/TextEditor.css +++ b/web/src/components/forums/CreateDialog/TextEditor.css @@ -1,9 +1,10 @@ .DraftEditor-editorContainer { - height: 15rem; - padding: 0.8rem; + height: 20rem; + padding: 1rem; margin-bottom: 10px; overflow-y: auto; overflow-x: hidden; + background-color: rgb(0, 0, 0, 0.1); } ::-webkit-scrollbar { diff --git a/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx b/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx index d4633784..d411aeea 100644 --- a/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx +++ b/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx @@ -1,132 +1,101 @@ -import React, {useState} from 'react'; -import Box from '@mui/material/Box'; -import {makeStyles} from '@mui/styles'; -import {CssBaseline} from '@mui/material'; -import FormatBoldIcon from '@mui/icons-material/FormatBold'; -import FormatItalicIcon from '@mui/icons-material/FormatItalic'; -import FormatUnderlinedIcon from '@mui/icons-material/FormatUnderlined'; -import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted'; -import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; -import CodeIcon from '@mui/icons-material/Code'; -import ImageIcon from '@mui/icons-material/Image'; // Need draft-js plugin -import IconButton from '@mui/material/IconButton'; - - +import React from 'react'; +import {Box, CssBaseline, IconButton} from '@mui/material'; +import { + FormatBold as FormatBoldIcon, + FormatItalic as FormatItalicIcon, + FormatUnderlined as FormatUnderlinedIcon, + FormatListBulleted as FormatListBulletedIcon, + FormatListNumbered as FormatListNumberedIcon, +} from '@mui/icons-material'; +import EmbedImage from './Embed/EmbedImage'; +import {useStyles} from './EditorToolbar.styles'; import {RichUtils} from 'draft-js'; -const useStyles = makeStyles((theme) => ({ - root: { - flex: 1, - display: 'flex', - flexDirection: 'row', - justifyContent: 'flex-start', - alignItems: 'center', - padding: theme.spacing(0), - margin: theme.spacing(0.5, 0.25), - }, - p: { - margin: theme.spacing(0), - }, - buttonIcon: { - // margin: theme.spacing(1, 0, 0, 0), - }, - IconLogo: { - // margin: theme.spacing(0, 0, 0, 0), - translate: 'transform(-50%, -50%)', - padding: theme.spacing(0.1), - }, - // inline: { - // display: 'inline', - // }, - // datePicker: { - // width: 300, - // marginRight: theme.spacing(2), - // }, - // button: { - // marginRight: theme.spacing(1), - // }, -})); +const MODES = { + INLINE: 'inline', + BLOCK: 'block', +}; -export default function EditorToolbar({editorState, setEditorState}) { +export default function EditorToolbar({editorState, setEditorState, imagePlugin}) { const classes = useStyles(); - // Create JSON array of the icon and their functions FIX THIS + + // Create JSON array of the icon and their functions const tools = [ - {label: 'H1', style: 'header-one', method: 'block'}, - {label: 'H2', style: 'header-two', method: 'block'}, - {label: 'H3', style: 'header-three', method: 'block'}, + { + label: 'H1', + style: 'header-one', + method: MODES.BLOCK, + }, + { + label: 'H2', + style: 'header-two', + method: MODES.BLOCK, + }, + { + label: 'H3', + style: 'header-three', + method: MODES.BLOCK, + }, { label: 'bold', style: 'BOLD', icon: , - method: 'inline', // Convert to enum? + method: MODES.INLINE, }, { label: 'italic', style: 'ITALIC', icon: , - method: 'inline', + method: MODES.INLINE, }, { label: 'underline', style: 'UNDERLINE', icon: , - method: 'inline', + method: MODES.INLINE, }, - // { - // label: 'Blockquote', - // style: 'blockQuote', - // icon: , - // method: 'block', - // }, { - label: 'Unordered-List', + label: 'unordered-list', style: 'unordered-list-item', - method: 'block', + method: MODES.BLOCK, icon: , }, { - label: 'Ordered-List', + label: 'ordered-list', style: 'ordered-list-item', - method: 'block', + method: MODES.BLOCK, icon: , }, - // { - // label: 'Code Block', - // style: 'CODEBLOCK', - // icon: , - // method: 'inline', - // }, - // { - // label: 'Uppercase', - // style: 'UPPERCASE', - // method: 'inline', - // }, - // { - // label: 'lowercase', - // style: 'LOWERCASE', - // method: 'inline', - // }, ]; + const applyStyle = (e, style, method) => { e.preventDefault(); - (method === 'block') ? setEditorState(RichUtils.toggleBlockType(editorState, style)) : + if (method === MODES.BLOCK) { + setEditorState(RichUtils.toggleBlockType(editorState, style)); + } else if (method === MODES.INLINE) { setEditorState(RichUtils.toggleInlineStyle(editorState, style)); + } }; const isActive = (style, method) => { - if (method === 'block') { + if (method === MODES.BLOCK) { const selection = editorState.getSelection(); const blockType = editorState .getCurrentContent() .getBlockForKey(selection.getStartKey()) .getType(); return blockType === style; - } else { + } else if (method === MODES.INLINE) { const currentStyle = editorState.getCurrentInlineStyle(); return currentStyle.has(style); } }; + const insertImage = (url) => { + console.log('inserting image'); + setEditorState(imagePlugin.addImage(editorState, url)); + }; + return ( <> @@ -140,7 +109,7 @@ export default function EditorToolbar({editorState, setEditorState}) { className={classes.buttonIcon} key={id} size="small" - onClick={(e) => applyStyle(e, tool.style, tool.method)} + onClick={(e) => applyStyle(e, tool.style, tool.method)} // Open modal for image onMouseDown={(e) => e.preventDefault()} > {/* If the tool has an icon, render it if not render the label */} @@ -148,6 +117,7 @@ export default function EditorToolbar({editorState, setEditorState}) { ))} + ); diff --git a/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.styles.jsx b/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.styles.jsx new file mode 100644 index 00000000..f24dd612 --- /dev/null +++ b/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.styles.jsx @@ -0,0 +1,19 @@ +import makeStyles from '@mui/styles/makeStyles'; +export const useStyles = makeStyles((theme) => ({ + root: { + flex: 1, + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + padding: theme.spacing(0), + margin: theme.spacing(0.5, 0.25), + }, + p: { + margin: theme.spacing(0), + }, + IconLogo: { + translate: 'transform(-50%, -50%)', + padding: theme.spacing(0.1), + }, +})); diff --git a/web/src/components/forums/CreateDialog/Toolbar/Embed/EmbedImage.jsx b/web/src/components/forums/CreateDialog/Toolbar/Embed/EmbedImage.jsx new file mode 100644 index 00000000..18dbd93a --- /dev/null +++ b/web/src/components/forums/CreateDialog/Toolbar/Embed/EmbedImage.jsx @@ -0,0 +1,110 @@ +import React, {useState} from 'react'; +import {Box, Button, DialogTitle, DialogContent, Input, IconButton} from '@mui/material'; +import ImageIcon from '@mui/icons-material/Image'; +import {makeStyles} from '@mui/styles'; +import Popover from '@mui/material/Popover'; + +export const useStyles = makeStyles((theme) => ({ + button: { + margin: theme.spacing(0.5), + }, +})); + +export default function EmbedImage({embedImage}) { + const [clicked, setClicked] = useState(false); + const [imageURL, setImageURL] = useState(''); + const [anchorEl, setAnchorEl] = React.useState(null); + const classes = useStyles(); + + const closeModal = () => { + setClicked(false); + setImageURL(''); + setAnchorEl(null); + }; + + const openModal = (e) => { + setClicked(true); + setAnchorEl(e.currentTarget); + }; + + const handleEmbedImage = () => { + if (!imageURL) { + return; + } + embedImage(imageURL); + closeModal(); + }; + + const uploadImage = (file) => { + const data = new FormData(); + data.append('file', file); + axios.post(`/api/public/forums/post/image`, data) + .then((image_url) => { + // Wait for image url of uploaded image to be returned + return image_url.data; + }) + .catch(standardErrorHandler(enqueueSnackbar)); + }; + + const handleFileUpload = (e) => { + const file = e.target.files[0]; + if (!file) { + return; + } + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onloadend = () => { + // const image_url = uploadImage(file); + embedImage(image_url); + }; + closeModal(); + }; + + return ( + + e.preventDefault()} + > + + + + + {'Embed Image'} + + + + setImageURL(e.target.value)} + /> + + +

OR

+ +
+
+
+
+ ); +}; diff --git a/web/src/pages/forums/Forum/Forum.styles.jsx b/web/src/pages/forums/Forum/Forum.styles.jsx index ec54d43b..bc0548b8 100644 --- a/web/src/pages/forums/Forum/Forum.styles.jsx +++ b/web/src/pages/forums/Forum/Forum.styles.jsx @@ -38,6 +38,10 @@ export const useStyles = makeStyles((theme) => ({ paddingTop: theme.spacing(1), paddingBottom: theme.spacing(1), borderRadius: '2px', + color: theme.palette.white, + '&:hover': { + color: theme.palette.primary.main, + }, }, postsContainer: { marginTop: theme.spacing(2), diff --git a/web/yarn.lock b/web/yarn.lock index 8a84e751..83186b60 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -1250,6 +1250,46 @@ dependencies: "@date-io/core" "^2.14.0" +"@draft-js-plugins/alignment@^5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@draft-js-plugins/alignment/-/alignment-5.0.3.tgz#25e133375accf3b34f9470a355568de95173d25f" + integrity sha512-Zw6AqI5mjqKLXPez+e9sKPQgsr+OZ9GJzJl4gsf02QLBzh5JHyzCRzcfB/eSVgJl4JJnudbG4cvdiWJny9AyCw== + dependencies: + "@draft-js-plugins/buttons" "^4.0.0" + "@draft-js-plugins/utils" "^4.0.0" + +"@draft-js-plugins/buttons@^4.0.0": + version "4.3.2" + resolved "https://registry.yarnpkg.com/@draft-js-plugins/buttons/-/buttons-4.3.2.tgz#053e3ca3be345034f22c4ba040f97cf1a41f3aef" + integrity sha512-KEBp37yCfJDZamAcUHJE0Q1gH2voqt+DMPvzmI2Th6Xz0El/cdcOoAAgX3JPqDzWfd4sc7dDhm/77r9HmO8wIA== + dependencies: + clsx "^1.2.1" + +"@draft-js-plugins/editor@^4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@draft-js-plugins/editor/-/editor-4.1.3.tgz#460d70d3b639d17f4e1f15b7d180b8c8698b1ec1" + integrity sha512-Mxr1GUFzN0VcVVP6XClvWS2o33Rh8ZG3l41h9l27hJoLRYVywpuzI6TXD0UFYDIHX50fz6n70sQCdoHmkBsfKQ== + dependencies: + immutable "~3.7.4" + prop-types "^15.8.1" + +"@draft-js-plugins/image@^4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@draft-js-plugins/image/-/image-4.1.3.tgz#2a90f9629b5784fb01ad4bc92c2b8125598dc5b5" + integrity sha512-/+E/dx/6GvcWiw7ViIaeKkVTLJm9cDejZu1tpinb53Cl4iwMT7hN3pwLE270MYcctj5ZdwjleRuMDnx4l/mDtw== + dependencies: + clsx "^1.0.4" + +"@draft-js-plugins/resizeable@^5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@draft-js-plugins/resizeable/-/resizeable-5.0.3.tgz#e4ff0a1b733bb1f6ce9a9aa32b5f2fca7c756f8e" + integrity sha512-aqhIRIL17C+MqMrZJgNakVrbNTeVsiNtIQszP6VcoPiVPm9RUO12aQAX1c0MfVeKbh9qk0JwNpHglSGd9LIUeA== + +"@draft-js-plugins/utils@^4.0.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@draft-js-plugins/utils/-/utils-4.2.0.tgz#ee3c643c94d548fd9460541881a7554c5b077988" + integrity sha512-tQhf7a0S9ZDtOb0Mje5a+xTOT6LVDRv2LueEqqVdX4aI+oh0jjkAuP0oTiZBv6h7K9XSPgsQ08q9+kwHc3EyUg== + "@emotion/babel-plugin@^11.7.1": version "11.9.2" resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz#723b6d394c89fb2ef782229d92ba95a740576e95" @@ -3660,7 +3700,7 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -clsx@^1.1.0, clsx@^1.1.1, clsx@^1.2.0, clsx@^1.2.1: +clsx@^1.0.4, clsx@^1.1.0, clsx@^1.1.1, clsx@^1.2.0, clsx@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== @@ -3857,11 +3897,16 @@ core-js-pure@^3.23.3, core-js-pure@^3.25.1: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.26.0.tgz#7ad8a5dd7d910756f3124374b50026e23265ca9a" integrity sha512-LiN6fylpVBVwT8twhhluD9TzXmZQQsr2I2eIKtWNbZI1XMfBT7CV18itaN6RA7EtQd/SDdRx/wzvAShX2HvhQA== -core-js@^3.19.2, core-js@^3.6.4: +core-js@^3.19.2: version "3.26.0" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.26.0.tgz#a516db0ed0811be10eac5d94f3b8463d03faccfe" integrity sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw== +core-js@^3.6.4: + version "3.31.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.31.1.tgz#f2b0eea9be9da0def2c5fece71064a7e5d687653" + integrity sha512-2sKLtfq1eFST7l7v62zaqXacPc7uG8ZAya8ogijLhTtaKNcpzpB4TMoTw2Si+8GYKRwFPMMtUT0263QFWFfqyQ== + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -3890,11 +3935,11 @@ cosmiconfig@^7.0.0: yaml "^1.10.0" cross-fetch@^3.0.4: - version "3.1.5" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" - integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + version "3.1.8" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" + integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== dependencies: - node-fetch "2.6.7" + node-fetch "^2.6.12" cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" @@ -4610,6 +4655,11 @@ draft-js@^0.11.7: immutable "~3.7.4" object-assign "^4.1.1" +draftjs-utils@^0.10.2: + version "0.10.2" + resolved "https://registry.yarnpkg.com/draftjs-utils/-/draftjs-utils-0.10.2.tgz#a7f16d2c1c174ac38ba3bbf700c256f176b2699c" + integrity sha512-EstHqr3R3JVcilJrBaO/A+01GvwwKmC7e4TCjC7S94ZeMh4IVmf60OuQXtHHpwItK8C2JCi3iljgN5KHkJboUg== + duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -5912,6 +5962,11 @@ html-tags@1: resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-1.2.0.tgz#c78de65b5663aa597989dd2b7ab49200d7e4db98" integrity sha512-uVteDXUCs08M7QJx0eY6ue7qQztwIfknap81vAtNob2sdEPKa8PjPinx0vxbs2JONPamovZjMvKZWNW44/PBKg== +html-to-draftjs@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/html-to-draftjs/-/html-to-draftjs-1.5.0.tgz#0df0eabf429deaedb63f5c859889e2c983606e86" + integrity sha512-kggLXBNciKDwKf+KYsuE+V5gw4dZ7nHyGMX9m0wy7urzWjKGWyNFetmArRLvRV0VrxKN70WylFsJvMTJx02OBQ== + html-to-react@^1.3.4: version "1.5.0" resolved "https://registry.yarnpkg.com/html-to-react/-/html-to-react-1.5.0.tgz#6e0cf47ae1b091ba2f28a3832389fbce4d199ccc" @@ -7358,6 +7413,13 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +linkify-it@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf" + integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw== + dependencies: + uc.micro "^1.0.1" + loader-runner@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" @@ -8189,10 +8251,10 @@ node-dir@^0.1.17: dependencies: minimatch "^3.0.2" -node-fetch@2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== +node-fetch@^2.6.12: + version "2.6.12" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba" + integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g== dependencies: whatwg-url "^5.0.0" @@ -9470,6 +9532,17 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-draft-wysiwyg@^1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/react-draft-wysiwyg/-/react-draft-wysiwyg-1.15.0.tgz#d5b4173991033859b9e161c883889ddc00909a57" + integrity sha512-p1cYZcWc6/ALFBVksbFoCM3b29fGQDlZLIMrXng0TU/UElxIOF2/AWWo4L5auIYVhmqKTZ0NkNjnXOzGGuxyeA== + dependencies: + classnames "^2.2.6" + draftjs-utils "^0.10.2" + html-to-draftjs "^1.5.0" + linkify-it "^2.2.0" + prop-types "^15.7.2" + react-error-overlay@^6.0.11: version "6.0.11" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" @@ -10988,15 +11061,20 @@ typedarray-to-buffer@^3.1.5: is-typedarray "^1.0.0" ua-parser-js@^0.7.18: - version "0.7.31" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6" - integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ== + version "0.7.35" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.35.tgz#8bda4827be4f0b1dda91699a29499575a1f1d307" + integrity sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g== ua-parser-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.2.tgz#e2976c34dbfb30b15d2c300b2a53eac87c57a775" integrity sha512-00y/AXhx0/SsnI51fTc0rLRmafiGOM4/O+ny10Ps7f+j/b8p/ZY11ytMgznXkOVo4GQ+KwQG5UQLkLGirsACRg== +uc.micro@^1.0.1: + version "1.0.6" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" From 6af0419dbf028f84a7524ef264cc7426d5835dcb Mon Sep 17 00:00:00 2001 From: dolf321 Date: Wed, 19 Jul 2023 09:27:47 +0300 Subject: [PATCH 20/38] CHG migrate text editor to separate component --- .../forums/CreateDialog/CreateDialog.jsx | 48 ++-------------- .../CreateDialog/CreateDialog.styles.jsx | 5 -- .../forums/RichTextEditor/RichTextEditor.jsx | 56 +++++++++++++++++++ .../TextEditor.css | 0 .../Toolbar/EditorToolbar.jsx | 0 .../Toolbar/EditorToolbar.styles.jsx | 0 .../Toolbar/Embed/EmbedImage.jsx | 0 7 files changed, 62 insertions(+), 47 deletions(-) create mode 100644 web/src/components/forums/RichTextEditor/RichTextEditor.jsx rename web/src/components/forums/{CreateDialog => RichTextEditor}/TextEditor.css (100%) rename web/src/components/forums/{CreateDialog => RichTextEditor}/Toolbar/EditorToolbar.jsx (100%) rename web/src/components/forums/{CreateDialog => RichTextEditor}/Toolbar/EditorToolbar.styles.jsx (100%) rename web/src/components/forums/{CreateDialog => RichTextEditor}/Toolbar/Embed/EmbedImage.jsx (100%) diff --git a/web/src/components/forums/CreateDialog/CreateDialog.jsx b/web/src/components/forums/CreateDialog/CreateDialog.jsx index b8fbe7dd..770e1105 100644 --- a/web/src/components/forums/CreateDialog/CreateDialog.jsx +++ b/web/src/components/forums/CreateDialog/CreateDialog.jsx @@ -1,21 +1,8 @@ import React, {useState, useRef} from 'react'; import {Dialog, DialogTitle, DialogContent, Box, Input, Typography, Button, Switch, IconButton} from '@mui/material'; -import EditorToolbar from './Toolbar/EditorToolbar'; -import {EditorState, RichUtils, convertToRaw} from 'draft-js'; -import Editor, {composeDecorators} from '@draft-js-plugins/editor'; -import createResizablePlugin from '@draft-js-plugins/resizeable'; -import createImagePlugin from '@draft-js-plugins/image'; import CloseIcon from '@mui/icons-material/Close'; import {useStyles} from './CreateDialog.styles'; - -import 'draft-js/dist/Draft.css'; -import './TextEditor.css'; - -const resizeablePlugin = createResizablePlugin(); -const decorator = composeDecorators( - resizeablePlugin.decorator, -); -const imagePlugin = createImagePlugin({decorator}); +import RichTextEditor from '../RichTextEditor/RichTextEditor'; export default function CreateDialog({ mode = 'post', @@ -25,17 +12,14 @@ export default function CreateDialog({ }) { // MUI theme-based css styles const classes = useStyles(); - + let getContent; // Form Data const [title, setTitle] = useState(''); const [isVisibleToStudents, setIsVisisbleToStudents] = useState(true); const [isAnonymous, setIsAnonymous] = useState(false); - const [editorState, setEditorState] = useState(EditorState.createEmpty()); - const editor = useRef(null); - const [error, setError] = useState(''); const validatePost = () => { - const content = JSON.stringify(convertToRaw(editorState.getCurrentContent())); + const content = getContent(); if (title && content) { handleCreatePost({ title: title, @@ -46,15 +30,6 @@ export default function CreateDialog({ }; }; - const handleKeyCommand = (command) => { - const newState = RichUtils.handleKeyCommand(editorState, command); - if (newState) { - setEditorState(newState); - return true; - } - return false; - }; - return ( setTitle(e.target.value)} placeholder={'Put Title Here'} /> -
- -
- { - setEditorState(editorState); - }} - plugins={[imagePlugin, resizeablePlugin]} - /> +
-

Visibile to Students?

+ Visibile to Students? setIsVisisbleToStudents(!isVisibleToStudents)}/>
-

Anonymous?

+ Anonymous? setIsAnonymous(!isAnonymous)}/>
diff --git a/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx b/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx index 09102638..373af793 100644 --- a/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx +++ b/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx @@ -13,11 +13,6 @@ export const useStyles = makeStyles((theme) => ({ buttonIcon: { color: theme.palette.primary.main, }, - toolbarContainer: { - borderColor: theme.palette.white, - borderTop: theme.spacing(0.1) + ' solid', - borderBottom: theme.spacing(0.1) + ' solid', - }, submit: { borderTopLeftRadius: 0, borderTopRightRadius: 0, diff --git a/web/src/components/forums/RichTextEditor/RichTextEditor.jsx b/web/src/components/forums/RichTextEditor/RichTextEditor.jsx new file mode 100644 index 00000000..315e81d5 --- /dev/null +++ b/web/src/components/forums/RichTextEditor/RichTextEditor.jsx @@ -0,0 +1,56 @@ +import React, {useState, useRef} from 'react'; +import {EditorState, RichUtils, convertToRaw} from 'draft-js'; +import Editor, {composeDecorators} from '@draft-js-plugins/editor'; +import createResizablePlugin from '@draft-js-plugins/resizeable'; +import EditorToolbar from './Toolbar/EditorToolbar'; +import createImagePlugin from '@draft-js-plugins/image'; +import {makeStyles} from '@mui/styles'; +import 'draft-js/dist/Draft.css'; +import './TextEditor.css'; +const resizeablePlugin = createResizablePlugin(); +const decorator = composeDecorators( + resizeablePlugin.decorator, +); +const imagePlugin = createImagePlugin({decorator}); + +const useStyles = makeStyles((theme) => ({ + toolbarContainer: { + borderColor: theme.palette.white, + borderTop: theme.spacing(0.1) + ' solid', + borderBottom: theme.spacing(0.1) + ' solid', + }, +})); + +export default function RichTextEditor({getContent}) { + const [editorState, setEditorState] = useState(EditorState.createEmpty()); + const editor = useRef(null); + const classes = useStyles(); + getContent = () => { + return JSON.stringify(convertToRaw(editorState.getCurrentContent())); + }; + + const handleKeyCommand = (command) => { + const newState = RichUtils.handleKeyCommand(editorState, command); + if (newState) { + setEditorState(newState); + return true; + } + return false; + }; + return ( + <> +
+ +
+ { + setEditorState(editorState); + }} + plugins={[imagePlugin, resizeablePlugin]} + /> + + ); +}; diff --git a/web/src/components/forums/CreateDialog/TextEditor.css b/web/src/components/forums/RichTextEditor/TextEditor.css similarity index 100% rename from web/src/components/forums/CreateDialog/TextEditor.css rename to web/src/components/forums/RichTextEditor/TextEditor.css diff --git a/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx b/web/src/components/forums/RichTextEditor/Toolbar/EditorToolbar.jsx similarity index 100% rename from web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.jsx rename to web/src/components/forums/RichTextEditor/Toolbar/EditorToolbar.jsx diff --git a/web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.styles.jsx b/web/src/components/forums/RichTextEditor/Toolbar/EditorToolbar.styles.jsx similarity index 100% rename from web/src/components/forums/CreateDialog/Toolbar/EditorToolbar.styles.jsx rename to web/src/components/forums/RichTextEditor/Toolbar/EditorToolbar.styles.jsx diff --git a/web/src/components/forums/CreateDialog/Toolbar/Embed/EmbedImage.jsx b/web/src/components/forums/RichTextEditor/Toolbar/Embed/EmbedImage.jsx similarity index 100% rename from web/src/components/forums/CreateDialog/Toolbar/Embed/EmbedImage.jsx rename to web/src/components/forums/RichTextEditor/Toolbar/Embed/EmbedImage.jsx From cb2a3adef039d988d0b5fd8fc335537a503dd8f0 Mon Sep 17 00:00:00 2001 From: dolf321 Date: Thu, 20 Jul 2023 20:05:00 +0300 Subject: [PATCH 21/38] CHG migrate text editor to new folder --- .../RichTextEditor.jsx | 45 +++++++++++++++---- .../components/forums/Editor/TextEditor.css | 27 +++++++++++ .../Toolbar/EditorToolbar.jsx | 0 .../Toolbar/EditorToolbar.styles.jsx | 0 .../Toolbar/Embed/EmbedImage.jsx | 0 .../components/forums/ReplyForm/ReplyForm.jsx | 0 .../forums/ReplyForm/ReplyForm.styles.jsx | 0 .../forums/RichTextEditor/TextEditor.css | 23 ---------- 8 files changed, 64 insertions(+), 31 deletions(-) rename web/src/components/forums/{RichTextEditor => Editor}/RichTextEditor.jsx (56%) create mode 100644 web/src/components/forums/Editor/TextEditor.css rename web/src/components/forums/{RichTextEditor => Editor}/Toolbar/EditorToolbar.jsx (100%) rename web/src/components/forums/{RichTextEditor => Editor}/Toolbar/EditorToolbar.styles.jsx (100%) rename web/src/components/forums/{RichTextEditor => Editor}/Toolbar/Embed/EmbedImage.jsx (100%) delete mode 100644 web/src/components/forums/ReplyForm/ReplyForm.jsx delete mode 100644 web/src/components/forums/ReplyForm/ReplyForm.styles.jsx delete mode 100644 web/src/components/forums/RichTextEditor/TextEditor.css diff --git a/web/src/components/forums/RichTextEditor/RichTextEditor.jsx b/web/src/components/forums/Editor/RichTextEditor.jsx similarity index 56% rename from web/src/components/forums/RichTextEditor/RichTextEditor.jsx rename to web/src/components/forums/Editor/RichTextEditor.jsx index 315e81d5..f8d1d5c1 100644 --- a/web/src/components/forums/RichTextEditor/RichTextEditor.jsx +++ b/web/src/components/forums/Editor/RichTextEditor.jsx @@ -1,12 +1,16 @@ -import React, {useState, useRef} from 'react'; -import {EditorState, RichUtils, convertToRaw} from 'draft-js'; +import React, {useState, useRef, useEffect} from 'react'; +import {EditorState, RichUtils, convertFromRaw, convertToRaw} from 'draft-js'; import Editor, {composeDecorators} from '@draft-js-plugins/editor'; import createResizablePlugin from '@draft-js-plugins/resizeable'; import EditorToolbar from './Toolbar/EditorToolbar'; import createImagePlugin from '@draft-js-plugins/image'; import {makeStyles} from '@mui/styles'; +import {Box, IconButton} from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; + import 'draft-js/dist/Draft.css'; import './TextEditor.css'; + const resizeablePlugin = createResizablePlugin(); const decorator = composeDecorators( resizeablePlugin.decorator, @@ -15,19 +19,34 @@ const imagePlugin = createImagePlugin({decorator}); const useStyles = makeStyles((theme) => ({ toolbarContainer: { + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', borderColor: theme.palette.white, borderTop: theme.spacing(0.1) + ' solid', borderBottom: theme.spacing(0.1) + ' solid', }, + editorContainer: { + width: '100%', + }, })); -export default function RichTextEditor({getContent}) { +export default function RichTextEditor({content = null, setContent = null, setOpen = null, readOnly = false, + enableToolbar = true}) { const [editorState, setEditorState] = useState(EditorState.createEmpty()); const editor = useRef(null); const classes = useStyles(); - getContent = () => { - return JSON.stringify(convertToRaw(editorState.getCurrentContent())); - }; + + if (!readOnly && setContent) { + useEffect(() => { + setContent(JSON.stringify(convertToRaw(editorState.getCurrentContent()))); + }, [editorState]); + } else if (content) { + useEffect(() => { + setEditorState(EditorState.createWithContent(convertFromRaw(content))); + }, []); + } const handleKeyCommand = (command) => { const newState = RichUtils.handleKeyCommand(editorState, command); @@ -39,16 +58,26 @@ export default function RichTextEditor({getContent}) { }; return ( <> -
+ {enableToolbar && + -
+ { + setOpen && + setOpen(false)}> + + + } + + } { setEditorState(editorState); }} + readOnly={readOnly} plugins={[imagePlugin, resizeablePlugin]} /> diff --git a/web/src/components/forums/Editor/TextEditor.css b/web/src/components/forums/Editor/TextEditor.css new file mode 100644 index 00000000..9a9a727b --- /dev/null +++ b/web/src/components/forums/Editor/TextEditor.css @@ -0,0 +1,27 @@ +[role="textbox"].public-DraftEditor-content { /* This is only if the container is editable */ + height: 20rem; + padding: 1rem; + margin-bottom: 10px; + overflow-y: auto; + overflow-x: hidden; + background-color: rgb(0, 0, 0, 0.1); +} + +.DraftEditor-root { + width: 100%; +} + +[role="textbox"].public-DraftEditor-content::-webkit-scrollbar { + height: 4px; + width: 6px; + scrollbar-color: white white; +} + +[role="textbox"].public-DraftEditor-content::-webkit-scrollbar-thumb { + background: white; + border-radius: 10px; +} + +[role="textbox"].public-DraftEditor-content::-webkit-scrollbar-track { + background: gray; +} \ No newline at end of file diff --git a/web/src/components/forums/RichTextEditor/Toolbar/EditorToolbar.jsx b/web/src/components/forums/Editor/Toolbar/EditorToolbar.jsx similarity index 100% rename from web/src/components/forums/RichTextEditor/Toolbar/EditorToolbar.jsx rename to web/src/components/forums/Editor/Toolbar/EditorToolbar.jsx diff --git a/web/src/components/forums/RichTextEditor/Toolbar/EditorToolbar.styles.jsx b/web/src/components/forums/Editor/Toolbar/EditorToolbar.styles.jsx similarity index 100% rename from web/src/components/forums/RichTextEditor/Toolbar/EditorToolbar.styles.jsx rename to web/src/components/forums/Editor/Toolbar/EditorToolbar.styles.jsx diff --git a/web/src/components/forums/RichTextEditor/Toolbar/Embed/EmbedImage.jsx b/web/src/components/forums/Editor/Toolbar/Embed/EmbedImage.jsx similarity index 100% rename from web/src/components/forums/RichTextEditor/Toolbar/Embed/EmbedImage.jsx rename to web/src/components/forums/Editor/Toolbar/Embed/EmbedImage.jsx diff --git a/web/src/components/forums/ReplyForm/ReplyForm.jsx b/web/src/components/forums/ReplyForm/ReplyForm.jsx deleted file mode 100644 index e69de29b..00000000 diff --git a/web/src/components/forums/ReplyForm/ReplyForm.styles.jsx b/web/src/components/forums/ReplyForm/ReplyForm.styles.jsx deleted file mode 100644 index e69de29b..00000000 diff --git a/web/src/components/forums/RichTextEditor/TextEditor.css b/web/src/components/forums/RichTextEditor/TextEditor.css deleted file mode 100644 index 7cf6acf8..00000000 --- a/web/src/components/forums/RichTextEditor/TextEditor.css +++ /dev/null @@ -1,23 +0,0 @@ -.DraftEditor-editorContainer { - height: 20rem; - padding: 1rem; - margin-bottom: 10px; - overflow-y: auto; - overflow-x: hidden; - background-color: rgb(0, 0, 0, 0.1); -} - -::-webkit-scrollbar { - height: 4px; - width: 6px; - scrollbar-color: white white; -} - -::-webkit-scrollbar-thumb { - background: white; - border-radius: 10px; -} - -::-webkit-scrollbar-track { - background: gray; -} \ No newline at end of file From fb6b5316494f54225ec781b2b7b345a84b8e4acd Mon Sep 17 00:00:00 2001 From: dolf321 Date: Thu, 20 Jul 2023 20:05:23 +0300 Subject: [PATCH 22/38] ADD commenting functionality --- web/src/components/forums/Comment/Comment.jsx | 3 ++- web/src/components/forums/CommentsList/CommentsList.jsx | 7 ++++--- .../components/forums/CommentsList/CommentsList.styles.jsx | 1 - 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/web/src/components/forums/Comment/Comment.jsx b/web/src/components/forums/Comment/Comment.jsx index 7c4ba034..a44834f8 100644 --- a/web/src/components/forums/Comment/Comment.jsx +++ b/web/src/components/forums/Comment/Comment.jsx @@ -12,6 +12,7 @@ import {useStyles} from './Comment.styles'; import standardErrorHandler from '../../../utils/standardErrorHandler'; import standardStatusHandler from '../../../utils/standardStatusHandler'; import {toRelativeDate} from '../../../utils/datetime'; +import RichTextEditor from '../Editor/RichTextEditor'; export default function Comment({ threadStart = false, @@ -42,7 +43,7 @@ export default function Comment({ - {content} + {hasReplies && diff --git a/web/src/components/forums/CommentsList/CommentsList.jsx b/web/src/components/forums/CommentsList/CommentsList.jsx index be0a4d48..442142ed 100644 --- a/web/src/components/forums/CommentsList/CommentsList.jsx +++ b/web/src/components/forums/CommentsList/CommentsList.jsx @@ -2,10 +2,10 @@ import React, {useState} from 'react'; import Box from '@mui/material/Box'; import Comment from '../Comment/Comment'; - +import Publisher from '../Publisher/Publisher'; import {useStyles} from './CommentsList.styles'; -export default function CommentsList({comments}) { +export default function CommentsList({comments, handleCreateComment}) { const classes = useStyles(); const [isReplying, setIsReplying] = useState(false); @@ -39,7 +39,8 @@ export default function CommentsList({comments}) { /> ))} {isReplying && - + handleCreateComment({...comment, comment_id: comment.id})}/> } } diff --git a/web/src/components/forums/CommentsList/CommentsList.styles.jsx b/web/src/components/forums/CommentsList/CommentsList.styles.jsx index 5cfdf5a8..34a332ed 100644 --- a/web/src/components/forums/CommentsList/CommentsList.styles.jsx +++ b/web/src/components/forums/CommentsList/CommentsList.styles.jsx @@ -7,7 +7,6 @@ export const useStyles = makeStyles((theme) => ({ flexDirection: 'column', borderLeft: `1px solid ${theme.palette.dark.blue['200']}`, paddingLeft: theme.spacing(1.5), - overflow: 'scroll', }, replies: { display: 'flex', From 69542d7f4dfec2e28b9e6c60690d9a33c2f452b9 Mon Sep 17 00:00:00 2001 From: dolf321 Date: Thu, 20 Jul 2023 20:05:48 +0300 Subject: [PATCH 23/38] CHG cleaned up post dialog --- .../forums/CreateDialog/CreateDialog.jsx | 54 +++---------------- .../CreateDialog/CreateDialog.styles.jsx | 30 ----------- 2 files changed, 8 insertions(+), 76 deletions(-) delete mode 100644 web/src/components/forums/CreateDialog/CreateDialog.styles.jsx diff --git a/web/src/components/forums/CreateDialog/CreateDialog.jsx b/web/src/components/forums/CreateDialog/CreateDialog.jsx index 770e1105..3510d30e 100644 --- a/web/src/components/forums/CreateDialog/CreateDialog.jsx +++ b/web/src/components/forums/CreateDialog/CreateDialog.jsx @@ -1,8 +1,12 @@ import React, {useState, useRef} from 'react'; import {Dialog, DialogTitle, DialogContent, Box, Input, Typography, Button, Switch, IconButton} from '@mui/material'; -import CloseIcon from '@mui/icons-material/Close'; -import {useStyles} from './CreateDialog.styles'; -import RichTextEditor from '../RichTextEditor/RichTextEditor'; +import makeStyles from '@mui/styles/makeStyles'; +import Publisher from '../Publisher/Publisher'; +export const useStyles = makeStyles((theme) => ({ + root: { + width: '800px', + }, +})); export default function CreateDialog({ mode = 'post', @@ -12,58 +16,16 @@ export default function CreateDialog({ }) { // MUI theme-based css styles const classes = useStyles(); - let getContent; - // Form Data - const [title, setTitle] = useState(''); - const [isVisibleToStudents, setIsVisisbleToStudents] = useState(true); - const [isAnonymous, setIsAnonymous] = useState(false); const [error, setError] = useState(''); - const validatePost = () => { - const content = getContent(); - if (title && content) { - handleCreatePost({ - title: title, - content: content, - visible_to_students: isVisibleToStudents, - anonymous: isAnonymous, - }); - }; - }; return ( setOpen(false)} > {error} - - - {mode === 'post' ? 'Create A New Post' : 'Create A New Comment'} - - setOpen(false)}> - - - - - - - setTitle(e.target.value)} placeholder={'Put Title Here'} /> - - -
- Visibile to Students? - setIsVisisbleToStudents(!isVisibleToStudents)}/> -
-
- Anonymous? - setIsAnonymous(!isAnonymous)}/> -
-
-
- +
); } diff --git a/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx b/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx deleted file mode 100644 index 373af793..00000000 --- a/web/src/components/forums/CreateDialog/CreateDialog.styles.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import makeStyles from '@mui/styles/makeStyles'; - -export const useStyles = makeStyles((theme) => ({ - root: { - width: '800px', - }, - switchContainer: { - display: 'flex', - alignItems: 'center', - margin: theme.spacing(0), - padding: theme.spacing(0), - }, - buttonIcon: { - color: theme.palette.primary.main, - }, - submit: { - borderTopLeftRadius: 0, - borderTopRightRadius: 0, - }, - titleContainer: { - backgroundColor: `${theme.palette.primary.main}80`, - padding: theme.spacing(1.5), - fontSize: '1rem', - }, - inputTitle: { - padding: theme.spacing(1.5, 1, 1.5, 1), - fontSize: '2.25rem', - }, -})); - From c2ad04dcc4200f45773d1a9adddb0c4ccb2c69cd Mon Sep 17 00:00:00 2001 From: dolf321 Date: Thu, 20 Jul 2023 20:06:24 +0300 Subject: [PATCH 24/38] ADD commenting with richtexteditor --- web/src/components/forums/Post/Post.jsx | 55 +++++++++++-------- .../components/forums/Post/Post.styles.jsx | 24 +++++++- 2 files changed, 55 insertions(+), 24 deletions(-) diff --git a/web/src/components/forums/Post/Post.jsx b/web/src/components/forums/Post/Post.jsx index 38d11152..1e588a08 100644 --- a/web/src/components/forums/Post/Post.jsx +++ b/web/src/components/forums/Post/Post.jsx @@ -1,16 +1,17 @@ -import React from 'react'; +import React, {useEffect} from 'react'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import VisibilityIcon from '@mui/icons-material/Visibility'; import CommentIcon from '@mui/icons-material/Comment'; -import CreateIcon from '@mui/icons-material/Create'; +import Button from '@mui/material/Button'; import Divider from '../../shared/Divider/Divider'; import CommentsList from '../CommentsList/CommentsList'; import {toRelativeDate} from '../../../utils/datetime'; - +import RichTextEditor from '../Editor/RichTextEditor'; import {useStyles} from './Post.styles'; +import Publisher from '../Publisher/Publisher'; export default function Post({ title, @@ -20,8 +21,14 @@ export default function Post({ createdDate, updatedDate, comments, + handleCreateComment, }) { const classes = useStyles(); + const [commentPressed, setCommentPressed] = React.useState(false); + + const closePublisher = () => { + setCommentPressed(false); + }; return ( @@ -35,38 +42,40 @@ export default function Post({ {user} - posted on {toRelativeDate(new Date(createdDate))} + posted {toRelativeDate(new Date(createdDate))} {title} - {content} + - - - - {seenCount} - - - - - - {comments.length} - - - - - - {toRelativeDate(new Date(updatedDate))} - + + + + + {seenCount} + + + + + + {comments.length} + + + {commentPressed || + + } + {commentPressed && } - + ); diff --git a/web/src/components/forums/Post/Post.styles.jsx b/web/src/components/forums/Post/Post.styles.jsx index a6f5e224..e4a77f27 100644 --- a/web/src/components/forums/Post/Post.styles.jsx +++ b/web/src/components/forums/Post/Post.styles.jsx @@ -38,18 +38,24 @@ export const useStyles = makeStyles((theme) => ({ content: { width: '100%', }, + infoToolbar: { + display: 'flex', + gap: theme.spacing(3), + alignItems: 'center', + }, extraInfo: { marginTop: theme.spacing(2), width: '100%', display: 'flex', gap: theme.spacing(3), alignItems: 'center', - opacity: '.8', + justifyContent: 'space-between', }, infoContainer: { display: 'flex', alignItems: 'center', gap: theme.spacing(1), + opacity: '.8', }, icon: { fontSize: '14px', @@ -58,5 +64,21 @@ export const useStyles = makeStyles((theme) => ({ paddingTop: theme.spacing(2), width: '100%', }, + addComment: { + height: '100%', + backgroundColor: theme.palette.primary.main, + paddingRight: theme.spacing(2), + paddingLeft: theme.spacing(2), + paddingTop: theme.spacing(1), + paddingBottom: theme.spacing(1), + borderRadius: '15px', + color: theme.palette.white, + '&:hover': { + color: theme.palette.primary.main, + }, + }, + commentEditor: { + width: '500px', + }, })); From b3d47f6b9375331f72bdb14ffebc041d5f8af102 Mon Sep 17 00:00:00 2001 From: dolf321 Date: Thu, 20 Jul 2023 20:06:44 +0300 Subject: [PATCH 25/38] ADD content summaries to post list items ``` --- .../components/forums/PostListItem/PostListItem.jsx | 6 +++++- .../forums/PostListItem/PostListItem.styles.jsx | 13 ++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/web/src/components/forums/PostListItem/PostListItem.jsx b/web/src/components/forums/PostListItem/PostListItem.jsx index 0e568d3f..9fc637ff 100644 --- a/web/src/components/forums/PostListItem/PostListItem.jsx +++ b/web/src/components/forums/PostListItem/PostListItem.jsx @@ -16,6 +16,7 @@ export default function PostListItem({ title, category, user, + content, date, seenCount, seen = false, @@ -35,7 +36,10 @@ export default function PostListItem({ {title} - + + {content} + + diff --git a/web/src/components/forums/PostListItem/PostListItem.styles.jsx b/web/src/components/forums/PostListItem/PostListItem.styles.jsx index 77604800..66408212 100644 --- a/web/src/components/forums/PostListItem/PostListItem.styles.jsx +++ b/web/src/components/forums/PostListItem/PostListItem.styles.jsx @@ -7,6 +7,7 @@ export const useStyles = makeStyles((theme) => ({ borderRadius: '3px', width: '100%', display: 'flex', + overflow: 'hidden', alignItems: 'center', gap: theme.spacing(1.5), cursor: 'pointer', @@ -30,11 +31,20 @@ export const useStyles = makeStyles((theme) => ({ }, content: { display: 'flex', + overflow: 'hidden', flexDirection: 'column', justifyContent: 'center', gap: '5px', }, - error: { + summary: { // i want two lines of text + margin: 0, + opacity: '.8', + fontSize: '0.8rem', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }, + dataSummary: { display: 'flex', alignItems: 'center', gap: '10px', @@ -48,6 +58,7 @@ export const useStyles = makeStyles((theme) => ({ display: 'flex', alignItems: 'center', gap: '5px', + fontSize: '0.8rem', }, icon: { fontSize: '14px', From a13889f1fdd7c82bf11a21c81898cd81c6db8c08 Mon Sep 17 00:00:00 2001 From: dolf321 Date: Thu, 20 Jul 2023 20:07:08 +0300 Subject: [PATCH 26/38] ADD cleaned up rich text editor --- .../components/forums/Publisher/Publisher.jsx | 77 +++++++++++++++++++ .../forums/Publisher/Publisher.styles.jsx | 35 +++++++++ 2 files changed, 112 insertions(+) create mode 100644 web/src/components/forums/Publisher/Publisher.jsx create mode 100644 web/src/components/forums/Publisher/Publisher.styles.jsx diff --git a/web/src/components/forums/Publisher/Publisher.jsx b/web/src/components/forums/Publisher/Publisher.jsx new file mode 100644 index 00000000..d1703901 --- /dev/null +++ b/web/src/components/forums/Publisher/Publisher.jsx @@ -0,0 +1,77 @@ +import React, {useState, useRef} from 'react'; +import {Dialog, DialogTitle, DialogContent, Box, Input, Typography, Button, Switch, IconButton} from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import {useStyles} from './Publisher.styles'; +import RichTextEditor from '../Editor/RichTextEditor'; + +export default function Publisher({ + mode = 'post', + setOpen, + handlePublish, +}) { + // MUI theme-based css styles + const classes = useStyles(); + // Form Data + const [title, setTitle] = useState(''); + const [content, setContent] = useState({}); + const [isVisibleToStudents, setIsVisisbleToStudents] = useState(true); + const [isAnonymous, setIsAnonymous] = useState(false); + + const [error, setError] = useState(''); + + const isPost = mode === 'post'; + + const validatePost = () => { + if (title && content) { + handlePublish({ + title: title, + content: content, + visible_to_students: isVisibleToStudents, + anonymous: isAnonymous, + }); + } else if (content && !isPost) { + handlePublish({ + content: content, + anonymous: isAnonymous, + visible_to_students: isVisibleToStudents, + }); + } + }; + + return ( + + {error} + {isPost && + + + {isPost ? 'Create A New Post' : 'Create A New Comment'} + setOpen(false)}> + + + + + } + + {isPost && + setTitle(e.target.value)} placeholder={'Put Title Here'} /> + } + {isPost ? + : + + } + +
+ Visibile to Students? + setIsVisisbleToStudents(!isVisibleToStudents)}/> +
+
+ Anonymous? + setIsAnonymous(!isAnonymous)}/> +
+
+
+ +
+ ); +} diff --git a/web/src/components/forums/Publisher/Publisher.styles.jsx b/web/src/components/forums/Publisher/Publisher.styles.jsx new file mode 100644 index 00000000..01da24f2 --- /dev/null +++ b/web/src/components/forums/Publisher/Publisher.styles.jsx @@ -0,0 +1,35 @@ +import makeStyles from '@mui/styles/makeStyles'; + +export const useStyles = makeStyles((theme) => ({ + publisherContainer: { + width: '100%', + border: `1px solid ${theme.palette.dark.blue['200']}`, + borderBottomLeftRadius: '10px', + borderBottomRightRadius: '10px', + }, + switchContainer: { + display: 'flex', + alignItems: 'center', + margin: theme.spacing(0), + padding: theme.spacing(0), + }, + buttonIcon: { + color: theme.palette.primary.main, + }, + submit: { + borderTopLeftRadius: 0, + borderTopRightRadius: 0, + width: '100%', + }, + titleContainer: { + backgroundColor: `${theme.palette.primary.main}80`, + padding: theme.spacing(1.5), + fontSize: '1rem', + width: '100%', + }, + inputTitle: { + padding: theme.spacing(1.5, 1, 1.5, 1), + fontSize: '2.25rem', + }, +})); + From 4fc636de1dd3219f49a04974d05c04be54d3b665 Mon Sep 17 00:00:00 2001 From: dolf321 Date: Thu, 20 Jul 2023 20:19:07 +0300 Subject: [PATCH 27/38] ADD Forum with temp mock data --- web/src/pages/forums/Forum/Forum.jsx | 97 ++++++++++++--------- web/src/pages/forums/Forum/Forum.styles.jsx | 6 ++ 2 files changed, 63 insertions(+), 40 deletions(-) diff --git a/web/src/pages/forums/Forum/Forum.jsx b/web/src/pages/forums/Forum/Forum.jsx index 1ae5dfb7..1b587ea2 100644 --- a/web/src/pages/forums/Forum/Forum.jsx +++ b/web/src/pages/forums/Forum/Forum.jsx @@ -9,8 +9,8 @@ import standardErrorHandler from '../../../utils/standardErrorHandler'; import PostListItem from '../../../components/forums/PostListItem/PostListItem'; import Post from '../../../components/forums/Post/Post'; import CreateDialog from '../../../components/forums/CreateDialog/CreateDialog'; - import {useStyles} from './Forum.styles.jsx'; +import CreateIcon from '@mui/icons-material/Create'; import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid'; @@ -25,16 +25,29 @@ export default function Forum({user}) { const classes = useStyles(); const [courses, setCourses] = useState(undefined); - const [selectedCourse, setSelectedCourse] = useState(undefined); + const [selectedCourse, setSelectedCourse] = useState(2); const [selectedCourseCode, setSelectedCourseCode] = useState(''); - const [posts, setPosts] = useState(undefined); + const [posts, setPosts] = useState([{ + title: 'Sample Post', + category: 'Sample Category', + content: {'blocks': [{'key': '4f5r7', 'text': 'dfgsdfgsddfsjfdfj', 'type': 'unstyled', + 'depth': 0, 'inlineStyleRanges': [{'offset': 9, 'length': 8, 'style': 'BOLD'}], + 'entityRanges': [], 'data': {}}], 'entityMap': {}}, + display_name: 'Sample User', + created: 1689602823000, + seen_count: 0, + comments: [{display_name: 'Rami', children: [{display_name: 'Rami', children: [], content: {'blocks': + [{'key': '4f5r7', + 'text': 'dfgsdfgsddfsjfdfj', 'type': 'unstyled', + 'depth': 0, 'inlineStyleRanges': [{'offset': 9, 'length': 8, 'style': 'BOLD'}], + 'entityRanges': [], 'data': {}}], 'entityMap': {}}}], content: {'blocks': [{'key': '4f5r7', + 'text': 'dfgsdfgsddfsjfdfj', 'type': 'unstyled', + 'depth': 0, 'inlineStyleRanges': [{'offset': 9, 'length': 8, 'style': 'BOLD'}], + 'entityRanges': [], 'data': {}}], 'entityMap': {}}}], + }]); const [selectedPost, setSelectedPost] = useState(undefined); - const [selectedContent, setSelectedContent] = useState(undefined); - - const [isDialogOpen, setIsDialogOpen] = useState(true); - const [dialogMode, setDialogMode] = useState('post'); - + const [isDialogOpen, setIsDialogOpen] = useState(false); const [refreshPosts, setRefreshPosts] = useState(0); const refresh = () => { @@ -70,26 +83,21 @@ export default function Forum({user}) { .catch(standardErrorHandler(enqueueSnackbar)); }, [selectedCourse, refreshPosts]); - useEffect(() => { - if (!selectedPost) { - return undefined; - } - - axios.get(`api/public/forums/post/${selectedPost.id}`) - .then((response) => { - const data = standardStatusHandler(response, enqueueSnackbar); - console.log(data.post); - if (data) { - setSelectedContent(data.post); - } - }) - .catch(standardErrorHandler(enqueueSnackbar)); - }, [selectedPost]); - - const handleOpenDialog = (mode = 'post') => { - setDialogMode(mode); - setIsDialogOpen(true); - }; + // useEffect(() => { + // if (!selectedPost) { + // return undefined; + // } + + // axios.get(`api/public/forums/post/${selectedPost.id}`) + // .then((response) => { + // const data = standardStatusHandler(response, enqueueSnackbar); + // console.log(data.post); + // if (data) { + // setSelectedContent(data.post); + // } + // }) + // .catch(standardErrorHandler(enqueueSnackbar)); + // }, [selectedPost]); const handleCourseSelect = (e) => { console.log(e.target.value); @@ -97,6 +105,7 @@ export default function Forum({user}) { }; const handleCreatePost = (post) => { + // console.log(post); axios.post(`/api/public/forums/post`, {...post, course_id: selectedCourse.id}) .then(() => { setRefreshPosts(refreshPosts + 1); @@ -105,12 +114,18 @@ export default function Forum({user}) { .catch(standardErrorHandler(enqueueSnackbar)); }; + const handleCreateComment = (comment) => { + axios.post(`/api/public/forums/comment`, {...comment, post_id: selectedPost.id, course_id: selectedCourse.id}) + .then(() => { + // Need to refresh comments + }) + .catch(standardErrorHandler(enqueueSnackbar)); + }; return ( @@ -126,7 +141,6 @@ export default function Forum({user}) { }} value={selectedCourseCode} onChange={handleCourseSelect} - disableUnderline > {courses && courses.map((course, index) => ( @@ -159,6 +174,7 @@ export default function Forum({user}) { key={`${post.title}-${index}`} title={post.title} category={post.category} + content={post.content?.blocks[0].text} user={post.display_name} date={post.created} seenCount={post.seen_count} @@ -168,15 +184,16 @@ export default function Forum({user}) { ))} - {selectedContent && ( + {selectedPost && ( )} diff --git a/web/src/pages/forums/Forum/Forum.styles.jsx b/web/src/pages/forums/Forum/Forum.styles.jsx index bc0548b8..d0a1f916 100644 --- a/web/src/pages/forums/Forum/Forum.styles.jsx +++ b/web/src/pages/forums/Forum/Forum.styles.jsx @@ -63,5 +63,11 @@ export const useStyles = makeStyles((theme) => ({ border: `1px solid ${theme.palette.dark.blue['200']}`, borderRadius: '0px 5px 5px 0px', }, + newPostIcon: { + width: '1.2rem', + marginLeft: theme.spacing(1), + position: 'relative', + transform: 'translateY(-5%)', + }, })); From ae75c361efba6dc7c359a4f8d691a4ae1d5f122d Mon Sep 17 00:00:00 2001 From: dolf321 Date: Fri, 21 Jul 2023 12:43:06 +0300 Subject: [PATCH 28/38] FIX console errors --- .../components/forums/Editor/Toolbar/Embed/EmbedImage.jsx | 4 ++-- web/src/components/forums/Post/Post.jsx | 8 ++++---- web/src/components/forums/Post/Post.styles.jsx | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/web/src/components/forums/Editor/Toolbar/Embed/EmbedImage.jsx b/web/src/components/forums/Editor/Toolbar/Embed/EmbedImage.jsx index 18dbd93a..0369082b 100644 --- a/web/src/components/forums/Editor/Toolbar/Embed/EmbedImage.jsx +++ b/web/src/components/forums/Editor/Toolbar/Embed/EmbedImage.jsx @@ -38,7 +38,7 @@ export default function EmbedImage({embedImage}) { const uploadImage = (file) => { const data = new FormData(); data.append('file', file); - axios.post(`/api/public/forums/post/image`, data) + axios.post(`/api/public/forums/image`, data) .then((image_url) => { // Wait for image url of uploaded image to be returned return image_url.data; @@ -54,7 +54,7 @@ export default function EmbedImage({embedImage}) { const reader = new FileReader(); reader.readAsDataURL(file); reader.onloadend = () => { - // const image_url = uploadImage(file); + const image_url = uploadImage(file); embedImage(image_url); }; closeModal(); diff --git a/web/src/components/forums/Post/Post.jsx b/web/src/components/forums/Post/Post.jsx index 1e588a08..c73c8da2 100644 --- a/web/src/components/forums/Post/Post.jsx +++ b/web/src/components/forums/Post/Post.jsx @@ -38,19 +38,19 @@ export default function Post({ {user[0]}
- + {user} posted {toRelativeDate(new Date(createdDate))}
- + {title} - + - +
diff --git a/web/src/components/forums/Post/Post.styles.jsx b/web/src/components/forums/Post/Post.styles.jsx index e4a77f27..ab278c97 100644 --- a/web/src/components/forums/Post/Post.styles.jsx +++ b/web/src/components/forums/Post/Post.styles.jsx @@ -24,13 +24,13 @@ export const useStyles = makeStyles((theme) => ({ width: '30px', height: '30px', }, - user: { + User: { }, whenPosted: { opacity: '.7', }, - title: { + Title: { width: '100%', fontSize: '28px', fontWeight: '600', From fa95147841904dbd899f72d5d76635be273f0cb8 Mon Sep 17 00:00:00 2001 From: dolf321 Date: Fri, 21 Jul 2023 12:48:02 +0300 Subject: [PATCH 29/38] CHG fixed post info size --- web/src/components/forums/Post/Post.styles.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/components/forums/Post/Post.styles.jsx b/web/src/components/forums/Post/Post.styles.jsx index ab278c97..251c6174 100644 --- a/web/src/components/forums/Post/Post.styles.jsx +++ b/web/src/components/forums/Post/Post.styles.jsx @@ -46,6 +46,7 @@ export const useStyles = makeStyles((theme) => ({ extraInfo: { marginTop: theme.spacing(2), width: '100%', + height: theme.spacing(5), display: 'flex', gap: theme.spacing(3), alignItems: 'center', From 5fae04f40dbda115927e2ecb3505551ef3edd223 Mon Sep 17 00:00:00 2001 From: dolf321 Date: Fri, 21 Jul 2023 12:58:23 +0300 Subject: [PATCH 30/38] ADD Some basic documentation to the RichTextEditor --- web/src/components/forums/Editor/RichTextEditor.jsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/web/src/components/forums/Editor/RichTextEditor.jsx b/web/src/components/forums/Editor/RichTextEditor.jsx index f8d1d5c1..b7469606 100644 --- a/web/src/components/forums/Editor/RichTextEditor.jsx +++ b/web/src/components/forums/Editor/RichTextEditor.jsx @@ -32,6 +32,15 @@ const useStyles = makeStyles((theme) => ({ }, })); +/** + * Rich text editor used to display and publish data + * @param {Object} content - the content of the editor, use this when we need to solely display data + * @param {Function} setContent - the function to set the content of the editor, use this when we need to publish data + * @param {Function} setOpen - the function to set the open state of the editor, called when close button clicked + * @param {Boolean} readOnly - whether or not the editor is read only + * @param {Boolean} enableToolbar - whether or not to show the toolbar + * @returns {JSX.Element} - the rich text editor + */ export default function RichTextEditor({content = null, setContent = null, setOpen = null, readOnly = false, enableToolbar = true}) { const [editorState, setEditorState] = useState(EditorState.createEmpty()); From 15e96ccec84d3ebc1f97b330234aa1b27d71f1ed Mon Sep 17 00:00:00 2001 From: dolf321 Date: Fri, 21 Jul 2023 13:00:26 +0300 Subject: [PATCH 31/38] CHG cleaned up imports --- web/src/components/forums/Comment/Comment.jsx | 3 --- web/src/components/forums/CreateDialog/CreateDialog.jsx | 4 ++-- web/src/components/forums/Post/Post.jsx | 2 +- web/src/components/forums/PostListItem/PostListItem.jsx | 1 - web/src/components/forums/Publisher/Publisher.jsx | 4 ++-- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/web/src/components/forums/Comment/Comment.jsx b/web/src/components/forums/Comment/Comment.jsx index a44834f8..78c73c95 100644 --- a/web/src/components/forums/Comment/Comment.jsx +++ b/web/src/components/forums/Comment/Comment.jsx @@ -1,5 +1,4 @@ import React, {useState} from 'react'; -import axios from 'axios'; import {useSnackbar} from 'notistack'; import Box from '@mui/material/Box'; @@ -9,8 +8,6 @@ import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import {useStyles} from './Comment.styles'; -import standardErrorHandler from '../../../utils/standardErrorHandler'; -import standardStatusHandler from '../../../utils/standardStatusHandler'; import {toRelativeDate} from '../../../utils/datetime'; import RichTextEditor from '../Editor/RichTextEditor'; diff --git a/web/src/components/forums/CreateDialog/CreateDialog.jsx b/web/src/components/forums/CreateDialog/CreateDialog.jsx index 3510d30e..016b1f69 100644 --- a/web/src/components/forums/CreateDialog/CreateDialog.jsx +++ b/web/src/components/forums/CreateDialog/CreateDialog.jsx @@ -1,5 +1,5 @@ -import React, {useState, useRef} from 'react'; -import {Dialog, DialogTitle, DialogContent, Box, Input, Typography, Button, Switch, IconButton} from '@mui/material'; +import React, {useState} from 'react'; +import {Dialog} from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import Publisher from '../Publisher/Publisher'; export const useStyles = makeStyles((theme) => ({ diff --git a/web/src/components/forums/Post/Post.jsx b/web/src/components/forums/Post/Post.jsx index c73c8da2..da35f236 100644 --- a/web/src/components/forums/Post/Post.jsx +++ b/web/src/components/forums/Post/Post.jsx @@ -1,4 +1,4 @@ -import React, {useEffect} from 'react'; +import React from 'react'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; diff --git a/web/src/components/forums/PostListItem/PostListItem.jsx b/web/src/components/forums/PostListItem/PostListItem.jsx index 9fc637ff..b316fb1b 100644 --- a/web/src/components/forums/PostListItem/PostListItem.jsx +++ b/web/src/components/forums/PostListItem/PostListItem.jsx @@ -5,7 +5,6 @@ import Box from '@mui/material/Box'; import Typography from '@mui/material/Box'; import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord'; import QueryBuilderIcon from '@mui/icons-material/QueryBuilder'; -import PersonIcon from '@mui/icons-material/Person'; import VisibilityIcon from '@mui/icons-material/Visibility'; import {toRelativeDate} from '../../../utils/datetime'; diff --git a/web/src/components/forums/Publisher/Publisher.jsx b/web/src/components/forums/Publisher/Publisher.jsx index d1703901..c37e33f5 100644 --- a/web/src/components/forums/Publisher/Publisher.jsx +++ b/web/src/components/forums/Publisher/Publisher.jsx @@ -1,5 +1,5 @@ -import React, {useState, useRef} from 'react'; -import {Dialog, DialogTitle, DialogContent, Box, Input, Typography, Button, Switch, IconButton} from '@mui/material'; +import React, {useState} from 'react'; +import {DialogTitle, DialogContent, Box, Input, Typography, Button, Switch, IconButton} from '@mui/material'; import CloseIcon from '@mui/icons-material/Close'; import {useStyles} from './Publisher.styles'; import RichTextEditor from '../Editor/RichTextEditor'; From dce80262d006a37c9e2708456733a8bc0987079f Mon Sep 17 00:00:00 2001 From: dolf321 Date: Sat, 22 Jul 2023 00:32:07 +0300 Subject: [PATCH 32/38] FIX promise related issue with image upload --- .../forums/Editor/Toolbar/Embed/EmbedImage.jsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/web/src/components/forums/Editor/Toolbar/Embed/EmbedImage.jsx b/web/src/components/forums/Editor/Toolbar/Embed/EmbedImage.jsx index 0369082b..13832cce 100644 --- a/web/src/components/forums/Editor/Toolbar/Embed/EmbedImage.jsx +++ b/web/src/components/forums/Editor/Toolbar/Embed/EmbedImage.jsx @@ -35,15 +35,10 @@ export default function EmbedImage({embedImage}) { closeModal(); }; - const uploadImage = (file) => { + const uploadImage = async (file) => { const data = new FormData(); data.append('file', file); - axios.post(`/api/public/forums/image`, data) - .then((image_url) => { - // Wait for image url of uploaded image to be returned - return image_url.data; - }) - .catch(standardErrorHandler(enqueueSnackbar)); + return await axios.post(`/api/public/forums/image`, data); }; const handleFileUpload = (e) => { @@ -54,8 +49,11 @@ export default function EmbedImage({embedImage}) { const reader = new FileReader(); reader.readAsDataURL(file); reader.onloadend = () => { - const image_url = uploadImage(file); - embedImage(image_url); + uploadImage(file) + .then((image_url) => { + embedImage(image_url); + }) + .catch(standardErrorHandler(enqueueSnackbar)); }; closeModal(); }; From 356a195294ebfa4b571f4d9902d1dd02e4d51441 Mon Sep 17 00:00:00 2001 From: dolf321 Date: Sat, 22 Jul 2023 01:04:17 +0300 Subject: [PATCH 33/38] ADD refreshing selected post --- web/src/pages/forums/Forum/Forum.jsx | 37 +++++++++++++++++----------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/web/src/pages/forums/Forum/Forum.jsx b/web/src/pages/forums/Forum/Forum.jsx index 1b587ea2..7cc2f4d0 100644 --- a/web/src/pages/forums/Forum/Forum.jsx +++ b/web/src/pages/forums/Forum/Forum.jsx @@ -47,6 +47,7 @@ export default function Forum({user}) { 'entityRanges': [], 'data': {}}], 'entityMap': {}}}], }]); const [selectedPost, setSelectedPost] = useState(undefined); + const [refreshSelectedPost, setRefreshSelectedPost] = useState(0); const [isDialogOpen, setIsDialogOpen] = useState(false); const [refreshPosts, setRefreshPosts] = useState(0); @@ -83,21 +84,27 @@ export default function Forum({user}) { .catch(standardErrorHandler(enqueueSnackbar)); }, [selectedCourse, refreshPosts]); - // useEffect(() => { - // if (!selectedPost) { - // return undefined; - // } + useEffect(() => { + if (!selectedPost) { + return undefined; + } - // axios.get(`api/public/forums/post/${selectedPost.id}`) - // .then((response) => { - // const data = standardStatusHandler(response, enqueueSnackbar); - // console.log(data.post); - // if (data) { - // setSelectedContent(data.post); - // } - // }) - // .catch(standardErrorHandler(enqueueSnackbar)); - // }, [selectedPost]); + axios.get(`api/public/forums/post/${selectedPost.id}`) + .then((response) => { + const data = standardStatusHandler(response, enqueueSnackbar); + if (data) { + setSelectedPost(data.post); + // Find and update data in post array for consistency do this instead of wasting an api call + setPosts(posts.map((post) => { + if (post.id === data.post.id) { + return data.post; + } + return post; + })); + } + }) + .catch(standardErrorHandler(enqueueSnackbar)); + }, [refreshSelectedPost]); const handleCourseSelect = (e) => { console.log(e.target.value); @@ -117,7 +124,7 @@ export default function Forum({user}) { const handleCreateComment = (comment) => { axios.post(`/api/public/forums/comment`, {...comment, post_id: selectedPost.id, course_id: selectedCourse.id}) .then(() => { - // Need to refresh comments + setRefreshSelectedPost(refreshSelectedPost + 1); }) .catch(standardErrorHandler(enqueueSnackbar)); }; From af378425866760ff5bd4570a2f343ffb64ee1dfe Mon Sep 17 00:00:00 2001 From: dolf321 Date: Sat, 22 Jul 2023 10:15:34 +0300 Subject: [PATCH 34/38] CHG Condition to lambda operator --- web/src/pages/forums/Forum/Forum.jsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/web/src/pages/forums/Forum/Forum.jsx b/web/src/pages/forums/Forum/Forum.jsx index 7cc2f4d0..eb80ebf5 100644 --- a/web/src/pages/forums/Forum/Forum.jsx +++ b/web/src/pages/forums/Forum/Forum.jsx @@ -95,12 +95,7 @@ export default function Forum({user}) { if (data) { setSelectedPost(data.post); // Find and update data in post array for consistency do this instead of wasting an api call - setPosts(posts.map((post) => { - if (post.id === data.post.id) { - return data.post; - } - return post; - })); + setPosts(posts.map((post) => post.id === data.post.id ? data.post : post)); } }) .catch(standardErrorHandler(enqueueSnackbar)); From 3a0c0e204b0af8b163b89dc531e13e2641783393 Mon Sep 17 00:00:00 2001 From: dolf321 Date: Sat, 22 Jul 2023 11:33:18 +0300 Subject: [PATCH 35/38] FIX slight bug in comment component ``` --- web/src/components/forums/Comment/Comment.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/components/forums/Comment/Comment.jsx b/web/src/components/forums/Comment/Comment.jsx index 78c73c95..b0901402 100644 --- a/web/src/components/forums/Comment/Comment.jsx +++ b/web/src/components/forums/Comment/Comment.jsx @@ -39,9 +39,9 @@ export default function Comment({ {toRelativeDate(new Date(createdDate))} - + - + {hasReplies && Date: Sat, 22 Jul 2023 11:33:49 +0300 Subject: [PATCH 36/38] CHG cleaned up forum refresh logic --- web/src/pages/forums/Forum/Forum.jsx | 65 ++++++++++++++-------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/web/src/pages/forums/Forum/Forum.jsx b/web/src/pages/forums/Forum/Forum.jsx index eb80ebf5..c9e1cda9 100644 --- a/web/src/pages/forums/Forum/Forum.jsx +++ b/web/src/pages/forums/Forum/Forum.jsx @@ -47,13 +47,7 @@ export default function Forum({user}) { 'entityRanges': [], 'data': {}}], 'entityMap': {}}}], }]); const [selectedPost, setSelectedPost] = useState(undefined); - const [refreshSelectedPost, setRefreshSelectedPost] = useState(0); const [isDialogOpen, setIsDialogOpen] = useState(false); - const [refreshPosts, setRefreshPosts] = useState(0); - - const refresh = () => { - setRefreshPosts(refreshPosts + 1); - }; useEffect(() => { axios.get('api/public/courses/') @@ -66,40 +60,47 @@ export default function Forum({user}) { } }) .catch(standardErrorHandler(enqueueSnackbar)); + + setInterval(() => { + refreshPosts(false); + }, 15000); }, []); useEffect(() => { if (!selectedCourse) { return undefined; } + refreshPosts(); + }, [selectedCourse]); - axios.get(`api/public/forums/course/${selectedCourse.id}`) - .then((response) => { - const data = standardStatusHandler(response, enqueueSnackbar); - if (data) { - setPosts(data.posts); - setSelectedPost(data.posts[0]); - } - }) - .catch(standardErrorHandler(enqueueSnackbar)); - }, [selectedCourse, refreshPosts]); + const refreshPosts = async (select_post = true) => { + if (!selectedCourse) { + standardErrorHandler(enqueueSnackbar)(new Error('No course selected')); + return undefined; + } + axios.get(`api/public/forums/course/${selectedCourse.id}`).then((response) => { + const data = standardStatusHandler(response, enqueueSnackbar); + if (data) { + setPosts(data.posts); + if (select_post) setSelectedPost(data.posts[0]); + } + }).catch(standardErrorHandler(enqueueSnackbar)); + }; - useEffect(() => { + const refreshSelectedPost = async (post_id) => { if (!selectedPost) { + standardErrorHandler(enqueueSnackbar)(new Error('No post selected')); return undefined; } - - axios.get(`api/public/forums/post/${selectedPost.id}`) - .then((response) => { - const data = standardStatusHandler(response, enqueueSnackbar); - if (data) { - setSelectedPost(data.post); - // Find and update data in post array for consistency do this instead of wasting an api call - setPosts(posts.map((post) => post.id === data.post.id ? data.post : post)); - } - }) - .catch(standardErrorHandler(enqueueSnackbar)); - }, [refreshSelectedPost]); + axios.get(`api/public/forums/post/${post_id}`).then((response) => { + const data = standardStatusHandler(response, enqueueSnackbar); + if (data) { + setSelectedPost(data.post); + // Find and update data in post array for consistency do this instead of wasting an api call + setPosts(posts.map((post) => post.id === data.post.id ? data.post : post)); + } + }).catch(standardErrorHandler(enqueueSnackbar)); + }; const handleCourseSelect = (e) => { console.log(e.target.value); @@ -110,7 +111,7 @@ export default function Forum({user}) { // console.log(post); axios.post(`/api/public/forums/post`, {...post, course_id: selectedCourse.id}) .then(() => { - setRefreshPosts(refreshPosts + 1); + refreshPosts(); setIsDialogOpen(false); }) .catch(standardErrorHandler(enqueueSnackbar)); @@ -119,7 +120,7 @@ export default function Forum({user}) { const handleCreateComment = (comment) => { axios.post(`/api/public/forums/comment`, {...comment, post_id: selectedPost.id, course_id: selectedCourse.id}) .then(() => { - setRefreshSelectedPost(refreshSelectedPost + 1); + refreshSelectedPost(selectedPost.id); }) .catch(standardErrorHandler(enqueueSnackbar)); }; @@ -156,7 +157,7 @@ export default function Forum({user}) { )} From 0233fe8d3dfd6e29b89436702662fcfc2ea738fb Mon Sep 17 00:00:00 2001 From: dolf321 Date: Sat, 22 Jul 2023 11:34:07 +0300 Subject: [PATCH 37/38] CHG removed unecesary arrow function --- web/src/components/forums/Editor/Toolbar/Embed/EmbedImage.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/forums/Editor/Toolbar/Embed/EmbedImage.jsx b/web/src/components/forums/Editor/Toolbar/Embed/EmbedImage.jsx index 13832cce..239a64c0 100644 --- a/web/src/components/forums/Editor/Toolbar/Embed/EmbedImage.jsx +++ b/web/src/components/forums/Editor/Toolbar/Embed/EmbedImage.jsx @@ -91,7 +91,7 @@ export default function EmbedImage({embedImage}) { type="text" onChange={(e) => setImageURL(e.target.value)} /> - From 77094ab691a26582fdca41a5e45a7b2a2b241821 Mon Sep 17 00:00:00 2001 From: dolf321 Date: Sat, 22 Jul 2023 12:23:11 +0300 Subject: [PATCH 38/38] FIX useless parameter --- web/src/pages/forums/Forum/Forum.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/pages/forums/Forum/Forum.jsx b/web/src/pages/forums/Forum/Forum.jsx index c9e1cda9..4ea596ab 100644 --- a/web/src/pages/forums/Forum/Forum.jsx +++ b/web/src/pages/forums/Forum/Forum.jsx @@ -87,12 +87,12 @@ export default function Forum({user}) { }).catch(standardErrorHandler(enqueueSnackbar)); }; - const refreshSelectedPost = async (post_id) => { + const refreshSelectedPost = async () => { if (!selectedPost) { standardErrorHandler(enqueueSnackbar)(new Error('No post selected')); return undefined; } - axios.get(`api/public/forums/post/${post_id}`).then((response) => { + axios.get(`api/public/forums/post/${selectedPost.id}`).then((response) => { const data = standardStatusHandler(response, enqueueSnackbar); if (data) { setSelectedPost(data.post); @@ -120,7 +120,7 @@ export default function Forum({user}) { const handleCreateComment = (comment) => { axios.post(`/api/public/forums/comment`, {...comment, post_id: selectedPost.id, course_id: selectedCourse.id}) .then(() => { - refreshSelectedPost(selectedPost.id); + refreshSelectedPost(); }) .catch(standardErrorHandler(enqueueSnackbar)); };