Nested comments

This commit is contained in:
ilya-bkv 2022-12-16 10:17:50 +03:00
parent 2f252da9db
commit 1ddfc8e9a5
3 changed files with 122 additions and 187 deletions

View File

@ -1,4 +1,5 @@
import styles from './Comment.module.scss' import styles from './Comment.module.scss'
import type { JSX } from 'solid-js/jsx-runtime'
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
import { AuthorCard } from '../Author/Card' import { AuthorCard } from '../Author/Card'
import { Show, createMemo, createSignal } from 'solid-js' import { Show, createMemo, createSignal } from 'solid-js'
@ -12,13 +13,13 @@ import { formatDate } from '../../utils'
import { SharePopup } from './SharePopup' import { SharePopup } from './SharePopup'
import stylesHeader from '../Nav/Header.module.scss' import stylesHeader from '../Nav/Header.module.scss'
import Userpic from '../Author/Userpic' import Userpic from '../Author/Userpic'
import CommentWrapper from './CommentWrapper'
export default (props: { export default (props: {
level?: number level?: number
comment: Partial<Point> comment: Partial<Point>
canEdit?: boolean canEdit?: boolean
compact?: boolean compact?: boolean
children?: JSX.Element[]
}) => { }) => {
const [isReplyVisible, setIsReplyVisible] = createSignal(false) const [isReplyVisible, setIsReplyVisible] = createSignal(false)
@ -35,116 +36,117 @@ export default (props: {
) )
return ( return (
<CommentWrapper level={props.level}> <li class={clsx(styles.comment, { [styles[`commentLevel${props.level}`]]: Boolean(props.level) })}>
<li class={clsx(styles.comment, { [styles[`commentLevel${props.level}`]]: Boolean(props.level) })}> <Show when={!!body()}>
<Show when={!!body()}> <div class={styles.commentContent}>
<div class={styles.commentContent}> <Show
<Show when={!props.compact}
when={!props.compact} fallback={
fallback={ <div>
<div> <Userpic user={comment().createdBy as Author} isBig={false} isAuthorsList={false} />
<Userpic user={comment().createdBy as Author} isBig={false} isAuthorsList={false} /> <small class={styles.commentArticle}>
<small class={styles.commentArticle}> <a href={`#comment-${comment()?.id}`}>{comment()?.shout.title || ''}</a>
<a href={`#comment-${comment()?.id}`}>{comment()?.shout.title || ''}</a> </small>
</small> </div>
</div> }
} >
> <div class={styles.commentDetails}>
<div class={styles.commentDetails}> <div class={styles.commentAuthor}>
<div class={styles.commentAuthor}> <AuthorCard
<AuthorCard author={comment()?.createdBy as Author}
author={comment()?.createdBy as Author} hideDescription={true}
hideDescription={true} hideFollow={true}
hideFollow={true} isComments={true}
isComments={true} hasLink={true}
hasLink={true} />
/>
</div>
<div class={styles.commentDate}>{formattedDate()}</div>
<div
class={styles.commentRating}
classList={{
[styles.commentRatingPositive]: comment().stat?.rating > 0,
[styles.commentRatingNegative]: comment().stat?.rating < 0
}}
>
<button class={clsx(styles.commentRatingControl, styles.commentRatingControlUp)} />
<div class={styles.commentRatingValue}>{comment().stat?.rating || 0}</div>
<button class={clsx(styles.commentRatingControl, styles.commentRatingControlDown)} />
</div>
</div> </div>
</Show>
<div <div class={styles.commentDate}>{formattedDate()}</div>
class={styles.commentBody} <div
contenteditable={props.canEdit} class={styles.commentRating}
id={'comment-' + (comment().id || '')} classList={{
> [styles.commentRatingPositive]: comment().stat?.rating > 0,
<MD body={body()} /> [styles.commentRatingNegative]: comment().stat?.rating < 0
}}
>
<button class={clsx(styles.commentRatingControl, styles.commentRatingControlUp)} />
<div class={styles.commentRatingValue}>{comment().stat?.rating || 0}</div>
<button class={clsx(styles.commentRatingControl, styles.commentRatingControlDown)} />
</div>
</div>
</Show>
<div
class={styles.commentBody}
contenteditable={props.canEdit}
id={'comment-' + (comment().id || '')}
>
<MD body={body()} />
</div>
<Show when={!props.compact}>
<div class={styles.commentControls}>
<button
class={clsx(styles.commentControl, styles.commentControlReply)}
onClick={() => setIsReplyVisible(!isReplyVisible())}
>
<Icon name="reply" class={styles.icon} />
{t('Reply')}
</button>
<Show when={props.canEdit}>
{/*FIXME implement edit comment modal*/}
{/*<button*/}
{/* class={clsx(styles.commentControl, styles.commentControlEdit)}*/}
{/* onClick={() => showModal('editComment')}*/}
{/*>*/}
{/* <Icon name="edit" class={styles.icon} />*/}
{/* {t('Edit')}*/}
{/*</button>*/}
<button
class={clsx(styles.commentControl, styles.commentControlDelete)}
onClick={() => remove()}
>
<Icon name="delete" class={styles.icon} />
{t('Delete')}
</button>
</Show>
<SharePopup
containerCssClass={stylesHeader.control}
trigger={
<button class={clsx(styles.commentControl, styles.commentControlShare)}>
<Icon name="share" class={styles.icon} />
{t('Share')}
</button>
}
/>
{/*<button*/}
{/* class={clsx(styles.commentControl, styles.commentControlComplain)}*/}
{/* onClick={() => showModal('reportComment')}*/}
{/*>*/}
{/* {t('Report')}*/}
{/*</button>*/}
</div> </div>
<Show when={!props.compact}> <Show when={isReplyVisible()}>
<div class={styles.commentControls}> <form class={styles.replyForm}>
<button <textarea name="reply" id="reply" rows="5" />
class={clsx(styles.commentControl, styles.commentControlReply)} <div class={styles.replyFormControls}>
onClick={() => setIsReplyVisible(!isReplyVisible())} <button class="button button--light" onClick={() => setIsReplyVisible(false)}>
> {t('Cancel')}
<Icon name="reply" class={styles.icon} />
{t('Reply')}
</button>
<Show when={props.canEdit}>
{/*FIXME implement edit comment modal*/}
{/*<button*/}
{/* class={clsx(styles.commentControl, styles.commentControlEdit)}*/}
{/* onClick={() => showModal('editComment')}*/}
{/*>*/}
{/* <Icon name="edit" class={styles.icon} />*/}
{/* {t('Edit')}*/}
{/*</button>*/}
<button
class={clsx(styles.commentControl, styles.commentControlDelete)}
onClick={() => remove()}
>
<Icon name="delete" class={styles.icon} />
{t('Delete')}
</button> </button>
</Show> <button class="button">{t('Send')}</button>
</div>
<SharePopup </form>
containerCssClass={stylesHeader.control}
trigger={
<button class={clsx(styles.commentControl, styles.commentControlShare)}>
<Icon name="share" class={styles.icon} />
{t('Share')}
</button>
}
/>
{/*<button*/}
{/* class={clsx(styles.commentControl, styles.commentControlComplain)}*/}
{/* onClick={() => showModal('reportComment')}*/}
{/*>*/}
{/* {t('Report')}*/}
{/*</button>*/}
</div>
<Show when={isReplyVisible()}>
<form class={styles.replyForm}>
<textarea name="reply" id="reply" rows="5" />
<div class={styles.replyFormControls}>
<button class="button button--light" onClick={() => setIsReplyVisible(false)}>
{t('Cancel')}
</button>
<button class="button">{t('Send')}</button>
</div>
</form>
</Show>
</Show> </Show>
</div> </Show>
</Show> </div>
</li> </Show>
</CommentWrapper> <Show when={props.children}>
<ul>{props.children}</ul>
</Show>
</li>
) )
} }

