feat(use-reply): implement replying to a post

This commit is contained in:
plebeius.eth
2023-11-11 11:30:51 +01:00
parent 050dfcce75
commit 34d380ecc6
3 changed files with 137 additions and 14 deletions

85
src/hooks/use-reply.ts Normal file
View File

@@ -0,0 +1,85 @@
import { useMemo } from 'react';
import { ChallengeVerification, Comment, PublishCommentOptions, usePublishComment } from '@plebbit/plebbit-react-hooks';
import { create } from 'zustand';
import useChallengesStore from './use-challenges';
import { alertChallengeVerificationFailed } from '../lib/utils/challenge-utils';
type SetReplyStoreData = {
subplebbitAddress: string;
parentCid: string;
content: string | undefined;
link: string | undefined;
spoiler: boolean;
};
type ReplyState = {
content: { [parentCid: string]: string | undefined };
link: { [parentCid: string]: string | undefined };
spoiler: { [parentCid: string]: boolean | undefined };
publishCommentOptions: PublishCommentOptions;
setReplyStore: (data: SetReplyStoreData) => void;
resetReplyStore: (parentCid: string) => void;
};
const { addChallenge } = useChallengesStore.getState();
const useReplyStore = create<ReplyState>((set) => ({
content: {},
link: {},
spoiler: {},
publishCommentOptions: {},
setReplyStore: (data: SetReplyStoreData) =>
set((state) => {
const { subplebbitAddress, parentCid, content, link, spoiler } = data;
const publishCommentOptions = {
subplebbitAddress,
parentCid,
content,
link,
spoiler,
onChallenge: (...args: any) => addChallenge(args),
onChallengeVerification: (challengeVerification: ChallengeVerification, comment: Comment) => {
alertChallengeVerificationFailed(challengeVerification, comment);
},
onError: (error: Error) => {
console.error(error);
alert(error.message);
},
};
return {
content: { ...state.content, [parentCid]: content },
link: { ...state.link, [parentCid]: link },
spoiler: { ...state.spoiler, [parentCid]: spoiler },
publishCommentOptions: { ...state.publishCommentOptions, [parentCid]: publishCommentOptions },
};
}),
resetReplyStore: (parentCid) =>
set((state) => ({
content: { ...state.content, [parentCid]: undefined },
link: { ...state.link, [parentCid]: undefined },
spoiler: { ...state.spoiler, [parentCid]: undefined },
publishCommentOptions: { ...state.publishCommentOptions, [parentCid]: undefined },
})),
}));
const useReply = (comment: Comment) => {
const subplebbitAddress = comment?.subplebbitAddress;
const parentCid = comment?.cid;
const publishCommentOptions = useReplyStore((state) => state.publishCommentOptions[parentCid]);
const setReplyStore = useReplyStore((state) => state.setReplyStore);
const resetReplyStore = useReplyStore((state) => state.resetReplyStore);
const setContent = useMemo(
() => (content: string | undefined, link: string | undefined, spoiler: boolean) => setReplyStore({ subplebbitAddress, parentCid, content, link, spoiler }),
[subplebbitAddress, parentCid, setReplyStore, comment],
);
const resetContent = useMemo(() => () => resetReplyStore(parentCid), [parentCid, resetReplyStore]);
const { index, publishComment } = usePublishComment(publishCommentOptions);
return { setContent, resetContent, replyIndex: index, publishReply: publishComment };
};
export default useReply;

View File

