Skip to content

Commit

Permalink
feat: comment preference
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <i@innei.in>
  • Loading branch information
Innei committed Jun 29, 2023
1 parent 46bb0d0 commit 118fa88
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 52 deletions.
2 changes: 1 addition & 1 deletion src/app/notes/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default async (props: PropsWithChildren) => {
'mt-12 md:mt-24',
)}
>
<div className="relative hidden lg:block">
<div className="relative hidden min-w-0 lg:block">
<NoteLeftSidebar />
</div>

Expand Down
93 changes: 81 additions & 12 deletions src/components/widgets/comment/CommentBox/ActionBar.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
'use client'

import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useCallback } from 'react'
import clsx from 'clsx'
import { AnimatePresence, motion } from 'framer-motion'
import { produce } from 'immer'
import { useAtomValue } from 'jotai'
import type {
CommentDto,
CommentModel,
PaginateResult,
RequestError,
Expand All @@ -30,6 +31,7 @@ import {
useCommentCompletedCallback,
useCommentOriginalRefId,
useGetCommentBoxAtomValues,
useSetCommentBoxValues,
useUseCommentReply,
} from './hooks'

Expand All @@ -47,6 +49,55 @@ const TextLengthIndicator = () => {
</span>
)
}

const WhisperCheckbox = () => {
const isLogged = useIsLogged()
const isReply = useUseCommentReply()
const isWhisper = useAtomValue(useGetCommentBoxAtomValues().isWhisper)
const setter = useSetCommentBoxValues()
if (isLogged) return null
if (isReply) return null
return (
<label className="label mx-2 flex items-center">
<input
className="checkbox-accent checkbox checkbox-sm mr-2"
checked={isWhisper}
type="checkbox"
onChange={(e) => {
const checked = e.target.checked
setter('isWhisper', checked)
}}
/>
<span className="label-text text-sm">悄悄话</span>
</label>
)
}

const SyncToRecentlyCheckbox = () => {
const isLogged = useIsLogged()
const syncToRecently = useAtomValue(
useGetCommentBoxAtomValues().syncToRecently,
)
const setter = useSetCommentBoxValues()
const isReply = useUseCommentReply()
if (!isLogged) return null
if (isReply) return null
return (
<label className="label mx-2 flex items-center">
<input
className="checkbox-accent checkbox checkbox-sm mr-2"
checked={syncToRecently}
type="checkbox"
onChange={(e) => {
const checked = e.target.checked
setter('syncToRecently', checked)
}}
/>
<span className="label-text text-sm">同步到碎碎念</span>
</label>
)
}

export const CommentBoxActionBar: Component = ({ className }) => {
const hasCommentText = useCommentBoxHasText()

Expand Down Expand Up @@ -79,6 +130,8 @@ export const CommentBoxActionBar: Component = ({ className }) => {
>
<TextLengthIndicator />

<WhisperCheckbox />
<SyncToRecentlyCheckbox />
<SubmitButton />
</motion.aside>
)}
Expand All @@ -93,28 +146,30 @@ const SubmitButton = () => {
text: textAtom,
author: authorAtom,
mail: mailAtom,

isWhisper: isWhisperAtom,
syncToRecently: syncToRecentlyAtom,
} = useGetCommentBoxAtomValues()
const isLogged = useIsLogged()
const queryClient = useQueryClient()
const isReply = useUseCommentReply()
const originalRefId = useCommentOriginalRefId()
const complatedCallback = useCommentCompletedCallback()

const wrappedComplatedCallback = useCallback(
<T extends CommentModel>(data: T): T => {
complatedCallback?.(data)
return data
},
[complatedCallback],
)
const wrappedComplatedCallback = <T extends CommentModel>(data: T): T => {
complatedCallback?.(data)
return data
}

