Files
snort/packages/app/src/Element/Feed/TimelineFragment.tsx
2023-11-25 20:56:23 +02:00

115 lines
3.4 KiB
TypeScript

import { ReactNode, useCallback } from "react";
import { FormattedMessage } from "react-intl";
import { useInView } from "react-intersection-observer";
import { TaggedNostrEvent } from "@snort/system";
import Note from "@/Element/Event/Note";
import ProfileImage from "@/Element/User/ProfileImage";
import Icon from "@/Icons/Icon";
import { findTag } from "@/SnortUtils";
export interface TimelineFragment {
events: Array<TaggedNostrEvent>;
refTime: number;
title?: ReactNode;
}
export interface TimelineRendererProps {
frags: Array<TimelineFragment>;
related: Array<TaggedNostrEvent>;
/**
* List of pubkeys who have posted recently
*/
latest: Array<string>;
showLatest: (toTop: boolean) => void;
noteRenderer?: (ev: TaggedNostrEvent) => ReactNode;
noteOnClick?: (ev: TaggedNostrEvent) => void;
noteContext?: (ev: TaggedNostrEvent) => ReactNode;
}
export function TimelineRenderer(props: TimelineRendererProps) {
const { ref, inView } = useInView();
return (
<>
{props.latest.length > 0 && (
<>
<div className="card latest-notes" onClick={() => props.showLatest(false)} ref={ref}>
{props.latest.slice(0, 3).map(p => {
return <ProfileImage pubkey={p} showUsername={false} link={""} showFollowDistance={false} />;
})}
<FormattedMessage
defaultMessage="{n} new {n, plural, =1 {note} other {notes}}"
id="3t3kok"
values={{ n: props.latest.length }}
/>
<Icon name="arrowUp" />
</div>
{!inView && (
<div
className="card latest-notes latest-notes-fixed pointer fade-in"
onClick={() => props.showLatest(true)}>
{props.latest.slice(0, 3).map(p => {
return <ProfileImage pubkey={p} showUsername={false} link={""} showFollowDistance={false} />;
})}
<FormattedMessage
defaultMessage="{n} new {n, plural, =1 {note} other {notes}}"
id="3t3kok"
values={{ n: props.latest.length }}
/>
<Icon name="arrowUp" />
</div>
)}
</>
)}
{props.frags.map(f => (
<TimelineFragment
frag={f}
related={props.related}
noteRenderer={props.noteRenderer}
noteOnClick={props.noteOnClick}
noteContext={props.noteContext}
/>
))}
</>
);
}
export interface TimelineFragProps {
frag: TimelineFragment;
related: Array<TaggedNostrEvent>;
noteRenderer?: (ev: TaggedNostrEvent) => ReactNode;
noteOnClick?: (ev: TaggedNostrEvent) => void;
noteContext?: (ev: TaggedNostrEvent) => ReactNode;
}
export function TimelineFragment(props: TimelineFragProps) {
const relatedFeed = useCallback(
(id: string) => {
return props.related.filter(a => findTag(a, "e") === id);
},
[props.related],
);
return (
<>
{props.frag.title}
{props.frag.events.map(
e =>
props.noteRenderer?.(e) ?? (
<Note
data={e}
related={relatedFeed(e.id)}
key={e.id}
depth={0}
onClick={props.noteOnClick}
context={props.noteContext?.(e)}
options={{
truncate: true,
}}
/>
),
)}
</>
);
}