@@ -1,4 +1,4 @@
import { useEffect } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
import { useComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import { useTranslation } from 'react-i18next';
@@ -7,19 +7,55 @@ import PostComponent from '../../components/post';
import useReplies from '../../hooks/use-replies';
import Reply from '../../components/reply';
import useStateString from '../../hooks/use-state-string';
import useReply from '../../hooks/use-reply';
import { usePendingReplyCount } from '../../hooks/use-pending-replycount';
const Post = () => {
const { t } = useTranslation();
const { commentCid } = useParams();
const comment = useComment({ commentCid });
const { content, replyCount, subplebbitAddress, title } = comment || {};
const { replyCount, subplebbitAddress, title } = comment || {};
const subplebbit = useSubplebbit({ subplebbitAddress });
const replies = useReplies(comment).map((reply, index) => <Reply key={`${index}${reply.cid}`} reply={reply} />) || '';
const postTitle = title?.slice(0, 40) || content?.slice(0, 40);
const subplebbitTitle = subplebbit?.title || subplebbit?.shortAddress;
const { t } = useTranslation();
const stateString = useStateString(comment);
const commentCount = replyCount === 0 ? t('no_comments') : replyCount === 1 ? t('one_comment') : t('all_comments', { count: replyCount });
const replies = useReplies(comment).map((reply, index) => <Reply key={`${index}${reply.cid}`} reply={reply} />) || '';
const { setContent, resetContent, replyIndex, publishReply } = useReply(comment);
const postTitle = title?.slice(0, 40) || comment?.content?.slice(0, 40);
const subplebbitTitle = subplebbit?.title || subplebbit?.shortAddress;
const textRef = useRef<HTMLTextAreaElement>(null);
const urlRef = useRef<HTMLInputElement>(null);
const spoilerRef = useRef<HTMLInputElement>(null);
const pendingReplyCount = usePendingReplyCount({ parentCommentCid: commentCid });
const totalReplyCount = replyCount + pendingReplyCount;
const commentCount = totalReplyCount === 0 ? t('no_comments') : totalReplyCount === 1 ? t('one_comment') : t('all_comments', { count: totalReplyCount });
const [readyToPublish, setReadyToPublish] = useState(false);
const onPublish = () => {
const currentContent = textRef.current?.value || '';
if (!currentContent.trim()) {
alert(`missing content`);
return;
}
setContent(textRef.current?.value || undefined, urlRef.current?.value || undefined, spoilerRef.current?.checked || false);
setReadyToPublish(true);
};
useEffect(() => {
if (readyToPublish) {
publishReply();
setReadyToPublish(false);
}
}, [readyToPublish, publishReply]);
useEffect(() => {
if (typeof replyIndex === 'number') {
resetContent();
}
}, [replyIndex, resetContent]);
useEffect(() => {
document.title = `${postTitle || ''}${postTitle && subplebbitTitle ? ' - ' : ''}${subplebbitTitle || ''}${postTitle || subplebbitTitle ? ' - seedit' : 'seedit'}`;
@@ -45,14 +81,16 @@ const Post = () => {
</div>
<div className={styles.mdContainer}>
<div className={styles.md}>
<input className={styles.url} placeholder={`url (${t('optional')})`} />
<input className={styles.url} ref={urlRef} placeholder={`url (${t('optional')})`} />
<span className={styles.spoiler}>
{t('spoiler')}: <input type='checkbox' className={styles.checkbox} />
{t('spoiler')}: <input type='checkbox' className={styles.checkbox} ref={spoilerRef} />
</span>
<textarea className={styles.textarea} placeholder={t('text')} />
<textarea className={styles.textarea} ref={textRef} placeholder={t('text')} />
</div>
<div className={styles.bottomArea}>
<button className={styles.save}>{t('post_save')}</button>
<button className={styles.save} onClick={onPublish}>
{t('post_save')}
</button>
</div>
</div>
</div>

View File

@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
import { usePublishComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import { PublishCommentOptions, usePublishComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import { useTranslation } from 'react-i18next';
import { create } from 'zustand';
import { isSubplebbitSubmitView } from '../../lib/utils/view-utils';
@@ -14,7 +14,7 @@ type SubmitState = {
title: string | undefined;
content: string | undefined;
link: string | undefined;
publishCommentOptions: any;
publishCommentOptions: PublishCommentOptions;
setSubmitStore: (data: Partial<SubmitState>) => void;
resetSubmitStore: () => void;
};
@@ -26,7 +26,7 @@ const useSubmitStore = create<SubmitState>((set) => ({
title: undefined,
content: undefined,
link: undefined,
publishCommentOptions: undefined,
publishCommentOptions: {},
setSubmitStore: ({ subplebbitAddress, title, content, link }) =>
set((state) => {
const nextState = { ...state };