forked from Kieran/snort
NoteHeader, NoteInner -> Note
This commit is contained in:
parent
2fbe90a39e
commit
84bda4e33d
@ -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<string, Array<NostrEvent>>;
|
||||
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 = <NoteInner {...props} />;
|
||||
content = <Note {...props} />;
|
||||
}
|
||||
|
||||
return <ErrorBoundary>{content}</ErrorBoundary>;
|
||||
|
@ -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<NoteTranslation>();
|
||||
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 && (
|
||||
<div className="header flex">
|
||||
<ProfileImage
|
||||
pubkey={ev.pubkey}
|
||||
subHeader={<ReplyTag ev={ev} />}
|
||||
link={opt?.canClick === undefined ? undefined : ""}
|
||||
showProfileCard={options.showProfileCard ?? true}
|
||||
showBadges={true}
|
||||
/>
|
||||
<div className="info">
|
||||
{props.context}
|
||||
{(options.showTime || options.showBookmarked) && (
|
||||
<>
|
||||
{options.showBookmarked && (
|
||||
<div
|
||||
className={`saved ${options.canUnbookmark ? "pointer" : ""}`}
|
||||
onClick={() => unbookmark(ev.id)}>
|
||||
<Icon name="bookmark" /> <FormattedMessage {...messages.Bookmarked} />
|
||||
</div>
|
||||
)}
|
||||
{!options.showBookmarked && <NoteTime from={ev.created_at * 1000} />}
|
||||
</>
|
||||
)}
|
||||
{options.showPinned && (
|
||||
<div className={`pinned ${options.canUnpin ? "pointer" : ""}`} onClick={() => unpin(ev.id)}>
|
||||
<Icon name="pin" /> <FormattedMessage {...messages.Pinned} />
|
||||
</div>
|
||||
)}
|
||||
{options.showContextMenu && (
|
||||
<NoteContextMenu
|
||||
ev={ev}
|
||||
react={async () => {}}
|
||||
onTranslated={t => setTranslated(t)}
|
||||
setShowReactions={setShowReactions}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{options.showHeader && <NoteHeader ev={ev} options={options} setTranslated={setTranslated} />}
|
||||
<div className="body" onClick={e => goToEvent(e, ev, true)}>
|
||||
<NoteText {...props} translated={translated} showTranslation={showTranslation} login={login} />
|
||||
<NoteText {...props} translated={translated} showTranslation={showTranslation} />
|
||||
{translation()}
|
||||
{pollOptions()}
|
||||
</div>
|
||||
{options.showFooter && <NoteFooter ev={ev} replies={props.threadChains?.get(chainKey(ev))?.length} />}
|
||||
<ReactionsModal show={showReactions} setShow={setShowReactions} event={ev} />
|
||||
</>
|
||||
);
|
||||
}
|
90
packages/app/src/Components/Event/Note/NoteHeader.tsx
Normal file
90
packages/app/src/Components/Event/Note/NoteHeader.tsx
Normal file
@ -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 (
|
||||
<div className="header flex">
|
||||
<ProfileImage
|
||||
pubkey={ev.pubkey}
|
||||
subHeader={<ReplyTag ev={ev} />}
|
||||
link={options.canClick === undefined ? undefined : ""}
|
||||
showProfileCard={options.showProfileCard ?? true}
|
||||
showBadges={true}
|
||||
/>
|
||||
<div className="info">
|
||||
{props.context}
|
||||
{(options.showTime || options.showBookmarked) && (
|
||||
<>
|
||||
{options.showBookmarked && (
|
||||
<div className={`saved ${options.canUnbookmark ? "pointer" : ""}`} onClick={() => unbookmark(ev.id)}>
|
||||
<Icon name="bookmark" /> <FormattedMessage {...messages.Bookmarked} />
|
||||
</div>
|
||||
)}
|
||||
{!options.showBookmarked && <NoteTime from={ev.created_at * 1000} />}
|
||||
</>
|
||||
)}
|
||||
{options.showPinned && (
|
||||
<div className={`pinned ${options.canUnpin ? "pointer" : ""}`} onClick={() => unpin(ev.id)}>
|
||||
<Icon name="pin" /> <FormattedMessage {...messages.Pinned} />
|
||||
</div>
|
||||
)}
|
||||
{options.showContextMenu && (
|
||||
<NoteContextMenu
|
||||
ev={ev}
|
||||
react={async () => {}}
|
||||
onTranslated={t => setTranslated(t)}
|
||||
setShowReactions={setShowReactions}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<ReactionsModal show={showReactions} setShow={setShowReactions} event={ev} />
|
||||
</div>
|
||||
);
|
||||
}
|
@ -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 (
|
||||
|
@ -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}
|
||||
/>
|
||||
|
Loading…
Reference in New Issue
Block a user