feat: loading state to comments, delay success until comment is fetched, enable collapsing replies, fix comments section cards borders

This commit is contained in:
MTG2000
2022-07-26 17:27:16 +03:00
parent 9492746441
commit a2d84c8f54
7 changed files with 93 additions and 35 deletions

View File

@@ -14,7 +14,7 @@ import {
PlaceholderExtension,
} from 'remirror/extensions';
import { EditorComponent, Remirror, useRemirror } from '@remirror/react';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Avatar from 'src/features/Profiles/Components/Avatar/Avatar';
import Toolbar from './Toolbar';
import Button from 'src/Components/Button/Button';
@@ -26,7 +26,7 @@ interface Props {
placeholder?: string;
avatar: string;
autoFocus?: boolean
onSubmit?: (comment: string) => void;
onSubmit?: (comment: string) => Promise<boolean>;
}
@@ -41,7 +41,7 @@ export default function AddComment({ initialContent, placeholder, avatar, autoFo
});
return extension;
}, []);
const [isLoading, setIsLoading] = useState(false)
const valueRef = useRef<string>("");
@@ -85,14 +85,18 @@ export default function AddComment({ initialContent, placeholder, avatar, autoFo
}, [autoFocus])
const submitComment = () => {
onSubmit?.(valueRef.current);
manager.view.updateState(manager.createState({ content: manager.createEmptyDoc() }))
const submitComment = async () => {
setIsLoading(true);
const isSuccess = await onSubmit?.(valueRef.current);
if (isSuccess)
manager.view.updateState(manager.createState({ content: manager.createEmptyDoc() }))
setIsLoading(false);
}
return (
<div className={`remirror-theme ${styles.wrapper} p-24 border rounded-12`} ref={containerRef}>
<div className={`remirror-theme ${styles.wrapper} p-24 border-2 border-gray-200 rounded-12 md:rounded-16`} ref={containerRef}>
<Remirror
manager={manager}
state={state}
@@ -112,7 +116,14 @@ export default function AddComment({ initialContent, placeholder, avatar, autoFo
</div>
<div className="flex flex-wrap gap-16 mt-16">
<Toolbar />
<Button onClick={submitComment} color='primary' className='ml-auto'>Submit</Button>
<Button
onClick={submitComment}
color='primary'
className='ml-auto'
isLoading={isLoading}
>
Submit
</Button>
</div>
</Remirror>
</div>

View File

@@ -1,5 +1,8 @@
import { useToggle } from "@react-hookz/web";
import { useState } from "react";
import { FaChevronDown, FaChevronUp } from "react-icons/fa";
import Button from "src/Components/Button/Button";
import { useAppSelector } from "src/utils/hooks";
import AddComment from "../AddComment/AddComment";
import CommentCard from "../CommentCard/CommentCard";
@@ -16,7 +19,8 @@ interface Props {
export default function Comment({ comment, canReply, isRoot, onClickedReply, onReply }: Props) {
const [replyOpen, setReplyOpen] = useState(false)
const [replyOpen, setReplyOpen] = useState(false);
const [repliesCollapsed, toggleRepliesCollapsed] = useToggle(true)
const user = useAppSelector(s => s.user.me)
const clickReply = () => {
@@ -26,13 +30,31 @@ export default function Comment({ comment, canReply, isRoot, onClickedReply, onR
onClickedReply?.()
}
const handleReply = async (text: string) => {
try {
await onReply?.(text);
setReplyOpen(false);
return true;
} catch (error) {
return false;
}
}
return (
<div >
<CommentCard canReply={canReply} comment={comment} onReply={clickReply} />
{(comment.replies.length > 0 || replyOpen) && <div className="flex mt-16 gap-8 md:gap-20 pl-8">
<div className="border-l border-b border-gray-200 w-16 md:w-24 h-40 rounded-bl-8 flex-shrink-0"></div>
<div className="border-l-2 border-b-2 border-gray-200 w-16 md:w-24 h-40 rounded-bl-8 flex-shrink-0"></div>
<div className="flex flex-col w-full gap-16">
{comment.replies.map(reply => <Comment
{comment.replies.length > 0 &&
<Button color="none" className="self-start mt-12 !px-0" onClick={() => toggleRepliesCollapsed()}>
{repliesCollapsed ?
<span className="text-gray-600"><span className="align-middle">Show {comment.replies.length} replies</span> <FaChevronDown className="ml-12" /></span>
:
<span className="text-gray-600"><span className="align-middle">Hide replies</span> <FaChevronUp className="ml-12" /> </span>
}
</Button>}
{!repliesCollapsed && comment.replies.map(reply => <Comment
key={reply.id}
comment={reply}
onClickedReply={clickReply}
@@ -42,7 +64,7 @@ export default function Comment({ comment, canReply, isRoot, onClickedReply, onR
avatar={user?.avatar!}
autoFocus
placeholder="Leave a reply..."
onSubmit={(text) => onReply?.(text)}
onSubmit={handleReply}
/>}
</div>
</div>}

View File

@@ -32,7 +32,7 @@ export default function CommentCard({ comment, canReply, onReply }: Props) {
}
return (
<div className="border rounded-12 p-24">
<div className="border-2 border-gray-200 rounded-12 md:rounded-16 p-24">
<Header author={comment.author} date={new Date(comment.created_at).toISOString()} />
<div
className="text-body4 mt-16 whitespace-pre-line"

View File

@@ -36,13 +36,18 @@ export default function CommentsSection({ type, id }: Props) {
}
}, [filter]);
const handleNewComment = (content: string, parentId?: string) => {
CommentsWorker.post({ content, filter, parentId });
const handleNewComment = async (content: string, parentId?: string) => {
try {
await CommentsWorker.post({ content, filter, parentId });
return true;
} catch (error) {
return false
}
}
return (
<div className="border border-gray-200 rounded-10 p-32 bg-white">
<div className="border-2 border-gray-200 rounded-12 md:rounded-16 p-32 bg-white">
<h6 className="text-body2 font-bolder">Discussion</h6>
{!!user && <div className="mt-24">
<AddComment

View File

@@ -35,6 +35,7 @@ export function sub(filter: string, cb: (data: Comment[]) => void) {
cb(newComments)
}, 1000)
let sub = pool.sub({
filter: {
"#r": [filter]
@@ -43,10 +44,16 @@ export function sub(filter: string, cb: (data: Comment[]) => void) {
//Got a new event
if (!event.id) return;
if (event.id in events) return
events[event.id] = event
if (event.id in events) return;
events[event.id] = event
reconstructTree()
document.dispatchEvent(
new CustomEvent('nostr-event', {
detail: event
})
)
}
});
@@ -110,25 +117,35 @@ export async function post({ content, filter, parentId }: {
const publishTimeout = setTimeout(() => {
alert(
`failed to publish comment to any relay.`
);
}, 5000)
pool.publish(event, (status: number, relay: string) => {
switch (status) {
case -1:
console.log(`failed to send ${JSON.stringify(event)} to ${relay}`)
// enable()
// onError()
break
case 1:
clearTimeout(publishTimeout)
console.log(`event ${event.id?.slice(0, 5)}… published to ${relay}.`)
// onSuccess()
break
return new Promise<void>((resolve, reject) => {
pool.publish(event, (status: number, relay: string) => {
switch (status) {
case -1:
console.log(`failed to send ${JSON.stringify(event)} to ${relay}`)
break
case 1:
clearTimeout(publishTimeout)
console.log(`event ${event.id?.slice(0, 5)}… published to ${relay}.`)
break
}
});
const onEventFetched = (e: CustomEvent<NostrEvent>) => {
if (e.detail.id === event.id) {
document.removeEventListener<any>('nostr-event', onEventFetched);
resolve();
}
}
document.addEventListener<any>('nostr-event', onEventFetched);
const publishTimeout = setTimeout(() => {
document.removeEventListener<any>('nostr-event', onEventFetched);
reject("Failed to publish to any relay...");
}, 5000)
})
}