chore: complete message send
This commit is contained in:
parent
50c7b92e36
commit
a9c055b467
11 changed files with 214 additions and 104 deletions
|
@ -95,6 +95,7 @@
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
"lucide-react": "^0.263.0",
|
"lucide-react": "^0.263.0",
|
||||||
|
"nanoid": "^4.0.2",
|
||||||
"peerjs": "^1.4.7",
|
"peerjs": "^1.4.7",
|
||||||
"react-markdown": "^8.0.7",
|
"react-markdown": "^8.0.7",
|
||||||
"react-nice-avatar": "^1.4.1",
|
"react-nice-avatar": "^1.4.1",
|
||||||
|
|
|
@ -38,6 +38,9 @@ dependencies:
|
||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.263.0
|
specifier: ^0.263.0
|
||||||
version: 0.263.0(react@18.2.0)
|
version: 0.263.0(react@18.2.0)
|
||||||
|
nanoid:
|
||||||
|
specifier: ^4.0.2
|
||||||
|
version: 4.0.2
|
||||||
peerjs:
|
peerjs:
|
||||||
specifier: ^1.4.7
|
specifier: ^1.4.7
|
||||||
version: 1.4.7
|
version: 1.4.7
|
||||||
|
@ -5717,6 +5720,12 @@ packages:
|
||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
/nanoid@4.0.2:
|
||||||
|
resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==}
|
||||||
|
engines: {node: ^14 || ^16 || >=18}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/natural-compare-lite@1.4.0:
|
/natural-compare-lite@1.4.0:
|
||||||
resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
|
resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
|
@ -1,57 +1,52 @@
|
||||||
import { type FC, type ChangeEvent, type KeyboardEvent } from 'react'
|
import { type FC, type ChangeEvent, type KeyboardEvent } from 'react'
|
||||||
import { useRemeshDomain, useRemeshQuery, useRemeshSend } from 'remesh-react'
|
|
||||||
import { Textarea } from '@/components/ui/Textarea'
|
import { Textarea } from '@/components/ui/Textarea'
|
||||||
import { Markdown } from '@/components/ui/Markdown'
|
import { Markdown } from '@/components/ui/Markdown'
|
||||||
import { cn } from '@/utils'
|
import { cn } from '@/utils'
|
||||||
import MessageInputDomain from '@/domain/MessageInput'
|
|
||||||
import { MESSAGE_MAX_LENGTH } from '@/constants'
|
|
||||||
|
|
||||||
export interface MessageInputProps {
|
export interface MessageInputProps {
|
||||||
|
value?: string
|
||||||
className?: string
|
className?: string
|
||||||
maxLength?: number
|
maxLength?: number
|
||||||
|
preview?: boolean
|
||||||
|
onInput?: (value: string) => void
|
||||||
|
onEnter?: (value: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessageInput: FC<MessageInputProps> = ({ className }) => {
|
const MessageInput: FC<MessageInputProps> = ({ value = '', className, maxLength = 500, onInput, onEnter, preview }) => {
|
||||||
const send = useRemeshSend()
|
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
const messageInputDomain = useRemeshDomain(MessageInputDomain())
|
|
||||||
|
|
||||||
const message = useRemeshQuery(messageInputDomain.query.ValueQuery())
|
|
||||||
const isPreview = useRemeshQuery(messageInputDomain.query.PreviewQuery())
|
|
||||||
|
|
||||||
const handleInput = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
||||||
send(messageInputDomain.command.InputCommand(e.target.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
|
||||||
if (e.key === 'Enter' && !(e.shiftKey || e.ctrlKey || e.altKey || e.metaKey)) {
|
if (e.key === 'Enter' && !(e.shiftKey || e.ctrlKey || e.altKey || e.metaKey)) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
send(messageInputDomain.command.EnterCommand())
|
onEnter?.(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const handleInput = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
onInput?.(e.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('relative', className)}>
|
<div className={cn('relative', className)}>
|
||||||
{isPreview ? (
|
{preview ? (
|
||||||
<Markdown className="max-h-32 rounded-lg border border-input bg-gray-50 2xl:max-h-40">{message}</Markdown>
|
<Markdown className="max-h-32 rounded-lg border border-input bg-gray-50 2xl:max-h-40">{value}</Markdown>
|
||||||
) : (
|
) : (
|
||||||
// Hack: Auto-Growing Textarea
|
// Hack: Auto-Growing Textarea
|
||||||
<div
|
<div
|
||||||
data-value={message}
|
data-value={value}
|
||||||
className="grid after:pointer-events-none after:invisible after:col-start-1 after:col-end-2 after:row-start-1 after:row-end-2 after:box-border after:max-h-28 after:w-full after:overflow-x-hidden after:whitespace-pre-wrap after:break-words after:rounded-lg after:border after:px-3 after:py-2 after:pb-5 after:text-sm after:content-[attr(data-value)] after:2xl:max-h-40"
|
className="grid after:pointer-events-none after:invisible after:col-start-1 after:col-end-2 after:row-start-1 after:row-end-2 after:box-border after:max-h-28 after:w-full after:overflow-x-hidden after:whitespace-pre-wrap after:break-words after:rounded-lg after:border after:px-3 after:py-2 after:pb-5 after:text-sm after:content-[attr(data-value)] after:2xl:max-h-40"
|
||||||
>
|
>
|
||||||
<Textarea
|
<Textarea
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
maxLength={MESSAGE_MAX_LENGTH}
|
maxLength={maxLength}
|
||||||
className="col-start-1 col-end-2 row-start-1 row-end-2 box-border max-h-28 resize-none overflow-x-hidden break-words rounded-lg bg-gray-50 pb-5 text-sm 2xl:max-h-40"
|
className="col-start-1 col-end-2 row-start-1 row-end-2 box-border max-h-28 resize-none overflow-x-hidden break-words rounded-lg bg-gray-50 pb-5 text-sm 2xl:max-h-40"
|
||||||
rows={2}
|
rows={2}
|
||||||
value={message}
|
value={value}
|
||||||
placeholder="Type your message here."
|
placeholder="Type your message here."
|
||||||
onInput={handleInput}
|
onInput={handleInput}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="absolute bottom-1 right-3 rounded-lg text-xs text-slate-400">
|
<div className="absolute bottom-1 right-3 rounded-lg text-xs text-slate-400">
|
||||||
{message?.length ?? 0}/{MESSAGE_MAX_LENGTH}
|
{value?.length ?? 0}/{maxLength}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,22 +4,13 @@ import { FrownIcon, ThumbsUpIcon } from 'lucide-react'
|
||||||
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/Avatar'
|
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/Avatar'
|
||||||
|
|
||||||
import LikeButton from '@/components/LikeButton'
|
import LikeButton from '@/components/LikeButton'
|
||||||
|
import { type Message } from '@/domain/MessageList'
|
||||||
|
|
||||||
export interface MessageProps {
|
export interface MessageItemProps {
|
||||||
data: {
|
data: Message
|
||||||
id: string
|
|
||||||
body: string
|
|
||||||
username: string
|
|
||||||
avatar: string
|
|
||||||
date: number
|
|
||||||
likeChecked: boolean
|
|
||||||
hateChecked: boolean
|
|
||||||
likeCount: number
|
|
||||||
hateCount: number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Message: FC<MessageProps> = ({ data }) => {
|
const MessageItem: FC<MessageItemProps> = ({ data }) => {
|
||||||
const [formatData, setFormatData] = useState({
|
const [formatData, setFormatData] = useState({
|
||||||
...data,
|
...data,
|
||||||
date: format(data.date, 'yyyy/MM/dd HH:mm:ss')
|
date: format(data.date, 'yyyy/MM/dd HH:mm:ss')
|
||||||
|
@ -76,5 +67,5 @@ const Message: FC<MessageProps> = ({ data }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Message.displayName = 'Message'
|
MessageItem.displayName = 'MessageItem'
|
||||||
export default Message
|
export default MessageItem
|
12
src/components/MessageList.tsx
Normal file
12
src/components/MessageList.tsx
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { type ReactElement, type FC } from 'react'
|
||||||
|
|
||||||
|
import { type MessageItemProps } from './MessageItem'
|
||||||
|
|
||||||
|
export interface MessageListProps {
|
||||||
|
children?: Array<ReactElement<MessageItemProps>>
|
||||||
|
}
|
||||||
|
const MessageList: FC<MessageListProps> = ({ children }) => {
|
||||||
|
return <div className="grid content-start overflow-y-auto p-4">{children}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MessageList
|
|
@ -4,10 +4,12 @@ import InputModule from './modules/Input'
|
||||||
const MessageInputDomain = Remesh.domain({
|
const MessageInputDomain = Remesh.domain({
|
||||||
name: 'MessageInputDomain',
|
name: 'MessageInputDomain',
|
||||||
impl: (domain) => {
|
impl: (domain) => {
|
||||||
const inputModule = InputModule(domain, {
|
const MessageInputModule = InputModule(domain, {
|
||||||
name: 'MessageInput'
|
name: 'MessageInputModule'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const MessageQuery = MessageInputModule.query.ValueQuery
|
||||||
|
|
||||||
const PreviewState = domain.state({
|
const PreviewState = domain.state({
|
||||||
name: 'MessageInput.PreviewState',
|
name: 'MessageInput.PreviewState',
|
||||||
default: false
|
default: false
|
||||||
|
@ -28,7 +30,10 @@ const MessageInputDomain = Remesh.domain({
|
||||||
})
|
})
|
||||||
|
|
||||||
const EnterEvent = domain.event({
|
const EnterEvent = domain.event({
|
||||||
name: 'MessageInput.EnterEvent'
|
name: 'MessageInput.EnterEvent',
|
||||||
|
impl: ({ get }) => {
|
||||||
|
return get(MessageInputModule.query.ValueQuery())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const EnterCommand = domain.command({
|
const EnterCommand = domain.command({
|
||||||
|
@ -41,23 +46,23 @@ const MessageInputDomain = Remesh.domain({
|
||||||
const ClearCommand = domain.command({
|
const ClearCommand = domain.command({
|
||||||
name: 'MessageInput.ClearCommand',
|
name: 'MessageInput.ClearCommand',
|
||||||
impl: () => {
|
impl: () => {
|
||||||
return inputModule.command.InputCommand('')
|
return MessageInputModule.command.InputCommand('')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
query: {
|
query: {
|
||||||
...inputModule.query,
|
MessageQuery,
|
||||||
PreviewQuery
|
PreviewQuery
|
||||||
},
|
},
|
||||||
command: {
|
command: {
|
||||||
...inputModule.command,
|
...MessageInputModule.command,
|
||||||
EnterCommand,
|
EnterCommand,
|
||||||
ClearCommand,
|
ClearCommand,
|
||||||
PreviewCommand
|
PreviewCommand
|
||||||
},
|
},
|
||||||
event: {
|
event: {
|
||||||
...inputModule.event,
|
...MessageInputModule.event,
|
||||||
EnterEvent
|
EnterEvent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
102
src/domain/MessageList.ts
Normal file
102
src/domain/MessageList.ts
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
import { Remesh } from 'remesh'
|
||||||
|
import { ListModule } from 'remesh/modules/list'
|
||||||
|
import { nanoid } from 'nanoid'
|
||||||
|
|
||||||
|
export interface Message {
|
||||||
|
id: string
|
||||||
|
body: string
|
||||||
|
username: string
|
||||||
|
avatar: string
|
||||||
|
date: number
|
||||||
|
likeChecked: boolean
|
||||||
|
hateChecked: boolean
|
||||||
|
likeCount: number
|
||||||
|
hateCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const MessageListDomain = Remesh.domain({
|
||||||
|
name: 'MessageListDomain',
|
||||||
|
impl: (domain) => {
|
||||||
|
const MessageListModule = ListModule<Message>(domain, {
|
||||||
|
name: 'MessageListModule',
|
||||||
|
key: (message) => message.id
|
||||||
|
})
|
||||||
|
|
||||||
|
const ListQuery = MessageListModule.query.ItemListQuery
|
||||||
|
|
||||||
|
const ItemQuery = MessageListModule.query.ItemQuery
|
||||||
|
|
||||||
|
const ChangeEvent = domain.event({
|
||||||
|
name: 'MessageList.ChangeEvent',
|
||||||
|
impl: ({ get }) => {
|
||||||
|
return get(ListQuery())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const CreateEvent = domain.event({
|
||||||
|
name: 'MessageList.CreateEvent'
|
||||||
|
})
|
||||||
|
|
||||||
|
const CreateCommand = domain.command({
|
||||||
|
name: 'MessageList.CreateCommand',
|
||||||
|
impl: (_, message: Omit<Message, 'id'>) => {
|
||||||
|
const id = nanoid()
|
||||||
|
return [MessageListModule.command.AddItemCommand({ ...message, id }), CreateEvent(), ChangeEvent()]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const UpdateEvent = domain.event({
|
||||||
|
name: 'MessageList.UpdateEvent'
|
||||||
|
})
|
||||||
|
|
||||||
|
const UpdateCommand = domain.command({
|
||||||
|
name: 'MessageList.UpdateCommand',
|
||||||
|
impl: (_, message: Message) => {
|
||||||
|
return [MessageListModule.command.UpdateItemCommand(message), UpdateEvent(), ChangeEvent()]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const DeleteEvent = domain.event({
|
||||||
|
name: 'MessageList.DeleteEvent'
|
||||||
|
})
|
||||||
|
|
||||||
|
const DeleteCommand = domain.command({
|
||||||
|
name: 'MessageList.DeleteCommand',
|
||||||
|
impl: (_, id: string) => {
|
||||||
|
return [MessageListModule.command.DeleteItemCommand(id), DeleteEvent(), ChangeEvent()]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const ClearEvent = domain.event({
|
||||||
|
name: 'MessageList.ClearEvent'
|
||||||
|
})
|
||||||
|
|
||||||
|
const ClearCommand = domain.command({
|
||||||
|
name: 'MessageList.ClearCommand',
|
||||||
|
impl: () => {
|
||||||
|
return [MessageListModule.command.SetListCommand([]), ClearEvent(), ChangeEvent()]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
query: {
|
||||||
|
ItemQuery,
|
||||||
|
ListQuery
|
||||||
|
},
|
||||||
|
command: {
|
||||||
|
CreateCommand,
|
||||||
|
UpdateCommand,
|
||||||
|
DeleteCommand,
|
||||||
|
ClearCommand
|
||||||
|
},
|
||||||
|
event: {
|
||||||
|
CreateEvent,
|
||||||
|
UpdateEvent,
|
||||||
|
DeleteEvent,
|
||||||
|
ClearEvent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default MessageListDomain
|
|
@ -1,7 +1,7 @@
|
||||||
import { Remesh, type Capitalize, type RemeshDomainContext } from 'remesh'
|
import { Remesh, type DomainConceptName, type RemeshDomainContext } from 'remesh'
|
||||||
|
|
||||||
export interface InputModuleOptions {
|
export interface InputModuleOptions {
|
||||||
name: Capitalize
|
name: DomainConceptName<'InputModule'>
|
||||||
value?: string
|
value?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
@ -19,27 +19,31 @@ const InputModule = (domain: RemeshDomainContext, options: InputModuleOptions) =
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const InputEvent = domain.event<string>({
|
const InputEvent = domain.event({
|
||||||
name: `${options.name}.InputEvent`
|
name: `${options.name}.InputEvent`,
|
||||||
|
impl: ({ get }) => {
|
||||||
|
return get(ValueState())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const InputCommand = domain.command({
|
const InputCommand = domain.command({
|
||||||
name: `${options.name}.InputCommand`,
|
name: `${options.name}.InputCommand`,
|
||||||
impl: (_, value: string) => {
|
impl: (_, value: string) => {
|
||||||
InputEvent(value)
|
return [ValueState().new(value), InputEvent()]
|
||||||
return ValueState().new(value)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const ChangeEvent = domain.event<string>({
|
const ChangeEvent = domain.event({
|
||||||
name: `${options.name}.ChangeEvent`
|
name: `${options.name}.ChangeEvent`,
|
||||||
|
impl: ({ get }) => {
|
||||||
|
return get(ValueState())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const ChangeCommand = domain.command({
|
const ChangeCommand = domain.command({
|
||||||
name: `${options.name}.ChangeCommand`,
|
name: `${options.name}.ChangeCommand`,
|
||||||
impl: (_, value: string) => {
|
impl: (_, value: string) => {
|
||||||
ChangeEvent(value)
|
return [ValueState().new(value), ChangeEvent()]
|
||||||
return ValueState().new(value)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -55,27 +59,25 @@ const InputModule = (domain: RemeshDomainContext, options: InputModuleOptions) =
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const FocusEvent = domain.event<boolean>({
|
const FocusEvent = domain.event({
|
||||||
name: `${options.name}.FocusEvent`
|
name: `${options.name}.FocusEvent`
|
||||||
})
|
})
|
||||||
|
|
||||||
const BlurEvent = domain.event<boolean>({
|
const BlurEvent = domain.event({
|
||||||
name: `${options.name}.BlurEvent`
|
name: `${options.name}.BlurEvent`
|
||||||
})
|
})
|
||||||
|
|
||||||
const BlurCommand = domain.command({
|
const BlurCommand = domain.command({
|
||||||
name: `${options.name}.BlurCommand`,
|
name: `${options.name}.BlurCommand`,
|
||||||
impl: () => {
|
impl: () => {
|
||||||
BlurEvent(false)
|
return [FocusState().new(false), BlurEvent()]
|
||||||
return FocusState().new(false)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const FocusCommand = domain.command({
|
const FocusCommand = domain.command({
|
||||||
name: `${options.name}.FocusCommand`,
|
name: `${options.name}.FocusCommand`,
|
||||||
impl: () => {
|
impl: () => {
|
||||||
FocusEvent(true)
|
return [FocusState().new(true), FocusEvent()]
|
||||||
return FocusState().new(true)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useMedia } from 'react-use'
|
import { useMedia } from 'react-use'
|
||||||
import { BREAKPOINTS } from '@/constants'
|
import { BREAKPOINTS } from '@/constants'
|
||||||
|
|
||||||
export function useBreakpoint() {
|
const useBreakpoint = () => {
|
||||||
const isSM = useMedia(`(min-width: ${BREAKPOINTS.sm})`)
|
const isSM = useMedia(`(min-width: ${BREAKPOINTS.sm})`)
|
||||||
const isMD = useMedia(`(min-width: ${BREAKPOINTS.md})`)
|
const isMD = useMedia(`(min-width: ${BREAKPOINTS.md})`)
|
||||||
const isLG = useMedia(`(min-width: ${BREAKPOINTS.lg})`)
|
const isLG = useMedia(`(min-width: ${BREAKPOINTS.lg})`)
|
||||||
|
@ -15,3 +15,5 @@ export function useBreakpoint() {
|
||||||
is2XL
|
is2XL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default useBreakpoint
|
||||||
|
|
|
@ -1,22 +1,42 @@
|
||||||
import { type FC } from 'react'
|
import { type FC } from 'react'
|
||||||
import { SmileIcon, CornerDownLeftIcon, ImageIcon } from 'lucide-react'
|
import { SmileIcon, CornerDownLeftIcon, ImageIcon } from 'lucide-react'
|
||||||
import { useRemeshDomain, useRemeshEvent, useRemeshSend } from 'remesh-react'
|
import { useRemeshDomain, useRemeshQuery, useRemeshSend } from 'remesh-react'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import MessageInput from '@/components/MessageInput'
|
import MessageInput from '@/components/MessageInput'
|
||||||
import MessageInputDomain from '@/domain/MessageInput'
|
import MessageInputDomain from '@/domain/MessageInput'
|
||||||
|
import MessageListDomain from '@/domain/MessageList'
|
||||||
|
|
||||||
const Footer: FC = () => {
|
const Footer: FC = () => {
|
||||||
const send = useRemeshSend()
|
const send = useRemeshSend()
|
||||||
|
const messageListDomain = useRemeshDomain(MessageListDomain())
|
||||||
const messageInputDomain = useRemeshDomain(MessageInputDomain())
|
const messageInputDomain = useRemeshDomain(MessageInputDomain())
|
||||||
|
|
||||||
|
const message = useRemeshQuery(messageInputDomain.query.MessageQuery())
|
||||||
|
const isPreview = useRemeshQuery(messageInputDomain.query.PreviewQuery())
|
||||||
|
|
||||||
|
const handleInput = (value: string) => {
|
||||||
|
send(messageInputDomain.command.InputCommand(value))
|
||||||
|
}
|
||||||
|
|
||||||
const handleSend = () => {
|
const handleSend = () => {
|
||||||
|
send(
|
||||||
|
messageListDomain.command.CreateCommand({
|
||||||
|
username: '墨绿青苔',
|
||||||
|
avatar: 'https://avatars.githubusercontent.com/u/10251037?v=4',
|
||||||
|
body: message,
|
||||||
|
date: Date.now(),
|
||||||
|
likeChecked: false,
|
||||||
|
likeCount: 0,
|
||||||
|
hateChecked: false,
|
||||||
|
hateCount: 0
|
||||||
|
})
|
||||||
|
)
|
||||||
send(messageInputDomain.command.ClearCommand())
|
send(messageInputDomain.command.ClearCommand())
|
||||||
}
|
}
|
||||||
useRemeshEvent(messageInputDomain.event.EnterEvent, handleSend)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-y-2 p-4">
|
<div className="grid gap-y-2 p-4">
|
||||||
<MessageInput></MessageInput>
|
<MessageInput value={message} preview={isPreview} onEnter={handleSend} onInput={handleInput}></MessageInput>
|
||||||
<div className="grid grid-cols-[auto_auto_1fr] items-center justify-items-end">
|
<div className="grid grid-cols-[auto_auto_1fr] items-center justify-items-end">
|
||||||
<Button variant="ghost" size="icon">
|
<Button variant="ghost" size="icon">
|
||||||
<SmileIcon size={20} />
|
<SmileIcon size={20} />
|
||||||
|
|
|
@ -1,48 +1,19 @@
|
||||||
import { type FC } from 'react'
|
import { type FC } from 'react'
|
||||||
import Message from '@/components/Message'
|
import { useRemeshDomain, useRemeshQuery } from 'remesh-react'
|
||||||
|
import MessageList from '@/components/MessageList'
|
||||||
|
import MessageItem from '@/components/MessageItem'
|
||||||
|
import MessageListDomain from '@/domain/MessageList'
|
||||||
|
|
||||||
const Main: FC = () => {
|
const Main: FC = () => {
|
||||||
const messages = [
|
const messageListDomain = useRemeshDomain(MessageListDomain())
|
||||||
{
|
const messageList = useRemeshQuery(messageListDomain.query.ListQuery())
|
||||||
id: '1',
|
|
||||||
body: 'Who are you?',
|
|
||||||
username: 'molvqingtai',
|
|
||||||
avatar: 'https://github.com/shadcn.png',
|
|
||||||
date: Date.now(),
|
|
||||||
likeChecked: false,
|
|
||||||
hateChecked: false,
|
|
||||||
likeCount: 0,
|
|
||||||
hateCount: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
body: `I'm Chinese`,
|
|
||||||
username: 'Love XJP',
|
|
||||||
avatar: 'https://github.com/shadcn.png',
|
|
||||||
date: Date.now(),
|
|
||||||
likeChecked: false,
|
|
||||||
hateChecked: false,
|
|
||||||
likeCount: 0,
|
|
||||||
hateCount: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
body: 'Do you like XJP?',
|
|
||||||
username: 'molvqingtai',
|
|
||||||
avatar: 'https://github.com/shadcn.png',
|
|
||||||
date: Date.now(),
|
|
||||||
likeChecked: false,
|
|
||||||
hateChecked: true,
|
|
||||||
likeCount: 9999,
|
|
||||||
hateCount: 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
return (
|
return (
|
||||||
<div className="grid content-start overflow-y-auto p-4">
|
<MessageList>
|
||||||
{messages.map((message) => (
|
{messageList.map((message) => (
|
||||||
<Message key={message.id} data={message} />
|
<MessageItem key={message.id} data={message}></MessageItem>
|
||||||
))}
|
))}
|
||||||
</div>
|
</MessageList>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue