refs #4653 Add emoji picker for emoji reactions
This commit is contained in:
parent
7ac9a17b7d
commit
22d2858c1c
|
@ -36,7 +36,7 @@ const customTheme: CustomFlowbiteTheme = {
|
|||
content: 'focus:outline-none',
|
||||
floating: {
|
||||
item: {
|
||||
base: ''
|
||||
base: 'hidden'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -162,6 +162,8 @@ export default function Compose(props: Props) {
|
|||
} else if (emoji.shortcodes) {
|
||||
setBody(current => `${current.slice(0, cursor)}${emoji.shortcodes} ${current.slice(cursor)}`)
|
||||
}
|
||||
const dummy = document.getElementById('dummy-emoji-picker')
|
||||
dummy.click()
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -197,8 +199,9 @@ export default function Compose(props: Props) {
|
|||
</span>
|
||||
)}
|
||||
>
|
||||
<Picker data={data} onEmojiSelect={onEmojiSelect} previewPosition="none" set="native" perLine="7" theme="light" />
|
||||
<Dropdown.Item>
|
||||
<Picker data={data} onEmojiSelect={onEmojiSelect} previewPosition="none" set="native" perLine="7" theme="light" />
|
||||
<span id="dummy-emoji-picker" />
|
||||
</Dropdown.Item>
|
||||
</Dropdown>
|
||||
</Flowbite>
|
||||
|
|
|
@ -5,9 +5,11 @@ import Thread from './Thread'
|
|||
import { Entity, MegalodonInterface } from 'megalodon'
|
||||
import Reply from './Reply'
|
||||
import Profile from './Profile'
|
||||
import { Account } from '@/db'
|
||||
|
||||
type Props = {
|
||||
client: MegalodonInterface
|
||||
account: Account
|
||||
openMedia: (media: Entity.Attachment) => void
|
||||
} & HTMLAttributes<HTMLElement>
|
||||
|
||||
|
@ -43,11 +45,25 @@ export default function Detail(props: Props) {
|
|||
<FaChevronLeft onClick={back} className="cursor-pointer text-lg" />
|
||||
<FaX onClick={close} className="cursor-pointer text-lg" />
|
||||
</div>
|
||||
{target === 'status' && <Thread client={props.client} status_id={router.query.status_id as string} openMedia={props.openMedia} />}
|
||||
{target === 'reply' && (
|
||||
<Reply client={props.client} status_id={router.query.reply_target_id as string} openMedia={props.openMedia} />
|
||||
{target === 'status' && (
|
||||
<Thread
|
||||
client={props.client}
|
||||
account={props.account}
|
||||
status_id={router.query.status_id as string}
|
||||
openMedia={props.openMedia}
|
||||
/>
|
||||
)}
|
||||
{target === 'reply' && (
|
||||
<Reply
|
||||
client={props.client}
|
||||
account={props.account}
|
||||
status_id={router.query.reply_target_id as string}
|
||||
openMedia={props.openMedia}
|
||||
/>
|
||||
)}
|
||||
{target === 'profile' && (
|
||||
<Profile client={props.client} account={props.account} user_id={router.query.user_id as string} openMedia={props.openMedia} />
|
||||
)}
|
||||
{target === 'profile' && <Profile client={props.client} user_id={router.query.user_id as string} openMedia={props.openMedia} />}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -8,9 +8,11 @@ import Timeline from './profile/Timeline'
|
|||
import Followings from './profile/Followings'
|
||||
import Followers from './profile/Followers'
|
||||
import { findLink } from '@/utils/statusParser'
|
||||
import { Account } from '@/db'
|
||||
|
||||
type Props = {
|
||||
client: MegalodonInterface
|
||||
account: Account
|
||||
user_id: string
|
||||
openMedia: (media: Entity.Attachment) => void
|
||||
}
|
||||
|
@ -122,7 +124,7 @@ export default function Profile(props: Props) {
|
|||
<div>
|
||||
<Tabs.Group aria-label="Tabs with icons" style="underline">
|
||||
<Tabs.Item active title={formatMessage({ id: 'profile.timeline' })}>
|
||||
<Timeline client={props.client} user_id={props.user_id} openMedia={props.openMedia} />
|
||||
<Timeline client={props.client} account={props.account} user_id={props.user_id} openMedia={props.openMedia} />
|
||||
</Tabs.Item>
|
||||
<Tabs.Item title={formatMessage({ id: 'profile.followings' })}>
|
||||
<Followings client={props.client} user_id={props.user_id} />
|
||||
|
|
|
@ -3,9 +3,11 @@ import { useEffect, useRef, useState } from 'react'
|
|||
import { Virtuoso } from 'react-virtuoso'
|
||||
import Status from '../timelines/status/Status'
|
||||
import Compose from '../compose/Compose'
|
||||
import { Account } from '@/db'
|
||||
|
||||
type Props = {
|
||||
client: MegalodonInterface
|
||||
account: Account
|
||||
status_id: string
|
||||
openMedia: (media: Entity.Attachment) => void
|
||||
}
|
||||
|
@ -49,7 +51,14 @@ export default function Reply(props: Props) {
|
|||
style={{ height: `calc(100% - ${composeHeight}px)` }}
|
||||
data={[...ancestors, status].filter(s => s !== null)}
|
||||
itemContent={(_, status) => (
|
||||
<Status client={props.client} status={status} key={status.id} onRefresh={() => {}} openMedia={props.openMedia} />
|
||||
<Status
|
||||
client={props.client}
|
||||
account={props.account}
|
||||
status={status}
|
||||
key={status.id}
|
||||
onRefresh={() => {}}
|
||||
openMedia={props.openMedia}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div ref={composeRef}>
|
||||
|
|
|
@ -2,9 +2,11 @@ import { Entity, MegalodonInterface } from 'megalodon'
|
|||
import { useEffect, useState } from 'react'
|
||||
import { Virtuoso } from 'react-virtuoso'
|
||||
import Status from '../timelines/status/Status'
|
||||
import { Account } from '@/db'
|
||||
|
||||
type Props = {
|
||||
client: MegalodonInterface
|
||||
account: Account
|
||||
status_id: string
|
||||
openMedia: (media: Entity.Attachment) => void
|
||||
}
|
||||
|
@ -33,7 +35,14 @@ export default function Thread(props: Props) {
|
|||
style={{ height: 'calc(100% - 50px)' }}
|
||||
data={[...ancestors, status, ...descendants].filter(s => s !== null)}
|
||||
itemContent={(_, status) => (
|
||||
<Status client={props.client} status={status} key={status.id} onRefresh={() => {}} openMedia={props.openMedia} />
|
||||
<Status
|
||||
client={props.client}
|
||||
account={props.account}
|
||||
status={status}
|
||||
key={status.id}
|
||||
onRefresh={() => {}}
|
||||
openMedia={props.openMedia}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import Status from '@/components/timelines/status/Status'
|
||||
import { Account } from '@/db'
|
||||
import { Entity, MegalodonInterface } from 'megalodon'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
type Props = {
|
||||
client: MegalodonInterface
|
||||
account: Account
|
||||
user_id: string
|
||||
openMedia: (media: Entity.Attachment) => void
|
||||
}
|
||||
|
@ -43,6 +45,7 @@ export default function Timeline(props: Props) {
|
|||
{statuses.map((status, index) => (
|
||||
<Status
|
||||
client={props.client}
|
||||
account={props.account}
|
||||
status={status}
|
||||
key={index}
|
||||
onRefresh={status => setStatuses(current => updateStatus(current, status))}
|
||||
|
|
|
@ -125,6 +125,7 @@ export default function Notifications(props: Props) {
|
|||
itemContent={(_, notification) => (
|
||||
<Notification
|
||||
client={props.client}
|
||||
account={props.account}
|
||||
notification={notification}
|
||||
onRefresh={updateStatus}
|
||||
key={notification.id}
|
||||
|
|
|
@ -182,6 +182,7 @@ export default function Timeline(props: Props) {
|
|||
itemContent={(_, status) => (
|
||||
<Status
|
||||
client={props.client}
|
||||
account={props.account}
|
||||
status={status}
|
||||
key={status.id}
|
||||
onRefresh={status => setStatuses(current => updateStatus(current, status))}
|
||||
|
@ -194,7 +195,7 @@ export default function Timeline(props: Props) {
|
|||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<Detail client={props.client} className="detail" openMedia={media => props.setAttachment(media)} />
|
||||
<Detail client={props.client} account={props.account} className="detail" openMedia={media => props.setAttachment(media)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,9 +2,11 @@ import { Entity, MegalodonInterface } from 'megalodon'
|
|||
import Status from '../status/Status'
|
||||
import Reaction from './Reaction'
|
||||
import Follow from './Follow'
|
||||
import { Account } from '@/db'
|
||||
|
||||
type Props = {
|
||||
notification: Entity.Notification
|
||||
account: Account
|
||||
client: MegalodonInterface
|
||||
onRefresh: (status: Entity.Status) => void
|
||||
openMedia: (media: Entity.Attachment) => void
|
||||
|
@ -14,7 +16,15 @@ export default function Notification(props: Props) {
|
|||
switch (props.notification.type) {
|
||||
case 'mention': {
|
||||
if (props.notification.status) {
|
||||
return <Status client={props.client} status={props.notification.status} onRefresh={props.onRefresh} openMedia={props.openMedia} />
|
||||
return (
|
||||
<Status
|
||||
account={props.account}
|
||||
client={props.client}
|
||||
status={props.notification.status}
|
||||
onRefresh={props.onRefresh}
|
||||
openMedia={props.openMedia}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -1,13 +1,29 @@
|
|||
import { CustomFlowbiteTheme, Dropdown, Flowbite } from 'flowbite-react'
|
||||
import { Entity, MegalodonInterface } from 'megalodon'
|
||||
import { useRouter } from 'next/router'
|
||||
import { FaBookmark, FaEllipsis, FaReply, FaRetweet, FaStar } from 'react-icons/fa6'
|
||||
import { FaBookmark, FaEllipsis, FaFaceLaughBeam, FaReply, FaRetweet, FaStar } from 'react-icons/fa6'
|
||||
import Picker from '@emoji-mart/react'
|
||||
import { data } from '@/utils/emojiData'
|
||||
import { Account } from '@/db'
|
||||
|
||||
type Props = {
|
||||
status: Entity.Status
|
||||
account: Account
|
||||
client: MegalodonInterface
|
||||
onRefresh: () => void
|
||||
}
|
||||
|
||||
const customTheme: CustomFlowbiteTheme = {
|
||||
dropdown: {
|
||||
content: 'focus:outline-none',
|
||||
floating: {
|
||||
item: {
|
||||
base: 'hidden'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function Actions(props: Props) {
|
||||
const router = useRouter()
|
||||
|
||||
|
@ -42,12 +58,39 @@ export default function Actions(props: Props) {
|
|||
props.onRefresh()
|
||||
}
|
||||
|
||||
const onEmojiSelect = async emoji => {
|
||||
await props.client.createEmojiReaction(props.status.id, emoji.native)
|
||||
const dummy = document.getElementById('dummy-emoji-picker')
|
||||
dummy.click()
|
||||
props.onRefresh()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-6">
|
||||
<FaReply className={`w-4 text-gray-400 cursor-pointer hover:text-gray-600`} onClick={reply} />
|
||||
<FaRetweet className={`${retweetColor(props.status)} w-4 cursor-pointer hover:text-gray-600`} onClick={reblog} />
|
||||
<FaStar className={`${favouriteColor(props.status)} w-4 cursor-pointer hover:text-gray-600`} onClick={favourite} />
|
||||
<FaBookmark className={`${bookmarkColor(props.status)} w-4 cursor-pointer hover:text-gray-600`} onClick={bookmark} />
|
||||
{props.account.sns !== 'mastodon' && (
|
||||
<Flowbite theme={{ theme: customTheme }}>
|
||||
<Dropdown
|
||||
disabled
|
||||
label=""
|
||||
dismissOnClick
|
||||
renderTrigger={() => (
|
||||
<span className="text-gray-400 hover:text-gray-600 cursor-pointer">
|
||||
<FaFaceLaughBeam />
|
||||
</span>
|
||||
)}
|
||||
>
|
||||
<Picker data={data} onEmojiSelect={onEmojiSelect} previewPosition="none" set="native" perLine="7" theme="light" />
|
||||
<Dropdown.Item>
|
||||
<span id="dummy-emoji-picker" />
|
||||
</Dropdown.Item>
|
||||
</Dropdown>
|
||||
</Flowbite>
|
||||
)}
|
||||
|
||||
<FaEllipsis className="w-4 text-gray-400 cursor-pointer hover:text-gray-600" />
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -11,9 +11,11 @@ import Actions from './Actions'
|
|||
import { useRouter } from 'next/router'
|
||||
import { MouseEventHandler, useState } from 'react'
|
||||
import { findLink } from '@/utils/statusParser'
|
||||
import { Account } from '@/db'
|
||||
|
||||
type Props = {
|
||||
status: Entity.Status
|
||||
account: Account
|
||||
client: MegalodonInterface
|
||||
onRefresh: (status: Entity.Status) => void
|
||||
openMedia: (media: Entity.Attachment) => void
|
||||
|
@ -83,7 +85,7 @@ export default function Status(props: Props) {
|
|||
</>
|
||||
)}
|
||||
|
||||
<Actions status={status} client={props.client} onRefresh={onRefresh} />
|
||||
<Actions status={status} client={props.client} account={props.account} onRefresh={onRefresh} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue