());
// fetch posts
useEffect(() => {
const loadPosts = async () => {
if (!nextPostId || totalPostsInThread === 0 || thread.isEmpty) return;
await refreshPostsInThreadCache(nextPostId, api);
const mapPostToThread = getPostsIdsInThreadCache();
const postIdsInThread = mapPostToThread.get(thread.id.toNumber()) as number[];
const postsInThisThread = await Promise.all(postIdsInThread
? postIdsInThread.map((postId: number) => api.query.forum.postById(postId)) : []) as Post[];
const sortedPosts = orderBy(
postsInThisThread,
[(x) => x.nr_in_thread.toNumber()],
['asc']
);
// initialize refs for posts
postsRefs.current = sortedPosts.reduce((acc, reply) => {
const refKey = reply.nr_in_thread.toNumber();
acc[refKey] = React.createRef();
return acc;
}, postsRefs.current);
setPosts(sortedPosts);
setLoaded(true);
};
void loadPosts();
}, [bnToStr(thread.id), bnToStr(nextPostId)]);
// handle selected post
useEffect(() => {
if (!selectedPostIdx) return;
const selectedPostPage = Math.ceil(selectedPostIdx / RepliesPerPage);
if (currentPage !== selectedPostPage) {
setCurrentPage(selectedPostPage);
}
if (!loaded) return;
if (selectedPostIdx > posts.length) {
// eslint-disable-next-line no-console
console.warn(`Tried to open nonexistent reply with idx: ${selectedPostIdx}`);
return;
}
const postRef = postsRefs.current[selectedPostIdx];
// postpone scrolling for one render to make sure the ref is set
setTimeout(() => {
if (postRef.current) {
postRef.current.scrollIntoView();
} else {
// eslint-disable-next-line no-console
console.warn('Ref for selected post empty');
}
});
}, [loaded, selectedPostIdx, currentPage]);
// handle displayed posts based on pagination
useEffect(() => {
if (!loaded) return;
const minIdx = (currentPage - 1) * RepliesPerPage;
const maxIdx = minIdx + RepliesPerPage - 1;
const postsToDisplay = posts.filter((_id, i) => i >= minIdx && i <= maxIdx);
setDisplayedPosts(postsToDisplay);
}, [loaded, posts, currentPage]);
const renderThreadNotFound = () => (
preview ? null : Thread not found
);
if (thread.isEmpty) {
return renderThreadNotFound();
}
if (!category) {
return {'Thread\'s category was not found.'};
} else if (category.deleted) {
return renderThreadNotFound();
}
if (preview) {
return ;
}
const changePageAndClearSelectedPost = (page?: number | string) => {
setSelectedPostIdx(null);
setCurrentPage(page, [ReplyIdxQueryParam]);
};
const scrollToReplyForm = () => {
if (!replyFormRef.current) return;
replyFormRef.current.scrollIntoView();
};
const clearEditedPost = () => {
setEditedPostId(null);
};
const onThreadReplyClick = () => {
clearEditedPost();
setQuotedPost(null);
scrollToReplyForm();
};
const onPostEditSuccess = async () => {
if (!editedPostId) {
// eslint-disable-next-line no-console
console.error('editedPostId not set!');
return;
}
const updatedPost = await api.query.forum.postById(editedPostId) as Post;
const updatedPosts = posts.map((post) => post.id.eq(editedPostId) ? updatedPost : post);
setPosts(updatedPosts);
clearEditedPost();
};
// console.log({ nextPostId: bnToStr(nextPostId), loaded, posts });
const renderPageOfPosts = () => {
if (!loaded) {
return Loading posts...;
}
const pagination =
;
const renderedReplies = displayedPosts.map((reply) => {
const replyIdx = reply.nr_in_thread.toNumber();
const onReplyEditClick = () => {
setEditedPostId(reply.id.toString());
scrollToReplyForm();
};
const onReplyQuoteClick = () => {
setQuotedPost(reply);
scrollToReplyForm();
};
return (
);
});
return <>
{pagination}
{renderedReplies}
{pagination}
>;
};
const renderActions = () => {
if (thread.moderated || category.archived || category.deleted) {
return null;
}
return
{/* TODO show 'Edit' button only if I am owner */}
{/*
Edit
*/}
;
};
const renderModerationRationale = () => {
if (!thread.moderation) return null;
return <>
>;
};
return
Created by
{renderActions()}
{category.archived &&
No new replies can be posted.
}
{showModerateForm &&
setShowModerateForm(false)} />
}
{thread.moderated
? renderModerationRationale()
: renderPageOfPosts()
}
{
editedPostId ? (
) : (
)
}
;
}
export const ViewThread = withMulti(
InnerViewThread,
withApi,
withForumCalls(
['nextPostId', { propName: 'nextPostId' }]
)
);
type ViewThreadByIdProps = RouteComponentProps<{ id: string }>;
export function ViewThreadById (props: ViewThreadByIdProps) {
const { api } = useApi();
const { match: { params: { id } } } = props;
const [loaded, setLoaded] = useState(false);
const [thread, setThread] = useState(api.createType('Thread', {}));
const [category, setCategory] = useState(api.createType('Category', {}));
let threadId: ThreadId | undefined;
try {
threadId = api.createType('ThreadId', id);
} catch (err) {
console.log('Failed to parse thread id form URL');
}
useEffect(() => {
const loadThreadAndCategory = async () => {
if (!threadId) return;
const thread = await api.query.forum.threadById(threadId) as Thread;
const category = await api.query.forum.categoryById(thread.category_id) as Category;
setThread(thread);
setCategory(category);
setLoaded(true);
};
void loadThreadAndCategory();
}, [id]);
if (threadId === undefined) {
return Invalid thread ID: {id};
}
// console.log({ threadId: id, page });
if (!loaded) {
return Loading thread details...;
}
if (thread.isEmpty) {
return Thread was not found by id;
}
if (category.isEmpty) {
return { 'Thread\'s category was not found' };
}
return ;
}