mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
implemented replying in modal
This commit is contained in:
+157
-11
@@ -1,18 +1,164 @@
|
|||||||
import React from 'react';
|
import React, { useRef, useState, useEffect } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { usePublishComment } from '@plebbit/plebbit-react-hooks';
|
||||||
import { StyledModal } from './styled/ReplyModal.styled';
|
import { StyledModal } from './styled/ReplyModal.styled';
|
||||||
import useGeneralStore from '../hooks/stores/useGeneralStore';
|
import useGeneralStore from '../hooks/stores/useGeneralStore';
|
||||||
import Modal from 'react-modal';
|
import Modal from 'react-modal';
|
||||||
import Draggable from 'react-draggable';
|
import Draggable from 'react-draggable';
|
||||||
|
import useError from '../hooks/useError';
|
||||||
|
import useSuccess from '../hooks/useSuccess';
|
||||||
|
|
||||||
|
|
||||||
const ReplyModal = ({ isOpen, closeModal }) => {
|
const ReplyModal = ({ isOpen, closeModal }) => {
|
||||||
const selectedStyle = useGeneralStore(state => state.selectedStyle);
|
const {
|
||||||
|
captchaResponse, setCaptchaResponse,
|
||||||
|
setChallengesArray,
|
||||||
|
setIsCaptchaOpen,
|
||||||
|
setPendingComment,
|
||||||
|
selectedAddress,
|
||||||
|
selectedReply,
|
||||||
|
selectedStyle,
|
||||||
|
selectedThread,
|
||||||
|
} = useGeneralStore(state => state);
|
||||||
|
|
||||||
const handleCloseModal = () => {
|
const navigate = useNavigate();
|
||||||
closeModal();
|
|
||||||
|
const nodeRef = useRef(null);
|
||||||
|
|
||||||
|
const nameRef = useRef();
|
||||||
|
const commentRef = useRef();
|
||||||
|
const linkRef = useRef();
|
||||||
|
|
||||||
|
const onChallengeVerificationRef = useRef();
|
||||||
|
|
||||||
|
const [errorMessage, setErrorMessage] = useState(null);
|
||||||
|
const [successMessage, setSuccessMessage] = useState(null);
|
||||||
|
useError(errorMessage, [errorMessage]);
|
||||||
|
useSuccess(successMessage, [successMessage]);
|
||||||
|
|
||||||
|
|
||||||
|
const onChallengeVerification = (challengeVerification) => {
|
||||||
|
if (challengeVerification.challengeSuccess === true) {
|
||||||
|
setSuccessMessage('challenge success', {publishedCid: challengeVerification.publication?.cid});
|
||||||
|
navigate(`/p/${selectedAddress}/c/${selectedThread}`);
|
||||||
|
}
|
||||||
|
else if (challengeVerification.challengeSuccess === false) {
|
||||||
|
setErrorMessage('challenge failed', {reason: challengeVerification.reason, errors: challengeVerification.errors});
|
||||||
|
setErrorMessage("Error: You seem to have mistyped the CAPTCHA. Please try again.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const onChallenge = async (challenges, comment) => {
|
||||||
|
setPendingComment(comment);
|
||||||
|
let challengeAnswers = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
challengeAnswers = await getChallengeAnswersFromUser(challenges)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
setErrorMessage(error);
|
||||||
|
}
|
||||||
|
if (challengeAnswers) {
|
||||||
|
await comment.publishChallengeAnswers(challengeAnswers)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const [publishCommentOptions, setPublishCommentOptions] = useState({
|
||||||
|
subplebbitAddress: selectedAddress,
|
||||||
|
onChallenge,
|
||||||
|
onChallengeVerification: onChallengeVerificationRef.current,
|
||||||
|
onError: (error) => {
|
||||||
|
setErrorMessage(error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const { publishComment, index } = usePublishComment(publishCommentOptions);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (index !== undefined) {
|
||||||
|
navigate(`/profile/c/${index}`);
|
||||||
|
setSuccessMessage('Comment pending with index ' + index + '.');
|
||||||
|
}
|
||||||
|
}, [index]);
|
||||||
|
|
||||||
|
|
||||||
|
onChallengeVerificationRef.current = onChallengeVerification;
|
||||||
|
|
||||||
|
|
||||||
|
const resetFields = () => {
|
||||||
|
nameRef.current.value = '';
|
||||||
|
commentRef.current.value = '';
|
||||||
|
linkRef.current.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPublishCommentOptions((prevPublishCommentOptions) => ({
|
||||||
|
...prevPublishCommentOptions,
|
||||||
|
subplebbitAddress: selectedAddress,
|
||||||
|
onChallengeVerification: onChallengeVerificationRef.current,
|
||||||
|
}));
|
||||||
|
}, [selectedAddress]);
|
||||||
|
|
||||||
|
|
||||||
|
const handleSubmit = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
setPublishCommentOptions((prevPublishCommentOptions) => ({
|
||||||
|
...prevPublishCommentOptions,
|
||||||
|
displayName: nameRef.current.value || undefined,
|
||||||
|
content: commentRef.current.value || undefined,
|
||||||
|
link: linkRef.current.value || undefined,
|
||||||
|
parentCid: selectedReply,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (publishCommentOptions.content) {
|
||||||
|
(async () => {
|
||||||
|
await publishComment();
|
||||||
|
resetFields();
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
}, [publishCommentOptions]);
|
||||||
|
|
||||||
|
|
||||||
|
const getChallengeAnswersFromUser = async (challenges) => {
|
||||||
|
setChallengesArray(challenges);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const imageString = challenges?.challenges[0].challenge;
|
||||||
|
const imageSource = `data:image/png;base64,${imageString}`;
|
||||||
|
const challengeImg = new Image();
|
||||||
|
challengeImg.src = imageSource;
|
||||||
|
|
||||||
|
challengeImg.onload = () => {
|
||||||
|
setIsCaptchaOpen(true);
|
||||||
|
|
||||||
|
const handleKeyDown = (event) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
resolve(captchaResponse);
|
||||||
|
setIsCaptchaOpen(false);
|
||||||
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setCaptchaResponse('');
|
||||||
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
|
};
|
||||||
|
|
||||||
|
challengeImg.onerror = () => {
|
||||||
|
reject(setErrorMessage('Could not load challenges'));
|
||||||
|
};
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const nodeRef = React.useRef(null);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledModal
|
<StyledModal
|
||||||
@@ -26,21 +172,21 @@ const ReplyModal = ({ isOpen, closeModal }) => {
|
|||||||
<Draggable handle=".modal-header" nodeRef={nodeRef}>
|
<Draggable handle=".modal-header" nodeRef={nodeRef}>
|
||||||
<div className="modal-content" ref={nodeRef}>
|
<div className="modal-content" ref={nodeRef}>
|
||||||
<div className="modal-header">
|
<div className="modal-header">
|
||||||
Reply to Thread
|
Reply to c/{selectedReply}
|
||||||
<button className="icon" onClick={handleCloseModal} title="close" />
|
<button className="icon" onClick={() => closeModal()} title="close" />
|
||||||
</div>
|
</div>
|
||||||
<div id="form">
|
<div id="form">
|
||||||
<div>
|
<div>
|
||||||
<input id="name" type="text" placeholder="Name"></input>
|
<input id="name" type="text" placeholder="Name" ref={nameRef} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input id="name" type="text" placeholder="Embed link" disabled></input>
|
<input id="name" type="text" placeholder="Embed link" ref={linkRef} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<textarea rows="4" placeholder="Comment" wrap="soft"></textarea>
|
<textarea rows="4" placeholder="Comment" wrap="soft" ref={commentRef} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button id="next">Post</button>
|
<button id="next" onClick={handleSubmit}>Post</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const Board = () => {
|
|||||||
isSettingsOpen, setIsSettingsOpen,
|
isSettingsOpen, setIsSettingsOpen,
|
||||||
setPendingComment,
|
setPendingComment,
|
||||||
selectedAddress, setSelectedAddress,
|
selectedAddress, setSelectedAddress,
|
||||||
|
setSelectedReply,
|
||||||
selectedStyle,
|
selectedStyle,
|
||||||
setSelectedThread,
|
setSelectedThread,
|
||||||
selectedTitle, setSelectedTitle,
|
selectedTitle, setSelectedTitle,
|
||||||
@@ -476,7 +477,10 @@ const Board = () => {
|
|||||||
|
|
||||||
<span key={`pn-${thread.cid}`} className="post-number">
|
<span key={`pn-${thread.cid}`} className="post-number">
|
||||||
<Link to="" key={`pl1-${thread.cid}`} onClick={() => {}} title="Link to this post">c/</Link>
|
<Link to="" key={`pl1-${thread.cid}`} onClick={() => {}} title="Link to this post">c/</Link>
|
||||||
<button id="reply-button" style={{ all: 'unset', cursor: 'pointer' }} key={`pl2-${thread.cid}`} onClick={() => setIsReplyOpen(true)} title="Reply to this post">{thread.shortCid}</button>
|
<button id="reply-button" style={{ all: 'unset', cursor: 'pointer' }} key={`pl2-${thread.cid}`}
|
||||||
|
onClick={() => {
|
||||||
|
setIsReplyOpen(true); setSelectedReply(thread.shortCid);
|
||||||
|
}} title="Reply to this post">{thread.shortCid}</button>
|
||||||
|
|
||||||
<span key={`rl1-${thread.cid}`}>
|
<span key={`rl1-${thread.cid}`}>
|
||||||
[
|
[
|
||||||
@@ -566,7 +570,10 @@ const Board = () => {
|
|||||||
|
|
||||||
<span key={`pn-${reply.cid}`} className="post-number">
|
<span key={`pn-${reply.cid}`} className="post-number">
|
||||||
<Link to="" key={`pl1-${reply.cid}`} onClick={() => {}} title="Link to this post">c/</Link>
|
<Link to="" key={`pl1-${reply.cid}`} onClick={() => {}} title="Link to this post">c/</Link>
|
||||||
<button id="reply-button" style={{ all: 'unset', cursor: 'pointer' }} key={`pl2-${reply.cid}`} onClick={() => setIsReplyOpen(true)} title="Reply to this post">{reply.shortCid}</button>
|
<button id="reply-button" style={{ all: 'unset', cursor: 'pointer' }} key={`pl2-${reply.cid}`}
|
||||||
|
onClick={() => {
|
||||||
|
setIsReplyOpen(true); setSelectedReply(reply.shortCid);
|
||||||
|
}} title="Reply to this post">{reply.shortCid}</button>
|
||||||
</span>
|
</span>
|
||||||
<button key={`pmb-${reply.cid}`} className="post-menu-button" onClick={() => {}} title="Post menu" style={{ all: 'unset', cursor: 'pointer' }} data-cmd="post-menu">▶</button>
|
<button key={`pmb-${reply.cid}`} className="post-menu-button" onClick={() => {}} title="Post menu" style={{ all: 'unset', cursor: 'pointer' }} data-cmd="post-menu">▶</button>
|
||||||
<div id="backlink-id" className="backlink">
|
<div id="backlink-id" className="backlink">
|
||||||
@@ -706,7 +713,9 @@ const Board = () => {
|
|||||||
{getDate(thread.timestamp)}
|
{getDate(thread.timestamp)}
|
||||||
|
|
||||||
<Link to="" key={`mob-no-${thread.cid}`} onClick={() => {}} title="Link to this post">c/</Link>
|
<Link to="" key={`mob-no-${thread.cid}`} onClick={() => {}} title="Link to this post">c/</Link>
|
||||||
<button id="reply-button" style={{ all: 'unset', cursor: 'pointer' }} key={`mob-no2-${thread.cid}`} onClick={() => setIsReplyOpen(true)} title="Reply to this post">{thread.shortCid}</button>
|
<button id="reply-button" style={{ all: 'unset', cursor: 'pointer' }} key={`mob-no2-${thread.cid}`} onClick={() => {
|
||||||
|
setIsReplyOpen(true); setSelectedReply(thread.shortCid);
|
||||||
|
}} title="Reply to this post">{thread.shortCid}</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{thread.link ? (
|
{thread.link ? (
|
||||||
@@ -800,7 +809,9 @@ const Board = () => {
|
|||||||
<span key={`mob-dt-${reply.cid}`} className="date-time-mobile">
|
<span key={`mob-dt-${reply.cid}`} className="date-time-mobile">
|
||||||
{getDate(reply.timestamp)}
|
{getDate(reply.timestamp)}
|
||||||
<Link to="" key={`mob-pl1-${reply.cid}`} onClick={() => {}} title="Link to this post">c/</Link>
|
<Link to="" key={`mob-pl1-${reply.cid}`} onClick={() => {}} title="Link to this post">c/</Link>
|
||||||
<button id="reply-button" style={{ all: 'unset', cursor: 'pointer' }} key={`mob-pl2-${reply.cid}`} onClick={() => setIsReplyOpen(true)} title="Reply to this post">{reply.shortCid}</button>
|
<button id="reply-button" style={{ all: 'unset', cursor: 'pointer' }} key={`mob-pl2-${reply.cid}`} onClick={() => {
|
||||||
|
setIsReplyOpen(true); setSelectedReply(reply.shortCid);
|
||||||
|
}} title="Reply to this post">{reply.shortCid}</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{reply.link ? (
|
{reply.link ? (
|
||||||
|
|||||||
@@ -95,7 +95,6 @@ const Thread = () => {
|
|||||||
|
|
||||||
|
|
||||||
const onChallenge = async (challenges, comment) => {
|
const onChallenge = async (challenges, comment) => {
|
||||||
console.log(comment);
|
|
||||||
setPendingComment(comment);
|
setPendingComment(comment);
|
||||||
let challengeAnswers = [];
|
let challengeAnswers = [];
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ const useGeneralStore = create((set) => ({
|
|||||||
selectedAddress: '',
|
selectedAddress: '',
|
||||||
setSelectedAddress: (address) => set({ selectedAddress: address }),
|
setSelectedAddress: (address) => set({ selectedAddress: address }),
|
||||||
|
|
||||||
|
selectedReply: '',
|
||||||
|
setSelectedReply: (reply) => set({ selectedReply: reply }),
|
||||||
|
|
||||||
selectedStyle: localStorage.getItem('selectedStyle') || 'Yotsuba',
|
selectedStyle: localStorage.getItem('selectedStyle') || 'Yotsuba',
|
||||||
setSelectedStyle: (style) => {
|
setSelectedStyle: (style) => {
|
||||||
localStorage.setItem('selectedStyle', style);
|
localStorage.setItem('selectedStyle', style);
|
||||||
|
|||||||
Reference in New Issue
Block a user