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