View File

@ -1,71 +0,0 @@
import type { JSX } from 'solid-js/jsx-runtime'
import { Switch, Match } from 'solid-js'
type Props = {
level?: number
children: JSX.Element
}
const CommentWrapper = (props: Props) => {
return (
<Switch fallback={props.children}>
<Match when={props.level === 1}>
<ul>{props.children}</ul>
</Match>
<Match when={props.level === 2}>
<ul>
<li>
<ul>{props.children}</ul>
</li>
</ul>
</Match>
<Match when={props.level === 3}>
<ul>
<li>
<ul>
<li>
<ul>{props.children}</ul>
</li>
</ul>
</li>
</ul>
</Match>
<Match when={props.level === 4}>
<ul>
<li>
<ul>
<li>
<ul>
<li>
<ul>{props.children}</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</Match>
<Match when={props.level === 5}>
<ul>
<li>
<ul>
<li>
<ul>
<li>
<ul>
<li>
<ul>{props.children}</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</Match>
</Switch>
)
}
export default CommentWrapper

View File

@ -10,6 +10,10 @@ import { clsx } from 'clsx'
import { byCreated, byStat } from '../../utils/sortby' import { byCreated, byStat } from '../../utils/sortby'
import { Loading } from '../Loading' import { Loading } from '../Loading'
type NestedReaction = {
children: Reaction[]
} & Reaction
const ARTICLE_COMMENTS_PAGE_SIZE = 50 const ARTICLE_COMMENTS_PAGE_SIZE = 50
const MAX_COMMENT_LEVEL = 6 const MAX_COMMENT_LEVEL = 6
@ -103,16 +107,16 @@ export const CommentsTree = (props: { shoutSlug: string }) => {
</div> </div>
<ul> <ul>
<For each={reactions().reverse()}> <For each={nestComments(reactions().reverse())}>
{(reaction: Reaction) => ( {(reaction: NestedReaction) => (
<> <Comment
{JSON.stringify(getCommentLevel(reaction))} comment={reaction}
<Comment level={getCommentLevel(reaction)}
comment={reaction} canEdit={reaction?.createdBy?.slug === session()?.user?.slug}
level={getCommentLevel(reaction)} children={(reaction.children || []).map((r) => {
canEdit={reaction.createdBy?.slug === session()?.user?.slug} return <Comment comment={r} />
/> })}
</> />
)} )}
</For> </For>
</ul> </ul>