diff --git a/packages/app/src/Components/Event/EventComponent.tsx b/packages/app/src/Components/Event/EventComponent.tsx index 216a7957..ba1159b0 100644 --- a/packages/app/src/Components/Event/EventComponent.tsx +++ b/packages/app/src/Components/Event/EventComponent.tsx @@ -13,7 +13,26 @@ import { LiveEvent } from "@/Components/LiveStream/LiveEvent"; import ProfilePreview from "@/Components/User/ProfilePreview"; import { LongFormText } from "./LongFormText"; -import { NoteInner } from "./Note/NoteInner"; +import { Note } from "./Note/Note"; + +export interface NotePropsOptions { + isRoot?: boolean; + showHeader?: boolean; + showContextMenu?: boolean; + showProfileCard?: boolean; + showTime?: boolean; + showPinned?: boolean; + showBookmarked?: boolean; + showFooter?: boolean; + showReactionsLink?: boolean; + showMedia?: boolean; + canUnpin?: boolean; + canUnbookmark?: boolean; + canClick?: boolean; + showMediaSpotlight?: boolean; + longFormPreview?: boolean; + truncate?: boolean; +} export interface NoteProps { data: TaggedNostrEvent; @@ -25,28 +44,11 @@ export interface NoteProps { searchedValue?: string; threadChains?: Map>; context?: ReactNode; - options?: { - isRoot?: boolean; - showHeader?: boolean; - showContextMenu?: boolean; - showProfileCard?: boolean; - showTime?: boolean; - showPinned?: boolean; - showBookmarked?: boolean; - showFooter?: boolean; - showReactionsLink?: boolean; - showMedia?: boolean; - canUnpin?: boolean; - canUnbookmark?: boolean; - canClick?: boolean; - showMediaSpotlight?: boolean; - longFormPreview?: boolean; - truncate?: boolean; - }; + options?: NotePropsOptions; waitUntilInView?: boolean; } -export default memo(function Note(props: NoteProps) { +export default memo(function EventComponent(props: NoteProps) { const { data: ev, className } = props; let content; @@ -84,7 +86,7 @@ export default memo(function Note(props: NoteProps) { ); break; default: - content = ; + content = ; } return {content}; diff --git a/packages/app/src/Components/Event/Note/NoteInner.tsx b/packages/app/src/Components/Event/Note/Note.tsx similarity index 55% rename from packages/app/src/Components/Event/Note/NoteInner.tsx rename to packages/app/src/Components/Event/Note/Note.tsx index 4d103ab9..b6e6ffb2 100644 --- a/packages/app/src/Components/Event/Note/NoteInner.tsx +++ b/packages/app/src/Components/Event/Note/Note.tsx @@ -1,46 +1,34 @@ -import { EventKind, HexKey, NostrLink, NostrPrefix, TaggedNostrEvent } from "@snort/system"; +import { EventKind, NostrLink, TaggedNostrEvent } from "@snort/system"; import classNames from "classnames"; import React, { useState } from "react"; import { useInView } from "react-intersection-observer"; -import { FormattedMessage, useIntl } from "react-intl"; +import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router-dom"; +import NoteHeader from "@/Components/Event/Note/NoteHeader"; import { NoteText } from "@/Components/Event/Note/NoteText"; -import ReplyTag from "@/Components/Event/Note/ReplyTag"; -import Icon from "@/Components/Icons/Icon"; -import useEventPublisher from "@/Hooks/useEventPublisher"; -import useLogin from "@/Hooks/useLogin"; import useModeration from "@/Hooks/useModeration"; import { chainKey } from "@/Hooks/useThreadContext"; import { findTag } from "@/Utils"; -import { setBookmarked, setPinned } from "@/Utils/Login"; import messages from "../../messages"; import Text from "../../Text/Text"; -import ProfileImage from "../../User/ProfileImage"; import { NoteProps } from "../EventComponent"; import HiddenNote from "../HiddenNote"; import Poll from "../Poll"; -import { NoteContextMenu, NoteTranslation } from "./NoteContextMenu"; +import { NoteTranslation } from "./NoteContextMenu"; import NoteFooter from "./NoteFooter"; -import NoteTime from "./NoteTime"; -import ReactionsModal from "./ReactionsModal"; -export function NoteInner(props: NoteProps) { +export function Note(props: NoteProps) { const { data: ev, highlight, options: opt, ignoreModeration = false, className, waitUntilInView } = props; const baseClassName = classNames("note min-h-[110px] flex flex-col gap-4 card", className); const navigate = useNavigate(); - const [showReactions, setShowReactions] = useState(false); const { isEventMuted } = useModeration(); const { ref, inView } = useInView({ triggerOnce: true, rootMargin: "2000px" }); - const login = useLogin(); - const { pinned, bookmarked } = useLogin(); - const { publisher, system } = useEventPublisher(); const [showTranslation, setShowTranslation] = useState(true); const [translated, setTranslated] = useState(); - const { formatMessage } = useIntl(); const options = { showHeader: true, @@ -52,28 +40,6 @@ export function NoteInner(props: NoteProps) { ...opt, }; - async function unpin(id: HexKey) { - if (options.canUnpin && publisher) { - if (window.confirm(formatMessage(messages.ConfirmUnpin))) { - const es = pinned.item.filter(e => e !== id); - const ev = await publisher.pinned(es.map(a => new NostrLink(NostrPrefix.Note, a))); - system.BroadcastEvent(ev); - setPinned(login, es, ev.created_at * 1000); - } - } - } - - async function unbookmark(id: HexKey) { - if (options.canUnbookmark && publisher) { - if (window.confirm(formatMessage(messages.ConfirmUnbookmark))) { - const es = bookmarked.item.filter(e => e !== id); - const ev = await publisher.pinned(es.map(a => new NostrLink(NostrPrefix.Note, a))); - system.BroadcastEvent(ev); - setBookmarked(login, es, ev.created_at * 1000); - } - } - } - function goToEvent(e: React.MouseEvent, eTarget: TaggedNostrEvent) { if (opt?.canClick === false) { return; @@ -163,52 +129,13 @@ export function NoteInner(props: NoteProps) { if (waitUntilInView && !inView) return undefined; return ( <> - {options.showHeader && ( -
- } - link={opt?.canClick === undefined ? undefined : ""} - showProfileCard={options.showProfileCard ?? true} - showBadges={true} - /> -
- {props.context} - {(options.showTime || options.showBookmarked) && ( - <> - {options.showBookmarked && ( -
unbookmark(ev.id)}> - -
- )} - {!options.showBookmarked && } - - )} - {options.showPinned && ( -
unpin(ev.id)}> - -
- )} - {options.showContextMenu && ( - {}} - onTranslated={t => setTranslated(t)} - setShowReactions={setShowReactions} - /> - )} -
-
- )} + {options.showHeader && }
goToEvent(e, ev, true)}> - + {translation()} {pollOptions()}
{options.showFooter && } - ); } diff --git a/packages/app/src/Components/Event/Note/NoteHeader.tsx b/packages/app/src/Components/Event/Note/NoteHeader.tsx new file mode 100644 index 00000000..888267b0 --- /dev/null +++ b/packages/app/src/Components/Event/Note/NoteHeader.tsx @@ -0,0 +1,90 @@ +import { HexKey, NostrLink, NostrPrefix, TaggedNostrEvent } from "@snort/system"; +import React, { useState } from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import { NotePropsOptions } from "@/Components/Event/EventComponent"; +import { NoteContextMenu, NoteTranslation } from "@/Components/Event/Note/NoteContextMenu"; +import NoteTime from "@/Components/Event/Note/NoteTime"; +import ReactionsModal from "@/Components/Event/Note/ReactionsModal"; +import ReplyTag from "@/Components/Event/Note/ReplyTag"; +import Icon from "@/Components/Icons/Icon"; +import messages from "@/Components/messages"; +import ProfileImage from "@/Components/User/ProfileImage"; +import useEventPublisher from "@/Hooks/useEventPublisher"; +import useLogin from "@/Hooks/useLogin"; +import { setBookmarked, setPinned } from "@/Utils/Login"; + +export default function NoteHeader(props: { + ev: TaggedNostrEvent; + options: NotePropsOptions; + setTranslated: (t: NoteTranslation) => void; + context?: React.ReactNode; +}) { + const [showReactions, setShowReactions] = useState(false); + const { ev, options, setTranslated } = props; + const { formatMessage } = useIntl(); + const { pinned, bookmarked } = useLogin(); + const { publisher, system } = useEventPublisher(); + const login = useLogin(); + + async function unpin(id: HexKey) { + if (options.canUnpin && publisher) { + if (window.confirm(formatMessage(messages.ConfirmUnpin))) { + const es = pinned.item.filter(e => e !== id); + const ev = await publisher.pinned(es.map(a => new NostrLink(NostrPrefix.Note, a))); + system.BroadcastEvent(ev); + setPinned(login, es, ev.created_at * 1000); + } + } + } + + async function unbookmark(id: HexKey) { + if (options.canUnbookmark && publisher) { + if (window.confirm(formatMessage(messages.ConfirmUnbookmark))) { + const es = bookmarked.item.filter(e => e !== id); + const ev = await publisher.pinned(es.map(a => new NostrLink(NostrPrefix.Note, a))); + system.BroadcastEvent(ev); + setBookmarked(login, es, ev.created_at * 1000); + } + } + } + + return ( +
+ } + link={options.canClick === undefined ? undefined : ""} + showProfileCard={options.showProfileCard ?? true} + showBadges={true} + /> +
+ {props.context} + {(options.showTime || options.showBookmarked) && ( + <> + {options.showBookmarked && ( +
unbookmark(ev.id)}> + +
+ )} + {!options.showBookmarked && } + + )} + {options.showPinned && ( +
unpin(ev.id)}> + +
+ )} + {options.showContextMenu && ( + {}} + onTranslated={t => setTranslated(t)} + setShowReactions={setShowReactions} + /> + )} +
+ +
+ ); +} diff --git a/packages/app/src/Components/Event/Note/NoteText.tsx b/packages/app/src/Components/Event/Note/NoteText.tsx index d55563a4..7b0c190a 100644 --- a/packages/app/src/Components/Event/Note/NoteText.tsx +++ b/packages/app/src/Components/Event/Note/NoteText.tsx @@ -6,13 +6,14 @@ import { NoteProps } from "@/Components/Event/EventComponent"; import { NoteTranslation } from "@/Components/Event/Note/NoteContextMenu"; import Reveal from "@/Components/Event/Reveal"; import Text from "@/Components/Text/Text"; -import { LoginSession } from "@/Utils/Login"; +import useLogin from "@/Hooks/useLogin"; const TEXT_TRUNCATE_LENGTH = 400; export const NoteText = function InnerContent( - props: NoteProps & { translated: NoteTranslation; showTranslation?: boolean; login: LoginSession }, + props: NoteProps & { translated: NoteTranslation; showTranslation?: boolean }, ) { - const { data: ev, options, translated, showTranslation, login } = props; + const { data: ev, options, translated, showTranslation } = props; + const appData = useLogin(s => s.appData); const [showMore, setShowMore] = useState(false); const body = translated && showTranslation ? translated.text : ev?.content ?? ""; const id = translated && showTranslation ? `${ev.id}-translated` : ev.id; @@ -52,7 +53,7 @@ export const NoteText = function InnerContent( ); - if (!login.appData.item.showContentWarningPosts) { + if (!appData.item.showContentWarningPosts) { const contentWarning = ev.tags.find(a => a[0] === "content-warning"); if (contentWarning) { return ( diff --git a/packages/app/src/Components/Event/Thread.tsx b/packages/app/src/Components/Event/Thread.tsx index cf75c4e7..6a190383 100644 --- a/packages/app/src/Components/Event/Thread.tsx +++ b/packages/app/src/Components/Event/Thread.tsx @@ -228,6 +228,11 @@ export function Thread(props: { onBack?: () => void; disableSpotlight?: boolean const isSingleNote = thread.chains?.size === 1 && [thread.chains.values].every(v => v.length === 0); const { formatMessage } = useIntl(); + const rootOptions = useMemo( + () => ({ showReactionsLink: true, showMediaSpotlight: !props.disableSpotlight, isRoot: true }), + [props.disableSpotlight], + ); + function navigateThread(e: TaggedNostrEvent) { thread.setCurrent(e.id); //router.navigate(`/${NostrLink.fromEvent(e).encode()}`, { replace: true }) @@ -252,7 +257,7 @@ export function Thread(props: { onBack?: () => void; disableSpotlight?: boolean className={className} key={note.id} data={note} - options={{ showReactionsLink: true, showMediaSpotlight: !props.disableSpotlight, isRoot: true }} + options={rootOptions} onClick={navigateThread} threadChains={thread.chains} />