tangled
alpha
login
or
join now
isuggest.selfce.st
/
strand
3
fork
atom
alternative tangled frontend (extremely wip)
3
fork
atom
overview
issues
pulls
pipelines
feat: backlink counts
serenity
1 week ago
59aaa796
292df67c
+133
-7
2 changed files
expand all
collapse all
unified
split
src
components
Profile
PinnedRepos.tsx
lib
queries
constellation-backlink-count.ts
+57
-7
src/components/Profile/PinnedRepos.tsx
···
1
1
import { UnderlineRouterLink } from "@/components/Animated/UnderlineRouterLink";
2
2
import { Loading } from "@/components/Icons/Loading";
3
3
+
import { useBacklinkCount } from "@/lib/queries/constellation-backlink-count";
3
4
import { useMiniDoc } from "@/lib/queries/resolve-minidoc";
4
5
import { usePinnedReposQuery } from "@/lib/queries/resolve-pinned-repos";
5
6
import { ShTangledRepo } from "@/lib/types/lexicons/sh/tangled/repo";
6
6
-
import { LucideBookMarked, LucideGitBranch } from "lucide-react";
7
7
+
import {
8
8
+
LucideBookMarked,
9
9
+
LucideCircleDot,
10
10
+
LucideGitBranch,
11
11
+
LucideGitBranchPlus,
12
12
+
LucideGitPullRequest,
13
13
+
LucideStar,
14
14
+
} from "lucide-react";
7
15
8
16
export const PinnedRepos = ({
9
17
repoUris,
···
58
66
isOwner: boolean;
59
67
repoUri: string;
60
68
}) => {
61
61
-
const isForked = !!repo.source;
62
69
const did = repoUri.split("/")[2];
63
70
const {
64
71
isLoading: miniDocLoading,
65
72
error: miniDocError,
66
73
data: miniDocData,
67
74
} = useMiniDoc(did);
75
75
+
const {
76
76
+
isLoading: starsLoading,
77
77
+
error: starsError,
78
78
+
data: starsData,
79
79
+
} = useBacklinkCount({
80
80
+
subject: repoUri,
81
81
+
source: "sh.tangled.feed.star:subject",
82
82
+
});
83
83
+
const {
84
84
+
isLoading: issuesLoading,
85
85
+
error: issuesError,
86
86
+
data: issuesData,
87
87
+
} = useBacklinkCount({
88
88
+
subject: repoUri,
89
89
+
source: "sh.tangled.repo.issue:repo",
90
90
+
});
91
91
+
const {
92
92
+
isLoading: pullsLoading,
93
93
+
error: pullsError,
94
94
+
data: pullsData,
95
95
+
} = useBacklinkCount({
96
96
+
subject: repoUri,
97
97
+
source: "sh.tangled.repo.pull:targetRepo",
98
98
+
});
68
99
69
69
-
if (miniDocError && !miniDocLoading)
70
70
-
return <p>Could not fetch pinned repos data.{miniDocError.message}</p>;
71
71
-
if (miniDocLoading || !miniDocData) return <Loading />;
100
100
+
const error = miniDocError ?? starsError ?? issuesError ?? pullsError;
101
101
+
const isLoading =
102
102
+
miniDocLoading || starsLoading || issuesLoading || pullsLoading;
103
103
+
104
104
+
if (error && !isLoading)
105
105
+
return <p>Could not fetch pinned repos data.{error.message}</p>;
106
106
+
if (isLoading || !miniDocData || !starsData || !issuesData || !pullsData)
107
107
+
return <Loading />;
72
108
73
109
const { handle: ownerHandle } = miniDocData;
74
110
75
111
return (
76
76
-
<div className="border-overlay0 flex flex-col gap-1 rounded-sm border p-2 pt-3">
112
112
+
<div className="border-overlay0 flex flex-col justify-between gap-1 rounded-sm border p-2 py-3">
77
113
<div className="flex flex-col gap-1">
78
114
<div className="flex items-center">
79
115
{isOwner ? (
···
92
128
</UnderlineRouterLink>
93
129
</div>
94
130
{repo.description && (
95
95
-
<p className="text-subtext pl-2 text-sm">
131
131
+
<p className="text-subtext line-clamp-2 pl-2 text-sm">
96
132
{repo.description}
97
133
</p>
98
134
)}
135
135
+
</div>
136
136
+
<div className="text-subtext flex gap-2 text-sm">
137
137
+
<div className="flex items-center">
138
138
+
<LucideStar height={16} />
139
139
+
<span>{starsData.total}</span>
140
140
+
</div>
141
141
+
<div className="flex items-center">
142
142
+
<LucideCircleDot height={16} />
143
143
+
<span>{issuesData.total}</span>
144
144
+
</div>
145
145
+
<div className="flex items-center">
146
146
+
<LucideGitPullRequest height={16} />
147
147
+
<span>{pullsData.total}</span>
148
148
+
</div>
99
149
</div>
100
150
</div>
101
151
);
+76
src/lib/queries/constellation-backlink-count.ts
···
1
1
+
import { CONSTELLATION_URL } from "@/lib/consts";
2
2
+
import { stripTrailingSlashFromUrl } from "@/lib/utils";
3
3
+
import { queryOptions, useQuery } from "@tanstack/react-query";
4
4
+
import { z } from "zod/v4";
5
5
+
6
6
+
const responseSchema = z.object({
7
7
+
total: z.number(),
8
8
+
});
9
9
+
10
10
+
export const getBacklinkCount = async ({
11
11
+
subject,
12
12
+
source,
13
13
+
}: {
14
14
+
subject: string;
15
15
+
source: string;
16
16
+
}) => {
17
17
+
const cleanedUrl = stripTrailingSlashFromUrl(CONSTELLATION_URL);
18
18
+
const reqUrl = new URL(
19
19
+
`${cleanedUrl}/xrpc/blue.microcosm.links.getBacklinksCount`,
20
20
+
);
21
21
+
reqUrl.searchParams.append("subject", subject);
22
22
+
reqUrl.searchParams.append("source", source);
23
23
+
24
24
+
console.log(reqUrl);
25
25
+
26
26
+
const req = new Request(reqUrl, {
27
27
+
headers: {
28
28
+
Accept: "application/json",
29
29
+
},
30
30
+
});
31
31
+
const res = await fetch(req);
32
32
+
33
33
+
if (!res.ok) {
34
34
+
throw new Error(
35
35
+
`Fetching backlink count for ${subject} on ${source} failed. Constellation responded with ${res.status.toString()}`,
36
36
+
);
37
37
+
}
38
38
+
39
39
+
const data: unknown = await res.json();
40
40
+
41
41
+
const { success, error, data: parsedData } = responseSchema.safeParse(data);
42
42
+
if (!success) {
43
43
+
console.error(error);
44
44
+
throw new Error(
45
45
+
"Constellation response object failed Zod validation. What is likely to have happened is Constellation has changed the return, and we need to update our validations. Please open an issue if one does not already exist!",
46
46
+
);
47
47
+
}
48
48
+
49
49
+
return { total: parsedData.total };
50
50
+
};
51
51
+
52
52
+
export const backlinkCountQueryOpts = ({
53
53
+
subject,
54
54
+
source,
55
55
+
}: {
56
56
+
subject?: string;
57
57
+
source?: string;
58
58
+
}) => {
59
59
+
return queryOptions({
60
60
+
queryKey: ["constellation", "backlinkCount", subject, source],
61
61
+
queryFn: async () => {
62
62
+
if (!subject || !source) return undefined;
63
63
+
return await getBacklinkCount({ subject, source });
64
64
+
},
65
65
+
});
66
66
+
};
67
67
+
68
68
+
export const useBacklinkCount = ({
69
69
+
subject,
70
70
+
source,
71
71
+
}: {
72
72
+
subject: string;
73
73
+
source: string;
74
74
+
}) => {
75
75
+
return useQuery(backlinkCountQueryOpts({ subject, source }));
76
76
+
};