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 ProfilePreview from "@/Components/User/ProfilePreview";
|
||||||
|
|
||||||
import { LongFormText } from "./LongFormText";
|
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 {
|
export interface NoteProps {
|
||||||
data: TaggedNostrEvent;
|
data: TaggedNostrEvent;
|
||||||
@ -25,28 +44,11 @@ export interface NoteProps {
|
|||||||
searchedValue?: string;
|
searchedValue?: string;
|
||||||
threadChains?: Map<string, Array<NostrEvent>>;
|
threadChains?: Map<string, Array<NostrEvent>>;
|
||||||
context?: ReactNode;
|
context?: ReactNode;
|
||||||
options?: {
|
options?: 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;
|
|
||||||
};
|
|
||||||
waitUntilInView?: boolean;
|
waitUntilInView?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(function Note(props: NoteProps) {
|
export default memo(function EventComponent(props: NoteProps) {
|
||||||
const { data: ev, className } = props;
|
const { data: ev, className } = props;
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
@ -84,7 +86,7 @@ export default memo(function Note(props: NoteProps) {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
content = <NoteInner {...props} />;
|
content = <Note {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ErrorBoundary>{content}</ErrorBoundary>;
|
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 classNames from "classnames";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useInView } from "react-intersection-observer";
|
import { useInView } from "react-intersection-observer";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
import NoteHeader from "@/Components/Event/Note/NoteHeader";
|
||||||
import { NoteText } from "@/Components/Event/Note/NoteText";
|
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 useModeration from "@/Hooks/useModeration";
|
||||||
import { chainKey } from "@/Hooks/useThreadContext";
|
import { chainKey } from "@/Hooks/useThreadContext";
|
||||||
import { findTag } from "@/Utils";
|
import { findTag } from "@/Utils";
|
||||||
import { setBookmarked, setPinned } from "@/Utils/Login";
|
|
||||||
|
|
||||||
import messages from "../../messages";
|
import messages from "../../messages";
|
||||||
import Text from "../../Text/Text";
|
import Text from "../../Text/Text";
|
||||||
import ProfileImage from "../../User/ProfileImage";
|
|
||||||
import { NoteProps } from "../EventComponent";
|
import { NoteProps } from "../EventComponent";
|
||||||
import HiddenNote from "../HiddenNote";
|
import HiddenNote from "../HiddenNote";
|
||||||
import Poll from "../Poll";
|
import Poll from "../Poll";
|
||||||
import { NoteContextMenu, NoteTranslation } from "./NoteContextMenu";
|
import { NoteTranslation } from "./NoteContextMenu";
|
||||||
import NoteFooter from "./NoteFooter";
|
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 { 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 baseClassName = classNames("note min-h-[110px] flex flex-col gap-4 card", className);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [showReactions, setShowReactions] = useState(false);
|
|
||||||
|
|
||||||
const { isEventMuted } = useModeration();
|
const { isEventMuted } = useModeration();
|
||||||
const { ref, inView } = useInView({ triggerOnce: true, rootMargin: "2000px" });
|
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 [showTranslation, setShowTranslation] = useState(true);
|
||||||
const [translated, setTranslated] = useState<NoteTranslation>();
|
const [translated, setTranslated] = useState<NoteTranslation>();
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
showHeader: true,
|
showHeader: true,
|
||||||
@ -52,28 +40,6 @@ export function NoteInner(props: NoteProps) {
|
|||||||
...opt,
|
...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) {
|
function goToEvent(e: React.MouseEvent, eTarget: TaggedNostrEvent) {
|
||||||
if (opt?.canClick === false) {
|
if (opt?.canClick === false) {
|
||||||
return;
|
return;
|
||||||
@ -163,52 +129,13 @@ export function NoteInner(props: NoteProps) {
|
|||||||
if (waitUntilInView && !inView) return undefined;
|
if (waitUntilInView && !inView) return undefined;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{options.showHeader && (
|
{options.showHeader && <NoteHeader ev={ev} options={options} setTranslated={setTranslated} />}
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
<div className="body" onClick={e => goToEvent(e, ev, true)}>
|
<div className="body" onClick={e => goToEvent(e, ev, true)}>
|
||||||
<NoteText {...props} translated={translated} showTranslation={showTranslation} login={login} />
|
<NoteText {...props} translated={translated} showTranslation={showTranslation} />
|
||||||
{translation()}
|
{translation()}
|
||||||
{pollOptions()}
|
{pollOptions()}
|
||||||
</div>
|
</div>
|
||||||
{options.showFooter && <NoteFooter ev={ev} replies={props.threadChains?.get(chainKey(ev))?.length} />}
|
{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 { NoteTranslation } from "@/Components/Event/Note/NoteContextMenu";
|
||||||
import Reveal from "@/Components/Event/Reveal";
|
import Reveal from "@/Components/Event/Reveal";
|
||||||
import Text from "@/Components/Text/Text";
|
import Text from "@/Components/Text/Text";
|
||||||
import { LoginSession } from "@/Utils/Login";
|
import useLogin from "@/Hooks/useLogin";
|
||||||
|
|
||||||
const TEXT_TRUNCATE_LENGTH = 400;
|
const TEXT_TRUNCATE_LENGTH = 400;
|
||||||
export const NoteText = function InnerContent(
|
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 [showMore, setShowMore] = useState(false);
|
||||||
const body = translated && showTranslation ? translated.text : ev?.content ?? "";
|
const body = translated && showTranslation ? translated.text : ev?.content ?? "";
|
||||||
const id = translated && showTranslation ? `${ev.id}-translated` : ev.id;
|
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");
|
const contentWarning = ev.tags.find(a => a[0] === "content-warning");
|
||||||
if (contentWarning) {
|
if (contentWarning) {
|
||||||
return (
|
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 isSingleNote = thread.chains?.size === 1 && [thread.chains.values].every(v => v.length === 0);
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
const rootOptions = useMemo(
|
||||||
|
() => ({ showReactionsLink: true, showMediaSpotlight: !props.disableSpotlight, isRoot: true }),
|
||||||
|
[props.disableSpotlight],
|
||||||
|
);
|
||||||
|
|
||||||
function navigateThread(e: TaggedNostrEvent) {
|
function navigateThread(e: TaggedNostrEvent) {
|
||||||
thread.setCurrent(e.id);
|
thread.setCurrent(e.id);
|
||||||
//router.navigate(`/${NostrLink.fromEvent(e).encode()}`, { replace: true })
|
//router.navigate(`/${NostrLink.fromEvent(e).encode()}`, { replace: true })
|
||||||
@ -252,7 +257,7 @@ export function Thread(props: { onBack?: () => void; disableSpotlight?: boolean
|
|||||||
className={className}
|
className={className}
|
||||||
key={note.id}
|
key={note.id}
|
||||||
data={note}
|
data={note}
|
||||||
options={{ showReactionsLink: true, showMediaSpotlight: !props.disableSpotlight, isRoot: true }}
|
options={rootOptions}
|
||||||
onClick={navigateThread}
|
onClick={navigateThread}
|
||||||
threadChains={thread.chains}
|
threadChains={thread.chains}
|
||||||
/>
|
/>
|
||||||
|
Loading…
Reference in New Issue
Block a user