import { Show, For, createSignal, createMemo, type JSX, type Component } from 'solid-js'; import type { Event as NostrEvent } from 'nostr-tools'; import uniq from 'lodash/uniq'; import HeartOutlined from 'heroicons/24/outline/heart.svg'; import HeartSolid from 'heroicons/24/solid/heart.svg'; import ArrowPathRoundedSquare from 'heroicons/24/outline/arrow-path-rounded-square.svg'; import ChatBubbleLeft from 'heroicons/24/outline/chat-bubble-left.svg'; import EllipsisHorizontal from 'heroicons/24/outline/ellipsis-horizontal.svg'; import useProfile from '@/nostr/useProfile'; import useConfig from '@/nostr/useConfig'; import usePubkey from '@/nostr/usePubkey'; import useCommands from '@/nostr/useCommands'; import useReactions from '@/nostr/useReactions'; import useDeprecatedReposts from '@/nostr/useDeprecatedReposts'; import useDatePulser from '@/hooks/useDatePulser'; import { formatRelative } from '@/utils/formatDate'; import ColumnItem from '@/components/ColumnItem'; import GeneralUserMentionDisplay from '@/components/textNote/GeneralUserMentionDisplay'; import TextNoteContentDisplay from '@/components/textNote/TextNoteContentDisplay'; import ReplyPostForm from '@/components/ReplyPostForm'; export type TextNoteProps = { event: NostrEvent; }; const TextNote: Component = (props) => { const currentDate = useDatePulser(); const [config] = useConfig(); const commands = useCommands(); const pubkey = usePubkey(); const [showReplyForm, setShowReplyForm] = createSignal(false); const { profile: author } = useProfile(() => ({ relayUrls: config().relayUrls, pubkey: props.event.pubkey, })); const { reactions, isReactedBy, invalidateReactions } = useReactions(() => ({ relayUrls: config().relayUrls, eventId: props.event.id, })); const { reposts, isRepostedBy, invalidateDeprecatedReposts } = useDeprecatedReposts(() => ({ relayUrls: config().relayUrls, eventId: props.event.id, })); const isReactedByMe = createMemo(() => isReactedBy(pubkey())); const isRepostedByMe = createMemo(() => isRepostedBy(pubkey())); const replyingToPubKeys = createMemo(() => uniq(props.event.tags.filter((tag) => tag[0] === 'p').map((e) => e[1])), ); const createdAt = () => formatRelative(new Date(props.event.created_at * 1000), currentDate()); const handleReplyPost = ({ content }: { content: string }) => { commands .publishTextNote({ relayUrls: config().relayUrls, pubkey: pubkey(), content, notifyPubkeys: [props.event.pubkey, ...replyingToPubKeys()], replyEventId: props.event.id, }) .then(() => { setShowReplyForm(false); }); }; const handleRepost: JSX.EventHandler = (ev) => { ev.preventDefault(); commands .publishDeprecatedRepost({ relayUrls: config().relayUrls, pubkey: pubkey(), eventId: props.event.id, notifyPubkey: props.event.pubkey, }) .then(() => invalidateDeprecatedReposts()); }; const handleReaction: JSX.EventHandler = (ev) => { if (isReactedByMe()) { // TODO remove reaction return; } ev.preventDefault(); commands .publishReaction({ relayUrls: config().relayUrls, pubkey: pubkey(), content: '+', eventId: props.event.id, notifyPubkey: props.event.pubkey, }) .then(() => invalidateReactions()); }; return (
{/* TODO 画像は脆弱性回避のためにimgじゃない方法で読み込みたい */} icon
{/* TODO link to author */} 0}>
{author()?.display_name}
@{author()?.name}
{createdAt()}
0}>
{'Replying to '} {(replyToPubkey: string) => ( )}
0}>
{reposts().length}
0}>
{reactions().length}
setShowReplyForm(false)} />
); }; export default TextNote;