NoteHeader, NoteInner -> Note
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Martti Malmi 2024-01-08 14:06:24 +02:00
parent 2fbe90a39e
commit 84bda4e33d
5 changed files with 131 additions and 106 deletions

View File

@ -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>;

View File

@ -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} />
</>
);
}

View 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>
);
}

View File

@ -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 (

View File

@ -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}
/>