const { isLoading, mutate } = useMutation({
mutationFn: async (refId: string) => {
const text = jotaiStore.get(textAtom)
const author = jotaiStore.get(authorAtom)
const mail = jotaiStore.get(mailAtom)

const commentDto = { text, author, mail }
const commentDto: CommentDto = { text, author, mail }

// Reply Comment
if (isReply) {
if (isLogged) {
return apiClient.comment.proxy.master
Expand All @@ -132,17 +187,31 @@ const SubmitButton = () => {
}
}

// Normal Comment
const isWhisper = jotaiStore.get(isWhisperAtom)
const syncToRecently = jotaiStore.get(syncToRecentlyAtom)

if (isLogged) {
return apiClient.comment.proxy.master
.comment(refId)
.post<CommentModel>({
params: {
ts: Date.now(),
},
data: { text },
})
.then(async (res) => {
if (syncToRecently)
await apiClient.recently.proxy.post({
data: {
content: text,
ref: refId,
},
})

return res
})
.then(wrappedComplatedCallback)
}
// @ts-ignore
commentDto.isWhispers = isWhisper
return apiClient.comment
.comment(refId, commentDto)
.then(wrappedComplatedCallback)
Expand Down
43 changes: 12 additions & 31 deletions src/components/widgets/comment/CommentBox/AuthedInput.tsx
Original file line number Diff line number Diff line change
@@ -1,69 +1,50 @@
'use client'

import { useEffect, useRef } from 'react'
import { useEffect } from 'react'
import clsx from 'clsx'
import Image from 'next/image'

import { useUser } from '@clerk/nextjs'

import { CommentBoxActionBar } from './ActionBar'
import { CommentBoxAuthedInputSkeleton } from './AuthedInputSkeleton'
import { getRandomPlaceholder } from './constants'
import { useCommentBoxTextValue, useSetCommentBoxValues } from './hooks'
import { useSetCommentBoxValues } from './hooks'
import { UniversalTextArea } from './UniversalTextArea'

const TextArea = () => {
const placeholder = useRef(getRandomPlaceholder()).current
const setter = useSetCommentBoxValues()
const value = useCommentBoxTextValue()
return (
<textarea
value={value}
onChange={(e) => {
setter('text', e.target.value)
}}
placeholder={placeholder}
className={clsx(
'h-full w-full resize-none bg-transparent',
'overflow-auto px-3 py-4',
'text-neutral-900/80 dark:text-slate-100/80',
)}
/>
)
}
export const CommentBoxAuthedInput = () => {
const { user } = useUser()
const setter = useSetCommentBoxValues()
const displayName = user
? user.fullName || user.lastName || user.firstName || 'Anonymous'
: ''

useEffect(() => {
if (!user) return
setter(
'author',
user.fullName || user.lastName || user.firstName || 'Anonymous',
)
setter('author', displayName)
setter('avatar', user.profileImageUrl)
setter('mail', user.primaryEmailAddress?.emailAddress || '')
}, [user])
}, [displayName, user])

if (!user) return <CommentBoxAuthedInputSkeleton />
return (
<div className="flex space-x-4">
<div
className={clsx(
'mb-2 ml-2 h-12 w-12 flex-shrink-0 select-none self-end overflow-hidden rounded-full',
'mb-2 flex-shrink-0 select-none self-end overflow-hidden rounded-full',
'dark:ring-zinc-800" bg-zinc-200 ring-2 ring-zinc-200 dark:bg-zinc-800',
'backface-hidden',
'ml-[2px] backface-hidden',
)}
>
<Image
className="rounded-full object-cover"
src={user.profileImageUrl}
alt={`${user.lastName}'s avatar`}
alt={`${displayName}'s avatar`}
width={48}
height={48}
/>
</div>
<div className="relative h-[150px] w-full rounded-lg bg-gray-200/50 pb-5 dark:bg-zinc-800/50">
<TextArea />
<UniversalTextArea />
</div>

<CommentBoxActionBar className="absolute bottom-0 left-12 right-0 mb-2 ml-4 w-auto px-4" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const CommentBoxLegacyForm = () => {
return null
}
23 changes: 20 additions & 3 deletions src/components/widgets/comment/CommentBox/Root.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
'use client'

import { useState } from 'react'
import { useEffect } from 'react'
import { useAtom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
import type { FC } from 'react'
import type { CommentBaseProps } from '../types'

import { SignedIn, SignedOut } from '@clerk/nextjs'

import { useIsLogged } from '~/atoms'
import { AutoResizeHeight } from '~/components/common/AutoResizeHeight'

import { CommentBoxAuthedInput } from './AuthedInput'
import { CommentBoxLegacyForm } from './CommentBoxLegacyForm'
import { CommentBoxProvider } from './providers'
import { CommentBoxSignedOutContent } from './SignedOutContent'

Expand All @@ -17,9 +21,18 @@ const enum CommentBoxMode {
'with-auth',
}

const commentModeAtom = atomWithStorage(
'comment-mode',
CommentBoxMode['with-auth'],
)
export const CommentBoxRoot: FC<CommentBaseProps> = (props) => {
const { refId } = props
const [mode, setMode] = useState<CommentBoxMode>(CommentBoxMode['with-auth'])
const [mode, setMode] = useAtom(commentModeAtom)

const isLogged = useIsLogged()
useEffect(() => {
if (isLogged) setMode(CommentBoxMode['legacy'])
}, [isLogged])
return (
<CommentBoxProvider refId={refId}>
<div className="relative w-full">
Expand All @@ -34,7 +47,11 @@ export const CommentBoxRoot: FC<CommentBaseProps> = (props) => {
}

const CommentBoxLegacy = () => {
return null
return (
<AutoResizeHeight>
<CommentBoxLegacyForm />
</AutoResizeHeight>
)
}

const CommentBoxWithAuth = () => {
Expand Down
27 changes: 27 additions & 0 deletions src/components/widgets/comment/CommentBox/UniversalTextArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use client'

import { useRef } from 'react'
import clsx from 'clsx'

import { getRandomPlaceholder } from './constants'
import { useCommentBoxTextValue, useSetCommentBoxValues } from './hooks'

export const UniversalTextArea = () => {
const placeholder = useRef(getRandomPlaceholder()).current
const setter = useSetCommentBoxValues()
const value = useCommentBoxTextValue()
return (
<textarea
value={value}
onChange={(e) => {
setter('text', e.target.value)
}}
placeholder={placeholder}
className={clsx(
'h-full w-full resize-none bg-transparent',
'overflow-auto px-3 py-4',
'text-neutral-900/80 dark:text-slate-100/80',
)}
/>
)
}
2 changes: 1 addition & 1 deletion src/components/widgets/comment/CommentBox/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,6 @@ export const useSetCommentBoxValues = <
return (key: T, value: ExtractAtomValue<CommentContextValue[T]>) => {
const atom = ctx[key]
if (!atom) throw new Error(`atom ${key} not found`)
jotaiStore.set(atom, value)
jotaiStore.set(atom as any, value)
}
}
19 changes: 15 additions & 4 deletions src/components/widgets/comment/CommentBox/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,32 @@

import { createContext, useRef } from 'react'
import { atom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
import type { CommentModel } from '@mx-space/api-client'
import type { PropsWithChildren } from 'react'

const commentStoragePrefix = 'comment-'
export const createInitialValue = () => ({
refId: atom(''),

text: atom(''),
author: atom(''),
mail: atom(''),
url: atom(''),
author: atomWithStorage(`${commentStoragePrefix}author`, ''),
mail: atomWithStorage(`${commentStoragePrefix}mail`, ''),
url: atomWithStorage(`${commentStoragePrefix}url`, ''),

avatar: atom(''),
source: atom(''),

// settings
isWhisper: atomWithStorage(`${commentStoragePrefix}is-whisper`, false),
syncToRecently: atomWithStorage(
`${commentStoragePrefix}sync-to-recently`,
true,
),
})
export const CommentBoxContext = createContext(createInitialValue())
export const CommentBoxContext = createContext<
ReturnType<typeof createInitialValue>
>(null!)
export const CommentBoxProvider = (
props: PropsWithChildren & { refId: string },
) => {
Expand Down

1 comment on commit 118fa88

@vercel
Copy link

@vercel vercel bot commented on 118fa88 Jun 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

springtide – ./

springtide.vercel.app
springtide-git-main-innei.vercel.app
springtide-innei.vercel.app
innei.in

Please sign in to comment.