Files
5chan/src/components/modals/ReplyModal.jsx
T

339 lines
9.9 KiB
React
Raw Normal View History

2023-04-27 12:36:21 +02:00
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useAccount, usePublishComment } from '@plebbit/plebbit-react-hooks';
2023-03-17 14:35:03 +01:00
import Modal from 'react-modal';
import Draggable from 'react-draggable';
2023-06-26 21:55:26 +02:00
import { StyledModal } from '../styled/modals/ReplyModal.styled';
import useAnonMode from '../../hooks/useAnonMode';
2023-05-24 12:15:43 +02:00
import useError from '../../hooks/useError';
2023-06-26 21:55:26 +02:00
import useAnonModeStore from '../../hooks/stores/useAnonModeStore';
import useGeneralStore from '../../hooks/stores/useGeneralStore';
2023-03-17 14:35:03 +01:00
const ReplyModal = ({ isOpen, closeModal }) => {
2023-04-12 21:00:51 +02:00
const {
captchaResponse, setCaptchaResponse,
setChallengesArray,
setIsCaptchaOpen,
setPendingComment,
2023-04-24 15:48:30 +02:00
setPendingCommentIndex,
2023-06-24 12:38:14 +02:00
replyQuoteCid,
setResolveCaptchaPromise,
selectedAddress,
2023-04-12 21:56:25 +02:00
selectedParentCid,
selectedShortCid,
2023-04-12 21:00:51 +02:00
selectedStyle,
2023-06-24 16:36:59 +02:00
selectedText, setSelectedText,
2023-06-24 12:38:14 +02:00
triggerInsertion,
2023-04-12 21:00:51 +02:00
} = useGeneralStore(state => state);
2023-03-17 14:35:03 +01:00
2023-06-26 21:55:26 +02:00
const { anonymousMode } = useAnonModeStore();
const account = useAccount();
2023-06-07 21:24:17 +02:00
2023-06-14 16:24:25 +02:00
const [, setNewErrorMessage] = useError();
2023-04-12 21:00:51 +02:00
const nodeRef = useRef(null);
const nameRef = useRef();
const commentRef = useRef();
const linkRef = useRef();
2023-04-27 12:36:21 +02:00
const [triggerPublishComment, setTriggerPublishComment] = useState(false);
2023-05-11 13:09:20 +02:00
const [isMobile, setIsMobile] = useState(window.innerWidth <= 480);
2023-06-26 21:55:26 +02:00
const [executeAnonMode, setExecuteAnonMode] = useState(false);
2023-06-26 21:55:26 +02:00
useAnonMode(selectedParentCid, anonymousMode && executeAnonMode);
2023-04-12 21:00:51 +02:00
2023-05-11 13:09:20 +02:00
useEffect(() => {
const handleResize = () => setIsMobile(window.innerWidth <= 480);
window.addEventListener('resize', handleResize);
// Cleanup function
return () => {
window.removeEventListener('resize', handleResize);
};
}, [setIsMobile]);
const onModalOpen = () => {
if (commentRef.current) {
2023-06-24 16:36:59 +02:00
if (selectedText) {
commentRef.current.value += '\n';
}
commentRef.current.focus();
2023-06-24 16:36:59 +02:00
const len = commentRef.current.value.length;
commentRef.current.setSelectionRange(len, len);
}
};
2023-06-24 12:38:14 +02:00
const insertAtCursor = (inputElement, valueToInsert) => {
const startPos = inputElement.selectionStart || inputElement.value.length;
const endPos = startPos + valueToInsert.length;
inputElement.setRangeText(valueToInsert, startPos, startPos, 'end');
inputElement.setSelectionRange(endPos, endPos);
}
useEffect(() => {
if (replyQuoteCid && commentRef.current) {
const prefixedReplyQuoteCid = `c/${replyQuoteCid}\n`;
insertAtCursor(commentRef.current, prefixedReplyQuoteCid);
}
}, [triggerInsertion, replyQuoteCid]);
const getSelectedText = useCallback(() => {
const text = document.getSelection().toString();
setSelectedText(text ? `>${text}\n` : '');
2023-06-24 16:36:59 +02:00
}, [setSelectedText]);
useEffect(() => {
if (isOpen) {
2023-06-24 16:36:59 +02:00
setTimeout(getSelectedText, 0);
} else {
setSelectedText('');
}
2023-06-24 16:36:59 +02:00
}, [isOpen, getSelectedText, setSelectedText]);
2023-04-12 21:00:51 +02:00
const onChallengeVerification = (challengeVerification) => {
if (challengeVerification.challengeSuccess === true) {
2023-07-04 13:50:21 +02:00
return;
} else if (challengeVerification.challengeSuccess === false) {
setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`);
console.log('challenge failed', challengeVerification);
2023-04-12 21:00:51 +02:00
}
};
const onChallenge = async (challenges, comment) => {
setPendingComment(comment);
let challengeAnswers = [];
try {
challengeAnswers = await getChallengeAnswersFromUser(challenges)
}
catch (error) {
2023-07-02 11:45:56 +02:00
setNewErrorMessage(error.message); console.log(error);
2023-04-12 21:00:51 +02:00
}
if (challengeAnswers) {
await comment.publishChallengeAnswers(challengeAnswers)
}
};
2023-04-23 15:14:49 +02:00
useEffect(() => {
setPublishCommentOptions((prevPublishCommentOptions) => ({
...prevPublishCommentOptions,
subplebbitAddress: selectedAddress,
}));
}, [selectedAddress]);
2023-04-12 21:00:51 +02:00
const [publishCommentOptions, setPublishCommentOptions] = useState({
subplebbitAddress: selectedAddress,
2023-04-12 21:00:51 +02:00
onChallenge,
2023-04-22 17:37:53 +02:00
onChallengeVerification,
2023-04-12 21:00:51 +02:00
onError: (error) => {
2023-07-02 11:45:56 +02:00
setNewErrorMessage(error.message); console.log(error);
2023-04-12 21:00:51 +02:00
},
});
2023-04-24 15:48:30 +02:00
const { publishComment, index } = usePublishComment(publishCommentOptions);
useEffect(() => {
if (index !== undefined) {
setPendingCommentIndex(index);
}
2023-05-10 10:00:08 +02:00
}, [index, setPendingCommentIndex]);
2023-04-12 21:00:51 +02:00
2023-04-27 12:36:21 +02:00
const resetFields = useCallback(() => {
2023-04-25 22:07:33 +02:00
if (nameRef.current) {
nameRef.current.value = '';
}
if (commentRef.current) {
commentRef.current.value = '';
}
if (linkRef.current) {
linkRef.current.value = '';
}
2023-04-27 12:36:21 +02:00
}, []);
2023-04-12 21:00:51 +02:00
const handleSubmit = async (event) => {
event.preventDefault();
2023-05-12 21:32:33 +02:00
if (
commentRef.current.value === "" &&
linkRef.current.value === ""
) {
2023-06-14 16:24:25 +02:00
setNewErrorMessage("Please enter a comment or link.");
2023-05-12 21:32:33 +02:00
return;
}
2023-04-12 21:00:51 +02:00
setPublishCommentOptions((prevPublishCommentOptions) => ({
...prevPublishCommentOptions,
2023-04-16 11:21:05 +02:00
author: {
displayName: nameRef.current.value || undefined,
address: account?.author.address,
2023-04-16 11:21:05 +02:00
},
2023-04-12 21:00:51 +02:00
content: commentRef.current.value || undefined,
link: linkRef.current.value || undefined,
2023-04-12 21:56:25 +02:00
parentCid: selectedParentCid,
2023-04-12 21:00:51 +02:00
}));
2023-04-27 12:36:21 +02:00
setTriggerPublishComment(true);
2023-04-12 21:00:51 +02:00
};
2023-06-26 21:55:26 +02:00
2023-06-29 18:10:27 +02:00
const updateSigner = useCallback(async () => {
if (anonymousMode) {
setExecuteAnonMode(true);
2023-06-26 21:55:26 +02:00
2023-06-29 18:10:27 +02:00
let storedSigners = JSON.parse(localStorage.getItem('storedSigners')) || {};
let signer;
2023-06-26 21:55:26 +02:00
2023-06-29 18:10:27 +02:00
if (!storedSigners[selectedParentCid]) {
signer = await account?.plebbit.createSigner();
storedSigners[selectedParentCid] = { privateKey: signer?.privateKey, address: signer?.address };
localStorage.setItem('storedSigners', JSON.stringify(storedSigners));
} else {
const signerPrivateKey = storedSigners[selectedParentCid].privateKey;
2023-06-26 21:55:26 +02:00
2023-06-29 18:10:27 +02:00
try {
signer = await account?.plebbit.createSigner({type: 'ed25519', privateKey: signerPrivateKey});
} catch (error) {
console.log(error);
2023-06-26 21:55:26 +02:00
}
2023-06-29 18:10:27 +02:00
}
2023-06-26 21:55:26 +02:00
2023-06-29 18:10:27 +02:00
setPublishCommentOptions(prevPublishCommentOptions => {
const newPublishCommentOptions = {
2023-06-26 21:55:26 +02:00
...prevPublishCommentOptions,
signer,
author: {
...prevPublishCommentOptions.author,
address: signer?.address,
2023-06-26 21:55:26 +02:00
},
2023-06-29 18:10:27 +02:00
};
2023-06-26 21:55:26 +02:00
2023-06-29 18:10:27 +02:00
if (JSON.stringify(prevPublishCommentOptions) !== JSON.stringify(newPublishCommentOptions)) {
return newPublishCommentOptions;
}
2023-06-26 21:55:26 +02:00
2023-06-29 18:10:27 +02:00
return prevPublishCommentOptions;
});
}
}, [selectedParentCid, anonymousMode, account]);
useEffect(() => {
if (anonymousMode) {
updateSigner();
}
}, [updateSigner, anonymousMode]);
2023-04-12 21:00:51 +02:00
useEffect(() => {
2023-05-12 21:32:33 +02:00
if (publishCommentOptions && triggerPublishComment) {
2023-04-12 21:00:51 +02:00
(async () => {
await publishComment();
resetFields();
2023-04-21 11:59:17 +02:00
closeModal();
2023-04-12 21:00:51 +02:00
})();
2023-04-27 12:36:21 +02:00
setTriggerPublishComment(false);
2023-06-26 21:55:26 +02:00
setExecuteAnonMode(false);
2023-04-12 21:00:51 +02:00
}
2023-04-27 12:36:21 +02:00
}, [publishCommentOptions, triggerPublishComment, publishComment, resetFields, closeModal]);
2023-04-12 21:00:51 +02:00
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);
2023-04-22 17:37:53 +02:00
const handleKeyDown = async (event) => {
2023-04-12 21:00:51 +02:00
if (event.key === 'Enter') {
2023-04-22 17:37:53 +02:00
const currentCaptchaResponse = captchaResponse;
resolve(currentCaptchaResponse);
2023-04-12 21:00:51 +02:00
setIsCaptchaOpen(false);
document.removeEventListener('keydown', handleKeyDown);
event.preventDefault();
}
};
setCaptchaResponse('');
document.addEventListener('keydown', handleKeyDown);
2023-04-22 17:37:53 +02:00
setResolveCaptchaPromise(resolve);
2023-04-12 21:00:51 +02:00
};
challengeImg.onerror = () => {
2023-06-14 16:24:25 +02:00
reject(setNewErrorMessage('Could not load challenges'));
2023-04-12 21:00:51 +02:00
};
});
2023-03-17 14:35:03 +01:00
};
2023-03-22 14:30:48 +01:00
2023-03-17 14:35:03 +01:00
return (
<StyledModal
isOpen={isOpen}
onAfterOpen={onModalOpen}
2023-03-17 14:35:03 +01:00
onRequestClose={closeModal}
contentLabel="Reply Modal"
shouldCloseOnEsc={true}
2023-05-11 15:24:46 +02:00
shouldCloseOnOverlayClick={isMobile}
2023-03-17 14:35:03 +01:00
selectedStyle={selectedStyle}
overlayClassName="overlay"
style={isMobile ? ({ overlay: { backgroundColor: "rgba(0,0,0,.25)" }}) : ({ overlay: { backgroundColor: "rgba(0,0,0,0)" }})
}
>
2023-05-11 13:09:20 +02:00
<Draggable handle=".modal-header" nodeRef={nodeRef} disabled={isMobile}>
2023-03-22 14:30:48 +01:00
<div className="modal-content" ref={nodeRef}>
2023-03-17 14:35:03 +01:00
<div className="modal-header">
2023-04-12 21:56:25 +02:00
Reply to c/{selectedShortCid}
2023-04-12 21:00:51 +02:00
<button className="icon" onClick={() => closeModal()} title="close" />
2023-03-17 14:35:03 +01:00
</div>
<div id="form">
<div>
2023-06-27 10:35:21 +02:00
{account && account?.author && account?.author.displayName ? (
<input id="name" type="text" value={account?.author?.displayName} ref={nameRef} disabled />
) : (
<input id="name" type="text" placeholder="Anonymous" ref={nameRef} />
)}
2023-03-17 14:35:03 +01:00
</div>
<div>
2023-04-12 21:00:51 +02:00
<input id="name" type="text" placeholder="Embed link" ref={linkRef} />
2023-03-17 14:35:03 +01:00
</div>
2023-04-18 12:44:55 +02:00
<div className="textarea-wrapper">
<span className="fixed-text">{`c/${selectedShortCid}`}</span>
<textarea className="textarea"
rows="4"
placeholder="Comment"
defaultValue={selectedText}
wrap="soft"
ref={commentRef}
/>
2023-03-17 17:49:30 +01:00
</div>
<div>
2023-04-12 21:00:51 +02:00
<button id="next" onClick={handleSubmit}>Post</button>
2023-03-17 14:35:03 +01:00
</div>
</div>
</div>
</Draggable>
</StyledModal>
);
};
Modal.setAppElement('#root');
2023-03-21 11:17:54 +01:00
export default ReplyModal;