tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
289
fork
atom
a tool for shared writing and social publishing
289
fork
atom
overview
issues
28
pulls
pipelines
use compound cursors and fix subscriptions pagination
awarm.space
5 months ago
947bc742
491f1f8c
+44
-20
4 changed files
expand all
collapse all
unified
split
app
reader
ReaderContent.tsx
SubscriptionsContent.tsx
getReaderFeed.ts
getSubscriptions.ts
+5
-5
app/reader/ReaderContent.tsx
···
14
14
import { PubLeafletDocument, PubLeafletPublication } from "lexicons/api";
15
15
import { blobRefToSrc } from "src/utils/blobRefToSrc";
16
16
import { Json } from "supabase/database.types";
17
17
-
import type { Post } from "./getReaderFeed";
17
17
+
import type { Cursor, Post } from "./getReaderFeed";
18
18
import useSWRInfinite from "swr/infinite";
19
19
import { getReaderFeed } from "./getReaderFeed";
20
20
import { useEffect, useRef } from "react";
···
23
23
export const ReaderContent = (props: {
24
24
root_entity: string;
25
25
posts: Post[];
26
26
-
nextCursor: string | null;
26
26
+
nextCursor: Cursor | null;
27
27
}) => {
28
28
const getKey = (
29
29
pageIndex: number,
30
30
-
previousPageData: { posts: Post[]; nextCursor: string | null } | null,
30
30
+
previousPageData: { posts: Post[]; nextCursor: Cursor | null } | null,
31
31
) => {
32
32
// Reached the end
33
33
if (previousPageData && !previousPageData.nextCursor) return null;
34
34
35
35
// First page, we don't have previousPageData
36
36
-
if (pageIndex === 0) return ["reader-feed", null];
36
36
+
if (pageIndex === 0) return ["reader-feed", null] as const;
37
37
38
38
// Add the cursor to the key
39
39
-
return ["reader-feed", previousPageData?.nextCursor];
39
39
+
return ["reader-feed", previousPageData?.nextCursor] as const;
40
40
};
41
41
42
42
const { data, error, size, setSize, isValidating } = useSWRInfinite(
+6
-5
app/reader/SubscriptionsContent.tsx
···
6
6
import { PublicationSubscription, getSubscriptions } from "./getSubscriptions";
7
7
import useSWRInfinite from "swr/infinite";
8
8
import { useEffect, useRef } from "react";
9
9
+
import { Cursor } from "./getReaderFeed";
9
10
10
11
export const SubscriptionsContent = (props: {
11
12
publications: PublicationSubscription[];
12
12
-
nextCursor: string | null;
13
13
+
nextCursor: Cursor | null;
13
14
}) => {
14
15
const getKey = (
15
16
pageIndex: number,
16
17
previousPageData: {
17
18
subscriptions: PublicationSubscription[];
18
18
-
nextCursor: string | null;
19
19
+
nextCursor: Cursor | null;
19
20
} | null,
20
21
) => {
21
22
// Reached the end
22
23
if (previousPageData && !previousPageData.nextCursor) return null;
23
24
24
25
// First page, we don't have previousPageData
25
25
-
if (pageIndex === 0) return ["subscriptions", null];
26
26
+
if (pageIndex === 0) return ["subscriptions", null] as const;
26
27
27
28
// Add the cursor to the key
28
28
-
return ["subscriptions", previousPageData?.nextCursor];
29
29
+
return ["subscriptions", previousPageData?.nextCursor] as const;
29
30
};
30
31
31
32
const { data, error, size, setSize, isValidating } = useSWRInfinite(
···
72
73
return (
73
74
<div className="relative">
74
75
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 gap-3">
75
75
-
{allPublications?.map((p) => <PubListing key={p.uri} {...p} />)}
76
76
+
{allPublications?.map((p, index) => <PubListing key={p.uri} {...p} />)}
76
77
</div>
77
78
{/* Trigger element for loading more subscriptions */}
78
79
<div
+20
-4
app/reader/getReaderFeed.ts
···
10
10
import { Json } from "supabase/database.types";
11
11
import { idResolver } from "./idResolver";
12
12
13
13
+
export type Cursor = {
14
14
+
timestamp: string;
15
15
+
uri: string;
16
16
+
};
17
17
+
13
18
export async function getReaderFeed(
14
14
-
cursor?: string | null,
15
15
-
): Promise<{ posts: Post[]; nextCursor: string | null }> {
19
19
+
cursor?: Cursor | null,
20
20
+
): Promise<{ posts: Post[]; nextCursor: Cursor | null }> {
16
21
let auth_res = await getIdentityData();
17
22
if (!auth_res?.atp_did) return { posts: [], nextCursor: null };
18
23
let query = supabaseServerClient
···
28
33
auth_res.atp_did,
29
34
)
30
35
.order("indexed_at", { ascending: false })
36
36
+
.order("uri", { ascending: false })
31
37
.limit(25);
32
32
-
if (cursor) query.lt("indexed_at", cursor);
38
38
+
if (cursor) {
39
39
+
query = query.lt("indexed_at", cursor.timestamp).lte("uri", cursor.uri);
40
40
+
}
33
41
let { data: feed, error } = await query;
34
42
35
43
let posts = await Promise.all(
···
55
63
return p;
56
64
}) || [],
57
65
);
66
66
+
const nextCursor =
67
67
+
posts.length > 0
68
68
+
? {
69
69
+
timestamp: posts[posts.length - 1].documents.indexed_at,
70
70
+
uri: posts[posts.length - 1].documents.uri,
71
71
+
}
72
72
+
: null;
73
73
+
58
74
return {
59
75
posts,
60
60
-
nextCursor: posts[posts.length - 1]?.documents.indexed_at || null,
76
76
+
nextCursor,
61
77
};
62
78
}
63
79
+13
-6
app/reader/getSubscriptions.ts
···
6
6
import { Json } from "supabase/database.types";
7
7
import { supabaseServerClient } from "supabase/serverClient";
8
8
import { idResolver } from "./idResolver";
9
9
+
import { Cursor } from "./getReaderFeed";
9
10
10
10
-
export async function getSubscriptions(cursor?: string | null): Promise<{
11
11
-
nextCursor: null | string;
11
11
+
export async function getSubscriptions(cursor?: Cursor | null): Promise<{
12
12
+
nextCursor: null | Cursor;
12
13
subscriptions: PublicationSubscription[];
13
14
}> {
14
15
let auth_res = await getIdentityData();
15
16
if (!auth_res?.atp_did) return { subscriptions: [], nextCursor: null };
16
17
let query = supabaseServerClient
17
18
.from("publication_subscriptions")
18
18
-
.select(`publications(*, documents_in_publications(*, documents(*)))`)
19
19
+
.select(`*, publications(*, documents_in_publications(*, documents(*)))`)
19
20
.order(`created_at`, { ascending: false })
21
21
+
.order(`uri`, { ascending: false })
20
22
.order("indexed_at", {
21
23
referencedTable: "publications.documents_in_publications",
22
24
})
···
24
26
.limit(25)
25
27
.eq("identity", auth_res.atp_did);
26
28
27
27
-
if (cursor) query.lt("indexed_at", cursor);
29
29
+
if (cursor) {
30
30
+
query = query.lt("created_at", cursor.timestamp).lte("uri", cursor.uri);
31
31
+
}
28
32
let { data: pubs, error } = await query;
33
33
+
console.log(cursor);
29
34
30
35
const actors: string[] = [
31
36
...new Set(
···
46
51
47
52
const nextCursor =
48
53
pubs && pubs.length > 0
49
49
-
? pubs[pubs.length - 1].publications?.documents_in_publications?.[0]
50
50
-
?.indexed_at || null
54
54
+
? {
55
55
+
timestamp: pubs[pubs.length - 1].created_at,
56
56
+
uri: pubs[pubs.length - 1].uri,
57
57
+
}
51
58
: null;
52
59
53
60
return {