tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
a tool for shared writing and social publishing
284
fork
atom
overview
issues
26
pulls
pipelines
adding tabs to Reader
cozylittle.house
3 days ago
cbae01a9
e57c816d
+12
-116
2 changed files
expand all
collapse all
unified
split
app
(home-pages)
reader
ReaderContent.tsx
page.tsx
-100
app/(home-pages)/reader/ReaderContent.tsx
···
1
-
"use client";
2
-
import { ButtonPrimary } from "components/Buttons";
3
-
import { DiscoverSmall } from "components/Icons/DiscoverSmall";
4
-
import type { Cursor, Post } from "./getReaderFeed";
5
-
import useSWRInfinite from "swr/infinite";
6
-
import { getReaderFeed } from "./getReaderFeed";
7
-
import { useEffect, useRef } from "react";
8
-
import Link from "next/link";
9
-
import { PostListing } from "components/PostListing";
10
-
11
-
export const ReaderContent = (props: {
12
-
posts: Post[];
13
-
nextCursor: Cursor | null;
14
-
}) => {
15
-
const getKey = (
16
-
pageIndex: number,
17
-
previousPageData: {
18
-
posts: Post[];
19
-
nextCursor: Cursor | null;
20
-
} | null,
21
-
) => {
22
-
// Reached the end
23
-
if (previousPageData && !previousPageData.nextCursor) return null;
24
-
25
-
// First page, we don't have previousPageData
26
-
if (pageIndex === 0) return ["reader-feed", null] as const;
27
-
28
-
// Add the cursor to the key
29
-
return ["reader-feed", previousPageData?.nextCursor] as const;
30
-
};
31
-
32
-
const { data, size, setSize, isValidating } = useSWRInfinite(
33
-
getKey,
34
-
([_, cursor]) => getReaderFeed(cursor),
35
-
{
36
-
fallbackData: [{ posts: props.posts, nextCursor: props.nextCursor }],
37
-
revalidateFirstPage: false,
38
-
},
39
-
);
40
-
41
-
const loadMoreRef = useRef<HTMLDivElement>(null);
42
-
43
-
// Set up intersection observer to load more when trigger element is visible
44
-
useEffect(() => {
45
-
const observer = new IntersectionObserver(
46
-
(entries) => {
47
-
if (entries[0].isIntersecting && !isValidating) {
48
-
const hasMore = data && data[data.length - 1]?.nextCursor;
49
-
if (hasMore) {
50
-
setSize(size + 1);
51
-
}
52
-
}
53
-
},
54
-
{ threshold: 0.1 },
55
-
);
56
-
57
-
if (loadMoreRef.current) {
58
-
observer.observe(loadMoreRef.current);
59
-
}
60
-
61
-
return () => observer.disconnect();
62
-
}, [data, size, setSize, isValidating]);
63
-
64
-
const allPosts = data ? data.flatMap((page) => page.posts) : [];
65
-
66
-
if (allPosts.length === 0 && !isValidating) return <ReaderEmpty />;
67
-
68
-
return (
69
-
<div className="flex flex-col gap-3 relative">
70
-
{allPosts.map((p) => (
71
-
<PostListing {...p} key={p.documents.uri} />
72
-
))}
73
-
{/* Trigger element for loading more posts */}
74
-
<div
75
-
ref={loadMoreRef}
76
-
className="absolute bottom-96 left-0 w-full h-px pointer-events-none"
77
-
aria-hidden="true"
78
-
/>
79
-
{isValidating && (
80
-
<div className="text-center text-tertiary py-4">
81
-
Loading more posts...
82
-
</div>
83
-
)}
84
-
</div>
85
-
);
86
-
};
87
-
88
-
export const ReaderEmpty = () => {
89
-
return (
90
-
<div className="flex flex-col gap-2 container bg-[rgba(var(--bg-page),.7)] sm:p-4 p-3 justify-between text-center text-tertiary">
91
-
Nothing to read yet… <br />
92
-
Subscribe to publications and find their posts here!
93
-
<Link href={"/discover"}>
94
-
<ButtonPrimary className="mx-auto place-self-center">
95
-
<DiscoverSmall /> Discover Publications
96
-
</ButtonPrimary>
97
-
</Link>
98
-
</div>
99
-
);
100
-
};
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
+12
-16
app/(home-pages)/reader/page.tsx
···
1
-
import { getIdentityData } from "actions/getIdentityData";
2
-
3
import { DashboardLayout } from "components/PageLayouts/DashboardLayout";
4
-
import { ReaderContent } from "./ReaderContent";
5
-
import { SubscriptionsContent } from "./SubscriptionsContent";
0
6
import { getReaderFeed } from "./getReaderFeed";
7
-
import { getSubscriptions } from "./getSubscriptions";
8
9
export default async function Reader(props: {}) {
10
let posts = await getReaderFeed();
11
-
let publications = await getSubscriptions();
12
return (
13
<DashboardLayout
14
id="reader"
15
currentPage="reader"
16
-
defaultTab="Read"
17
actions={null}
18
tabs={{
19
-
Read: {
20
controls: null,
21
content: (
22
-
<ReaderContent nextCursor={posts.nextCursor} posts={posts.posts} />
23
),
24
},
25
-
Subscriptions: {
26
controls: null,
27
-
content: (
28
-
<SubscriptionsContent
29
-
publications={publications.subscriptions}
30
-
nextCursor={publications.nextCursor}
31
-
/>
32
-
),
33
},
34
}}
35
/>
···
0
0
1
import { DashboardLayout } from "components/PageLayouts/DashboardLayout";
2
+
import { InboxContent } from "./InboxContent";
3
+
import { LocalContent } from "./LocalContent";
4
+
import { GlobalContent } from "./GlobalContent";
5
import { getReaderFeed } from "./getReaderFeed";
0
6
7
export default async function Reader(props: {}) {
8
let posts = await getReaderFeed();
0
9
return (
10
<DashboardLayout
11
id="reader"
12
currentPage="reader"
13
+
defaultTab="Inbox"
14
actions={null}
15
tabs={{
16
+
Inbox: {
17
controls: null,
18
content: (
19
+
<InboxContent nextCursor={posts.nextCursor} posts={posts.posts} />
20
),
21
},
22
+
Friends: {
23
controls: null,
24
+
content: <LocalContent />,
25
+
},
26
+
Global: {
27
+
controls: null,
28
+
content: <GlobalContent />,
0
29
},
30
}}
31
/>