From 71911063b0107e5b22311a970387c0254fe8e7b1 Mon Sep 17 00:00:00 2001 From: mrfry <mr.fry@tutanota.com> Date: Thu, 4 Mar 2021 21:31:32 +0100 Subject: [PATCH] Added comments to news items --- src/components/comments.js | 169 ++++++++++++++++++++++++++ src/components/comments.module.css | 80 ++++++++++++ src/components/newsEntry.js | 33 ++++- src/components/reactButton.js | 3 + src/components/reactButton.module.css | 10 +- src/components/tooltip.js | 8 +- src/components/tooltip.module.css | 1 - src/constants.json | 2 +- src/pages/index.js | 73 ++++++++++- 9 files changed, 357 insertions(+), 22 deletions(-) create mode 100644 src/components/comments.js create mode 100644 src/components/comments.module.css diff --git a/src/components/comments.js b/src/components/comments.js new file mode 100644 index 0000000..909e573 --- /dev/null +++ b/src/components/comments.js @@ -0,0 +1,169 @@ +import React, { useState } from 'react' + +import ReactButton from './reactButton.js' + +import styles from './comments.module.css' + +function CommentInput({ onSubmit }) { + const [val, setVal] = useState('') + return ( + <div className={styles.commentAreaContainer}> + <textarea + autoFocus + className={styles.commentArea} + value={val} + onChange={(e) => { + setVal(e.target.value) + }} + /> + <div> + <span + onClick={() => { + onSubmit(val) + }} + className={styles.button} + > + Submit + </span> + </div> + </div> + ) +} + +function Comment({ comment, index, onComment, onDelete, onReact, uid }) { + const [displayed, setDisplayed] = useState(true) + const [commenting, setCommenting] = useState(false) + const { text, subComments, date, user, reacts } = comment + const own = uid === user + + return ( + <div className={styles.comment}> + <div className={`${styles.commentData} ${own && styles.ownComment}`}> + <div className={styles.commentHeader}> + <div className={styles.userContainer}> + <div + className={styles.showHide} + onClick={() => { + setDisplayed(!displayed) + }} + > + {displayed ? '[-]' : '[+]'} + </div> + <div>User #{user}</div> + </div> + <div>{date}</div> + </div> + <div className={`${!displayed && styles.hidden}`}> + <div className={styles.commentText}> {text}</div> + <div className={styles.actionsContainer}> + <div + className={styles.button} + onClick={() => { + setCommenting(true) + }} + > + Reply... + </div> + {own && ( + <div + className={styles.button} + onClick={() => { + onDelete([index]) + }} + > + Delete + </div> + )} + <ReactButton + onClick={(reaction, isDelete) => { + onReact([index], reaction, isDelete) + }} + uid={uid} + existingReacts={reacts} + /> + </div> + {commenting && ( + <CommentInput + onSubmit={(e) => { + onComment([index], e) + setCommenting(false) + }} + /> + )} + </div> + </div> + <div className={`${!displayed && styles.hidden}`}> + {subComments && + subComments.map((sc, i) => { + return ( + <Comment + comment={sc} + onReact={(path, reaction, isDelete) => { + onReact([...path, index], reaction, isDelete) + }} + onDelete={(path) => { + onDelete([...path, index]) + }} + onComment={(path, text) => { + onComment([...path, index], text) + }} + index={i} + key={i} + uid={uid} + /> + ) + })} + </div> + </div> + ) +} + +export default function Comments({ + comments, + onComment, + onDelete, + onReact, + uid, +}) { + const [addingNewComment, setAddingNewComment] = useState(false) + return ( + <div> + {comments && comments.length > 0 ? ( + comments.map((comment, i) => { + return ( + <Comment + onReact={onReact} + onComment={onComment} + onDelete={onDelete} + comment={comment} + index={i} + key={i} + uid={uid} + /> + ) + }) + ) : ( + <div> + <div>No comments yet</div> + </div> + )} + {addingNewComment ? ( + <CommentInput + onSubmit={(e) => { + setAddingNewComment(false) + onComment([], e) + }} + /> + ) : ( + <span + onClick={() => { + setAddingNewComment(true) + }} + className={styles.button} + > + Add new + </span> + )} + </div> + ) +} diff --git a/src/components/comments.module.css b/src/components/comments.module.css new file mode 100644 index 0000000..ef4c72d --- /dev/null +++ b/src/components/comments.module.css @@ -0,0 +1,80 @@ +.comment { + margin-left: 25px; + padding: 8px 0px; +} + +.commentData { + padding: 5px 2px; + border-left: 1px solid var(--text-color); + background-color: #222; +} + +.commentHeader { + display: flex; + justify-content: space-between; +} + +.commentHeader > div { + font-weight: bold; + margin: 2px; +} + +.userContainer { + display: flex; + flex-direction: row; +} + +.userContainer > div { + padding: 0px 3px; +} + +.commentText { + margin: 2px; + padding: 10px 5px; +} + +.ownComment { + border-left: 1px solid yellow; +} + +.showHide { + cursor: pointer; +} + +.hidden { + display: none; +} + +.actionsContainer { + display: flex; + align-items: center; +} + +.button { + margin: 2px 2px; + padding: 0px 10px; + border: 1px solid #444; + border-radius: 6px; + cursor: pointer; +} + +.button:hover { + background-color: #444; +} + +.commentArea { + color: var(--text-color); + background-color: var(--background-color); + font-size: 16px; + box-sizing: border-box; + height: 120px; + width: 100%; +} + +.commentAreaContainer { + margin: 0px 8px 3px 8px; +} + +.commentAreaContainer > div { + margin: 5px 0px; +} diff --git a/src/components/newsEntry.js b/src/components/newsEntry.js index 58228d2..f899f52 100644 --- a/src/components/newsEntry.js +++ b/src/components/newsEntry.js @@ -1,29 +1,52 @@ import React from 'react' import ReactButton from './reactButton.js' +import Comments from './comments.js' import styles from './newsEntry.module.css' -export default function NewsEntry({ newsKey, newsItem, uid, onReact }) { +export default function NewsEntry({ + newsKey, + newsItem, + uid, + onReact, + onComment, + onDelete, +}) { + const { reacts, title, body, comments } = newsItem + return ( <div> <div className={styles.newsContainer}> <div className={styles.itemNumber}>{newsKey} :</div> <div className={styles.newsTitle} - dangerouslySetInnerHTML={{ __html: newsItem.title }} + dangerouslySetInnerHTML={{ __html: title }} /> <div className={styles.newsBody} - dangerouslySetInnerHTML={{ __html: newsItem.body }} + dangerouslySetInnerHTML={{ __html: body }} /> </div> <ReactButton - existingReacts={newsItem.reacts} + existingReacts={reacts} uid={uid} - onClick={onReact} + onClick={(reaction, isDelete) => { + onReact({ type: 'news', reaction, isDelete }) + }} /> <hr /> + <Comments + uid={uid} + onReact={(path, reaction, isDelete) => { + onReact({ type: 'comment', path, reaction, isDelete }) + }} + onComment={onComment} + onDelete={onDelete} + comments={comments} + /> + <hr /> + <hr /> </div> ) } diff --git a/src/components/reactButton.js b/src/components/reactButton.js index 1937463..d23aa3c 100644 --- a/src/components/reactButton.js +++ b/src/components/reactButton.js @@ -5,6 +5,7 @@ import Tooltip from './tooltip.js' import styles from './reactButton.module.css' import reactions from '../data/reactions.json' +// TODO: fixdis const breakEvery = 7 function ExistingReacts({ existingReacts, onClick, uid }) { @@ -20,6 +21,7 @@ function ExistingReacts({ existingReacts, onClick, uid }) { } return ( <div + title={currReact.join(', ')} className={`${currReact.includes(uid) && styles.reacted}`} key={key} onClick={() => { @@ -78,6 +80,7 @@ export default function ReactButton({ onClick, existingReacts, uid }) { return ( <div + className={styles.reactContainer} onMouseEnter={() => { setOpened(true) }} diff --git a/src/components/reactButton.module.css b/src/components/reactButton.module.css index 03217c8..9c53a71 100644 --- a/src/components/reactButton.module.css +++ b/src/components/reactButton.module.css @@ -15,12 +15,10 @@ .reactionContainer > div { margin: 2px 2px; - padding: 0px 8px; - background-color: #444; + padding: 0px 10px; + border: 1px solid #444; border-radius: 6px; cursor: pointer; - - font-size: 18px; } .reactionContainer > div:hover { @@ -35,3 +33,7 @@ .reacted { color: yellow; } + +.reactContainer { + display: inline-block; +} diff --git a/src/components/tooltip.js b/src/components/tooltip.js index a629003..f8a8987 100644 --- a/src/components/tooltip.js +++ b/src/components/tooltip.js @@ -3,11 +3,9 @@ import styles from './tooltip.module.css' export default function Tooltip({ children, text, opened }) { return ( - <div> - <div className={styles.tooltip}> - {text()} - {opened && <span className={styles.tooltiptext}>{children}</span>} - </div> + <div className={styles.tooltip}> + {text()} + {opened && <span className={styles.tooltiptext}>{children}</span>} </div> ) } diff --git a/src/components/tooltip.module.css b/src/components/tooltip.module.css index 59d5723..05b9267 100644 --- a/src/components/tooltip.module.css +++ b/src/components/tooltip.module.css @@ -1,7 +1,6 @@ .tooltip { position: relative; display: inline-block; - border-bottom: 1px dotted black; } .tooltip .tooltiptext { diff --git a/src/constants.json b/src/constants.json index 8650745..dec44ec 100644 --- a/src/constants.json +++ b/src/constants.json @@ -1,6 +1,6 @@ { "siteUrl": "https://qmining.frylabs.net/", - "apiUrl": "https://api.frylabs.net/", + "apiUrl": "http://localhost:8080/", "mobileWindowWidth": 700, "maxQuestionsToRender": 250 } diff --git a/src/pages/index.js b/src/pages/index.js index bbc157c..23f193f 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -57,9 +57,50 @@ export default function Index({ globalData }) { let newsEntryData = news[key] return ( <NewsEntry - onReact={(reaction, isDelete) => { - console.log(reaction, isDelete) - fetch(constants.apiUrl + 'infos', { + onReact={({ type, path, reaction, isDelete }) => { + if (type === 'news') { + fetch(constants.apiUrl + 'infos', { + method: 'POST', + credentials: 'include', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + react: reaction, + newsKey: key, + isDelete: isDelete, + }), + }).then((res) => { + // TODO: dont refetch news + fetchNews().then((res) => { + setNews(res) + }) + }) + } else if (type === 'comment') { + fetch(constants.apiUrl + 'comment', { + method: 'POST', + credentials: 'include', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + type: 'reaction', + newsKey: key, + path: path, + reaction: reaction, + isDelete: isDelete, + }), + }).then((res) => { + fetchNews().then((res) => { + setNews(res) + }) + }) + } + }} + onDelete={(path) => { + fetch(constants.apiUrl + 'comment', { method: 'POST', credentials: 'include', headers: { @@ -67,11 +108,31 @@ export default function Index({ globalData }) { 'Content-Type': 'application/json', }, body: JSON.stringify({ - react: reaction, + type: 'delete', + path: path, newsKey: key, - isDelete: isDelete, }), - }).then((res) => { + }).then(() => { + fetchNews().then((res) => { + setNews(res) + }) + }) + }} + onComment={(path, text) => { + fetch(constants.apiUrl + 'comment', { + method: 'POST', + credentials: 'include', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + type: 'add', + path: path, + text: text, + newsKey: key, + }), + }).then(() => { fetchNews().then((res) => { setNews(res) })