feat: infinite scroll

This commit is contained in:
Kieran 2023-01-17 17:13:22 +00:00
parent 02148fd97a
commit e7d24b8c7d
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
4 changed files with 37 additions and 15 deletions

13
src/element/LoadMore.tsx Normal file
View File

@ -0,0 +1,13 @@
import { useEffect } from "react";
import { useInView } from "react-intersection-observer";
export default function LoadMore({ onLoadMore }: { onLoadMore: () => void }) {
const { ref, inView } = useInView();
useEffect(() => {
if (inView === true) {
onLoadMore();
}
}, [inView]);
return <div ref={ref} className="mb10">Loading...</div>;
}

View File

@ -1,5 +1,5 @@
import "./Note.css";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useCallback, useMemo } from "react";
import { useNavigate } from "react-router-dom";
import { default as NEvent } from "../nostr/Event";
@ -33,8 +33,7 @@ export default function Note(props: NoteProps) {
const pubKeys = useMemo(() => ev.Thread?.PubKeys || [], [ev]);
const users = useProfile(pubKeys);
const deletions = useMemo(() => getReactions(related, ev.Id, EventKind.Deletion), [related]);
const { ref, inView } = useInView();
const [visible, setVisible] = useState(false);
const { ref, inView } = useInView({triggerOnce: true});
const options = {
showHeader: true,
@ -51,12 +50,6 @@ export default function Note(props: NoteProps) {
return <Text content={body} tags={ev.Tags} users={users || new Map()} />;
}, [ev]);
useEffect(() => {
if (inView && !visible) {
setVisible(true);
}
}, [inView]);
function goToEvent(e: any, id: u256) {
if (!window.location.pathname.startsWith("/e/")) {
e.stopPropagation();
@ -102,7 +95,7 @@ export default function Note(props: NoteProps) {
}
function content() {
if (!visible) return null;
if (!inView) return null;
return (
<>
{options.showHeader ?

View File

@ -2,6 +2,7 @@ import { useMemo } from "react";
import useTimelineFeed from "../feed/TimelineFeed";
import { HexKey, TaggedRawEvent, u256 } from "../nostr";
import EventKind from "../nostr/EventKind";
import LoadMore from "./LoadMore";
import Note from "./Note";
import NoteReaction from "./NoteReaction";
@ -15,7 +16,7 @@ export interface TimelineProps {
* A list of notes by pubkeys
*/
export default function Timeline({ global, pubkeys, postsOnly = false }: TimelineProps) {
const { main, others } = useTimelineFeed(pubkeys, global);
const { main, others, loadMore } = useTimelineFeed(pubkeys, global);
const mainFeed = useMemo(() => {
return main?.sort((a, b) => b.created_at - a.created_at)?.filter(a => postsOnly ? !a.tags.some(b => b[0] === "e") : true);
@ -33,5 +34,10 @@ export default function Timeline({ global, pubkeys, postsOnly = false }: Timelin
}
}
return <>{mainFeed.map(eventElement)}</>;
return (
<>
{mainFeed.map(eventElement)}
{mainFeed.length > 0 ? <LoadMore onLoadMore={loadMore} /> : null}
</>
);
}

View File

@ -5,6 +5,8 @@ import { Subscriptions } from "../nostr/Subscriptions";
import useSubscription from "./Subscription";
export default function useTimelineFeed(pubKeys: HexKey | Array<HexKey>, global: boolean = false) {
const TimeRange = 60 * 60; // 1 hr
const [since, setSince] = useState<number>(Math.floor(new Date().getTime() / 1000) - TimeRange);
const [trackingEvents, setTrackingEvent] = useState<u256[]>([]);
const subTab = global ? "global" : "follows";
@ -21,10 +23,11 @@ export default function useTimelineFeed(pubKeys: HexKey | Array<HexKey>, global:
sub.Id = `timeline:${subTab}`;
sub.Authors = global ? undefined : new Set(pubKeys);
sub.Kinds = new Set([EventKind.TextNote, EventKind.Repost]);
sub.Limit = 20;
sub.Since = since;
sub.Until = since + TimeRange;
return sub;
}, [pubKeys, global]);
}, [pubKeys, global, since]);
const main = useSubscription(sub, { leaveOpen: true });
@ -54,5 +57,12 @@ export default function useTimelineFeed(pubKeys: HexKey | Array<HexKey>, global:
return () => clearTimeout(t);
}
}, [main.notes]);
return { main: main.notes, others: others.notes };
return {
main: main.notes,
others: others.notes,
loadMore: () => {
setSince(s => s - TimeRange);
}
};
}