refactor replies and full items
This commit is contained in:
parent
e7787e3e67
commit
02c44dca63
|
@ -3,7 +3,6 @@ import * as Yup from 'yup'
|
||||||
import { gql, useMutation } from '@apollo/client'
|
import { gql, useMutation } from '@apollo/client'
|
||||||
import styles from './reply.module.css'
|
import styles from './reply.module.css'
|
||||||
import TextareaAutosize from 'react-textarea-autosize'
|
import TextareaAutosize from 'react-textarea-autosize'
|
||||||
import Countdown from '../components/countdown'
|
|
||||||
|
|
||||||
export const CommentSchema = Yup.object({
|
export const CommentSchema = Yup.object({
|
||||||
text: Yup.string().required('required').trim()
|
text: Yup.string().required('required').trim()
|
||||||
|
@ -53,19 +52,8 @@ export default function CommentEdit ({ comment, editThreshold, onSuccess, onCanc
|
||||||
minRows={4}
|
minRows={4}
|
||||||
autoFocus
|
autoFocus
|
||||||
required
|
required
|
||||||
groupClassName='mb-0'
|
|
||||||
hint={<Countdown date={editThreshold} />}
|
|
||||||
/>
|
/>
|
||||||
<div className='d-flex align-items-center justify-content-between'>
|
<SubmitButton variant='secondary' className='mt-1'>save</SubmitButton>
|
||||||
<SubmitButton variant='secondary' className='mt-1'>save</SubmitButton>
|
|
||||||
<div
|
|
||||||
className='font-weight-bold text-muted mr-3'
|
|
||||||
style={{ fontSize: '80%', cursor: 'pointer' }}
|
|
||||||
onClick={onCancel}
|
|
||||||
>
|
|
||||||
cancel
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -41,9 +41,8 @@ function Parent ({ item, rootText }) {
|
||||||
|
|
||||||
export default function Comment ({
|
export default function Comment ({
|
||||||
item, children, replyOpen, includeParent,
|
item, children, replyOpen, includeParent,
|
||||||
rootText, noComments, noReply
|
rootText, noComments
|
||||||
}) {
|
}) {
|
||||||
const [reply, setReply] = useState(replyOpen)
|
|
||||||
const [edit, setEdit] = useState()
|
const [edit, setEdit] = useState()
|
||||||
const [collapse, setCollapse] = useState(false)
|
const [collapse, setCollapse] = useState(false)
|
||||||
const ref = useRef(null)
|
const ref = useRef(null)
|
||||||
|
@ -92,28 +91,36 @@ export default function Comment ({
|
||||||
<span> </span>
|
<span> </span>
|
||||||
<span>{timeSince(new Date(item.createdAt))}</span>
|
<span>{timeSince(new Date(item.createdAt))}</span>
|
||||||
{includeParent && <Parent item={item} rootText={rootText} />}
|
{includeParent && <Parent item={item} rootText={rootText} />}
|
||||||
|
{canEdit &&
|
||||||
|
<>
|
||||||
|
<span> \ </span>
|
||||||
|
<div
|
||||||
|
className={styles.edit}
|
||||||
|
onClick={() => setEdit(!edit)}
|
||||||
|
>
|
||||||
|
{edit ? 'cancel' : 'edit'}
|
||||||
|
<Countdown
|
||||||
|
date={editThreshold}
|
||||||
|
onComplete={() => {
|
||||||
|
setCanEdit(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
</div>
|
</div>
|
||||||
{!includeParent && (collapse
|
{!includeParent && (collapse
|
||||||
? <Eye className={styles.collapser} height={10} width={10} onClick={() => setCollapse(false)} />
|
? <Eye className={styles.collapser} height={10} width={10} onClick={() => setCollapse(false)} />
|
||||||
: <EyeClose className={styles.collapser} height={10} width={10} onClick={() => setCollapse(true)} />)}
|
: <EyeClose className={styles.collapser} height={10} width={10} onClick={() => setCollapse(true)} />)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{edit
|
{edit
|
||||||
? (
|
? (
|
||||||
<div className={styles.replyWrapper}>
|
<CommentEdit
|
||||||
<CommentEdit
|
comment={item}
|
||||||
comment={item}
|
onSuccess={() => {
|
||||||
onSuccess={() => {
|
setEdit(!edit)
|
||||||
setEdit(!edit)
|
setCanEdit(mine && (Date.now() < editThreshold))
|
||||||
setCanEdit(mine && (Date.now() < editThreshold))
|
}}
|
||||||
}}
|
/>
|
||||||
onCancel={() => {
|
|
||||||
setEdit(!edit)
|
|
||||||
setCanEdit(mine && (Date.now() < editThreshold))
|
|
||||||
}}
|
|
||||||
editThreshold={editThreshold}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
<div className={styles.text}>
|
<div className={styles.text}>
|
||||||
|
@ -123,40 +130,9 @@ export default function Comment ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={`${styles.children}`}>
|
<div className={`${styles.children}`}>
|
||||||
{!noReply && !edit && (
|
<Reply
|
||||||
<div className={`${itemStyles.other} ${styles.reply}`}>
|
parentId={item.id} replyOpen={replyOpen}
|
||||||
<div
|
/>
|
||||||
className='d-inline-block'
|
|
||||||
onClick={() => setReply(!reply)}
|
|
||||||
>
|
|
||||||
{reply ? 'cancel' : 'reply'}
|
|
||||||
</div>
|
|
||||||
{canEdit && !reply && !edit &&
|
|
||||||
<>
|
|
||||||
<span> \ </span>
|
|
||||||
<div
|
|
||||||
className='d-inline-block'
|
|
||||||
onClick={() => setEdit(!edit)}
|
|
||||||
>
|
|
||||||
edit
|
|
||||||
<Countdown
|
|
||||||
date={editThreshold}
|
|
||||||
className=' '
|
|
||||||
onComplete={() => {
|
|
||||||
setCanEdit(false)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className={reply ? styles.replyWrapper : 'd-none'}>
|
|
||||||
<Reply
|
|
||||||
parentId={item.id} autoFocus={!replyOpen}
|
|
||||||
onSuccess={() => setReply(replyOpen || false)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{children}
|
{children}
|
||||||
<div className={`${styles.comments} ml-sm-1 ml-md-3`}>
|
<div className={`${styles.comments} ml-sm-1 ml-md-3`}>
|
||||||
{item.comments && !noComments
|
{item.comments && !noComments
|
||||||
|
|
|
@ -11,6 +11,11 @@
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit {
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.collapsed .hunk {
|
.collapsed .hunk {
|
||||||
margin-bottom: .5rem;
|
margin-bottom: .5rem;
|
||||||
}
|
}
|
||||||
|
@ -33,20 +38,8 @@
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reply {
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
padding-bottom: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.replyWrapper {
|
|
||||||
padding-right: 15px;
|
|
||||||
padding-bottom: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.children {
|
.children {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
padding-top: .25rem;
|
|
||||||
margin-left: 24px;
|
margin-left: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import Countdown from 'react-countdown'
|
||||||
|
|
||||||
export default function SimpleCountdown ({ className, onComplete, date }) {
|
export default function SimpleCountdown ({ className, onComplete, date }) {
|
||||||
return (
|
return (
|
||||||
<span className={className || 'text-muted font-weight-bold'}>
|
<span className={className}>
|
||||||
<Countdown
|
<Countdown
|
||||||
date={date}
|
date={date}
|
||||||
renderer={props => <span> {props.formatted.minutes}:{props.formatted.seconds}</span>}
|
renderer={props => <span> {props.formatted.minutes}:{props.formatted.seconds}</span>}
|
||||||
|
|
|
@ -10,7 +10,33 @@ import styles from '../styles/item.module.css'
|
||||||
import { NOFOLLOW_LIMIT } from '../lib/constants'
|
import { NOFOLLOW_LIMIT } from '../lib/constants'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
export default function ItemFull ({ item: qItem, minimal }) {
|
function BioItem ({ item }) {
|
||||||
|
if (!item.text) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ItemText item={item} />
|
||||||
|
<Reply parentId={item.id} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TopLevelItem ({ item }) {
|
||||||
|
return (
|
||||||
|
<Item item={item}>
|
||||||
|
{item.text && <ItemText item={item} />}
|
||||||
|
<Reply parentId={item.id} replyOpen />
|
||||||
|
</Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ItemText ({ item }) {
|
||||||
|
return <Text nofollow={item.sats + item.boost < NOFOLLOW_LIMIT}>{item.text}</Text>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ItemFull ({ item: qItem, bio }) {
|
||||||
const query = gql`
|
const query = gql`
|
||||||
${ITEM_FIELDS}
|
${ITEM_FIELDS}
|
||||||
${COMMENTS}
|
${COMMENTS}
|
||||||
|
@ -49,24 +75,9 @@ export default function ItemFull ({ item: qItem, minimal }) {
|
||||||
<>
|
<>
|
||||||
{item.parentId
|
{item.parentId
|
||||||
? <Comment item={item} replyOpen includeParent noComments />
|
? <Comment item={item} replyOpen includeParent noComments />
|
||||||
: (minimal
|
: (bio
|
||||||
? (
|
? <BioItem item={item} />
|
||||||
<>
|
: <TopLevelItem item={item} />
|
||||||
{item.text &&
|
|
||||||
<div className='mb-3'>
|
|
||||||
<Text nofollow={item.sats + item.boost < NOFOLLOW_LIMIT}>{item.text}</Text>
|
|
||||||
</div>}
|
|
||||||
</>)
|
|
||||||
: (
|
|
||||||
<>
|
|
||||||
<Item item={item}>
|
|
||||||
{item.text &&
|
|
||||||
<div className='mb-3'>
|
|
||||||
<Text nofollow={item.sats + item.boost < NOFOLLOW_LIMIT}>{item.text}</Text>
|
|
||||||
</div>}
|
|
||||||
<Reply parentId={item.id} />
|
|
||||||
</Item>
|
|
||||||
</>)
|
|
||||||
)}
|
)}
|
||||||
<div className={styles.comments}>
|
<div className={styles.comments}>
|
||||||
<Comments comments={item.comments} />
|
<Comments comments={item.comments} />
|
||||||
|
|
|
@ -6,12 +6,14 @@ import { COMMENTS } from '../fragments/comments'
|
||||||
import { useMe } from './me'
|
import { useMe } from './me'
|
||||||
import ActionTooltip from './action-tooltip'
|
import ActionTooltip from './action-tooltip'
|
||||||
import TextareaAutosize from 'react-textarea-autosize'
|
import TextareaAutosize from 'react-textarea-autosize'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
export const CommentSchema = Yup.object({
|
export const CommentSchema = Yup.object({
|
||||||
text: Yup.string().required('required').trim()
|
text: Yup.string().required('required').trim()
|
||||||
})
|
})
|
||||||
|
|
||||||
export default function Reply ({ parentId, onSuccess, autoFocus }) {
|
export default function Reply ({ parentId, onSuccess, replyOpen }) {
|
||||||
|
const [reply, setReply] = useState(replyOpen)
|
||||||
const me = useMe()
|
const me = useMe()
|
||||||
|
|
||||||
const [createComment] = useMutation(
|
const [createComment] = useMutation(
|
||||||
|
@ -47,35 +49,41 @@ export default function Reply ({ parentId, onSuccess, autoFocus }) {
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.reply} mb-1`}>
|
<div>
|
||||||
<Form
|
<div
|
||||||
initial={{
|
className={styles.replyButtons}
|
||||||
text: ''
|
onClick={() => setReply(!reply)}
|
||||||
}}
|
|
||||||
schema={CommentSchema}
|
|
||||||
onSubmit={async (values, { resetForm }) => {
|
|
||||||
const { error } = await createComment({ variables: { ...values, parentId } })
|
|
||||||
if (error) {
|
|
||||||
throw new Error({ message: error.toString() })
|
|
||||||
}
|
|
||||||
resetForm({ text: '' })
|
|
||||||
if (onSuccess) {
|
|
||||||
onSuccess()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<MarkdownInput
|
{reply ? 'cancel' : 'reply'}
|
||||||
name='text'
|
</div>
|
||||||
as={TextareaAutosize}
|
<div className={reply ? `${styles.reply}` : 'd-none'}>
|
||||||
minRows={4}
|
<Form
|
||||||
autoFocus={autoFocus}
|
initial={{
|
||||||
required
|
text: ''
|
||||||
hint={me?.freeComments ? <span className='text-success'>{me.freeComments} free comments left</span> : null}
|
}}
|
||||||
/>
|
schema={CommentSchema}
|
||||||
<ActionTooltip>
|
onSubmit={async (values, { resetForm }) => {
|
||||||
<SubmitButton variant='secondary' className='mt-1'>reply</SubmitButton>
|
const { error } = await createComment({ variables: { ...values, parentId } })
|
||||||
</ActionTooltip>
|
if (error) {
|
||||||
</Form>
|
throw new Error({ message: error.toString() })
|
||||||
|
}
|
||||||
|
resetForm({ text: '' })
|
||||||
|
setReply(replyOpen || false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MarkdownInput
|
||||||
|
name='text'
|
||||||
|
as={TextareaAutosize}
|
||||||
|
minRows={4}
|
||||||
|
autoFocus={!replyOpen}
|
||||||
|
required
|
||||||
|
hint={me?.freeComments ? <span className='text-success'>{me.freeComments} free comments left</span> : null}
|
||||||
|
/>
|
||||||
|
<ActionTooltip>
|
||||||
|
<SubmitButton variant='secondary' className='mt-1'>reply</SubmitButton>
|
||||||
|
</ActionTooltip>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
.reply {
|
.reply {
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
|
padding-right: 15px;
|
||||||
|
padding-bottom: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.replyButtons {
|
||||||
|
font-size: 70%;
|
||||||
|
color: grey;
|
||||||
|
font-weight: bold;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
padding-bottom: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton .input {
|
.skeleton .input {
|
||||||
|
|
|
@ -85,12 +85,14 @@ export default function User ({ user }) {
|
||||||
const [create, setCreate] = useState(false)
|
const [create, setCreate] = useState(false)
|
||||||
const [session] = useSession()
|
const [session] = useSession()
|
||||||
|
|
||||||
|
// need to check if this is the user's page
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout noSeo containClassName={styles.contain}>
|
<Layout noSeo containClassName={styles.contain}>
|
||||||
<Seo user={user} />
|
<Seo user={user} />
|
||||||
<UserHeader user={user} />
|
<UserHeader user={user} />
|
||||||
{user.bio
|
{user.bio
|
||||||
? <ItemFull item={user.bio} minimal />
|
? <ItemFull item={user.bio} bio />
|
||||||
: (
|
: (
|
||||||
<div className={styles.create}>
|
<div className={styles.create}>
|
||||||
{create
|
{create
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
.comments {
|
.comments {
|
||||||
margin-top: 3rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 600px) {
|
@media only screen and (max-width: 600px) {
|
||||||
.comments {
|
.comments {
|
||||||
margin-top: 3rem;
|
margin-top: 1rem;
|
||||||
margin-left: -15px;
|
margin-left: -15px;
|
||||||
margin-right: -15px;
|
margin-right: -15px;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue