-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Used slate-react to input and show comments Added new route called '/comments' - post, creates new comment (no form validation on backend) - get, gets all comments + replies with ticket id - Aggregates replies in an array in the response - No edits and deletion yet Every comment is stored in comments (replies too) - Reference code is either _id of parent comment or ticket_id No error toasts yet Had a few issues with getting the comments to update with slate and emptying the input, so whenever function getComments in frontend is called, it forcefully rerenders the whole comment section Also had issues with placeholder text, since it wouldn't follow the padding I gave to the text area - decided to remove placeholder altogether resolves #65 --------- Co-authored-by: William Li <[email protected]>
- Loading branch information
1 parent
2b46ae6
commit 3aed4f8
Showing
11 changed files
with
644 additions
and
117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
const { createComment, getAllComments } = require('../service/comments.service') | ||
|
||
const getAllCommentsController = (req, res) => { | ||
const ticketID = req.params.id | ||
getAllComments(ticketID) | ||
.then((data) => res.send(data)) | ||
.catch((err) => { | ||
res.status(500).send(err) | ||
console.log(err) | ||
}) | ||
} | ||
|
||
const createCommentController = (req, res) => { | ||
createComment(req.body) | ||
.then((data) => { | ||
console.log(data) | ||
res.send(data) | ||
}) | ||
.catch((error) => { | ||
console.log(error) | ||
res.status(500).send(error) | ||
}) | ||
} | ||
|
||
module.exports = { | ||
createCommentController, | ||
getAllCommentsController, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
const router = require('express').Router() | ||
const { authenticateUser } = require('../auth/middleware') | ||
const { | ||
createCommentController, | ||
getAllCommentsController, | ||
} = require('../controller/comments.controller') | ||
|
||
router.route('/:id').get(authenticateUser, getAllCommentsController) | ||
|
||
router.route('/').post(authenticateUser, createCommentController) | ||
|
||
//TODO: delete & edit | ||
|
||
module.exports = router |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
const Comment = require('../models/comment.model') | ||
|
||
const createComment = async (body) => { | ||
const comment = new Comment(body) | ||
const newComment = await comment.save() | ||
return newComment | ||
} | ||
|
||
const getAllComments = async (code) => { | ||
//add reply aggregation, sortby | ||
const res = await Comment.aggregate([ | ||
{ | ||
$match: { | ||
reference_code: code, | ||
}, | ||
}, | ||
{ | ||
$lookup: { | ||
from: 'comments', | ||
let: { idStr: { $toString: '$_id' } }, // Define variable for use in the pipeline | ||
pipeline: [ | ||
{ | ||
$match: { | ||
$expr: { $eq: ['$reference_code', '$$idStr'] }, // Use the variable to match documents | ||
}, | ||
}, | ||
{ $sort: { createdAt: 1 } }, // Sort matching documents in ascending order | ||
], | ||
as: 'replies', | ||
}, | ||
}, | ||
{ | ||
$sort: { createdAt: -1 }, | ||
}, | ||
{ | ||
$set: { | ||
replies: '$replies', | ||
}, | ||
}, | ||
]) | ||
return res | ||
} | ||
|
||
module.exports = { | ||
createComment, | ||
getAllComments, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import { useCallback, useMemo, useState } from 'react' | ||
import isHotkey from 'is-hotkey' | ||
import { Editable, withReact, Slate } from 'slate-react' | ||
import { createEditor } from 'slate' | ||
import { withHistory } from 'slate-history' | ||
import { useAuth } from '../contexts/AuthContext' | ||
import { axiosPreset } from '../axiosConfig' | ||
import { Box, Button } from '@chakra-ui/react' | ||
|
||
import { | ||
BlockButton, | ||
Element, | ||
Leaf, | ||
MarkButton, | ||
Toolbar, | ||
toggleMark, | ||
} from './SlateComponents' | ||
|
||
const HOTKEYS = { | ||
'mod+b': 'bold', | ||
'mod+i': 'italic', | ||
'mod+u': 'underline', | ||
} | ||
|
||
//Cleans leading and ending white space | ||
const cleanInput = (val) => { | ||
const textList = val.map((item) => item['children'][0]['text']) | ||
|
||
const firstIndex = textList.findIndex((text) => text != '') | ||
if (firstIndex == -1) { | ||
return [] | ||
} | ||
|
||
const lastIndex = textList.findLastIndex((text) => text != '') | ||
|
||
return val.slice(firstIndex, lastIndex + 1) | ||
} | ||
|
||
//Disables the "send" button if input isn't valid | ||
const invalidInput = (val) => { | ||
for (let i = 0; i < val.length; i++) { | ||
if (val[i]['children'][0]['text'] != '') { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
const CommentInput = ({ code, getComments, reply, onClose, ticket }) => { | ||
const renderElement = useCallback((props) => <Element {...props} />, []) | ||
const renderLeaf = useCallback((props) => <Leaf {...props} />, []) | ||
const editor = useMemo(() => withHistory(withReact(createEditor())), []) | ||
const [loading, setLoading] = useState(false) | ||
const auth = useAuth() | ||
const [val, setVal] = useState(editor.children) | ||
const handleSubmit = (ref, ticket) => { | ||
setLoading(true) | ||
const comment = cleanInput(val) | ||
if (comment.length === 0) { | ||
return | ||
} | ||
const payload = { | ||
author_id: auth.currentUser.uid, | ||
comment: comment, | ||
reference_code: ref, | ||
} | ||
axiosPreset | ||
.post('/comments', payload) | ||
.then(() => getComments(ticket).then(() => setLoading(false))) | ||
|
||
.catch(() => setLoading(false)) | ||
} | ||
|
||
return ( | ||
<Slate | ||
editor={editor} | ||
initialValue={initialValue} | ||
onChange={() => { | ||
setVal([...editor.children]) | ||
}} | ||
> | ||
<Editable | ||
style={{ | ||
marginTop: '10px', | ||
padding: '20px', | ||
paddingLeft: '30px', | ||
minHeight: '150px', | ||
border: '2px #f0f0f0 solid', | ||
borderRadius: '10px', | ||
}} | ||
renderElement={renderElement} | ||
renderLeaf={renderLeaf} | ||
spellCheck | ||
autoFocus={reply} | ||
onKeyDown={(event) => { | ||
for (const hotkey in HOTKEYS) { | ||
if (isHotkey(hotkey, event)) { | ||
event.preventDefault() | ||
const mark = HOTKEYS[hotkey] | ||
toggleMark(editor, mark) | ||
} | ||
} | ||
}} | ||
/> | ||
<Box | ||
display="flex" | ||
width="100%" | ||
justifyContent="space-between" | ||
paddingLeft="5px" | ||
> | ||
<Toolbar> | ||
<MarkButton format="bold" icon="format_bold" /> | ||
<MarkButton format="italic" icon="format_italic" /> | ||
<MarkButton format="underline" icon="format_underlined" /> | ||
<BlockButton format="block-quote" icon="format_quote" /> | ||
<BlockButton | ||
format="numbered-list" | ||
icon="format_list_numbered" | ||
/> | ||
<BlockButton | ||
format="bulleted-list" | ||
icon="format_list_bulleted" | ||
/> | ||
</Toolbar> | ||
<Box | ||
display="flex" | ||
gap="10px" | ||
marginTop="5px" | ||
paddingRight="5px" | ||
> | ||
{reply && ( | ||
<Button | ||
onClick={onClose} | ||
padding="10px" | ||
height="32px" | ||
colorScheme="red" | ||
> | ||
Cancel | ||
</Button> | ||
)} | ||
<Button | ||
isLoading={loading} | ||
disabled={val.length === 0 || invalidInput(val)} | ||
padding="10px" | ||
height="32px" | ||
colorScheme="blue" | ||
onClick={() => { | ||
handleSubmit(code, ticket) | ||
}} | ||
> | ||
{reply ? 'Reply' : 'Comment'} | ||
</Button> | ||
</Box> | ||
</Box> | ||
</Slate> | ||
) | ||
} | ||
|
||
//TODO: If replying another reply, make the initial value quote the comment above (makes it easier to keep track who's replying to who) | ||
const initialValue = [ | ||
{ | ||
type: 'paragraph', | ||
children: [{ text: '' }], | ||
}, | ||
] | ||
|
||
export default CommentInput |
Oops, something went wrong.