+1
env.d.ts
+1
env.d.ts
+1
package.json
+1
package.json
+11
pnpm-lock.yaml
+11
pnpm-lock.yaml
···
11
'@atcute/atproto':
12
specifier: ^3.1.1
13
version: 3.1.3
14
'@atcute/client':
15
specifier: ^4.0.3
16
version: 4.0.3
···
66
67
'@atcute/atproto@3.1.3':
68
resolution: {integrity: sha512-+5u0l+8E7h6wZO7MM1HLXIPoUEbdwRtr28ZRTgsURp+Md9gkoBj9e5iMx/xM8F2Exfyb65J5RchW/WlF2mw/RQ==}
69
70
'@atcute/client@4.0.3':
71
resolution: {integrity: sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw==}
···
1935
1936
'@atcute/atproto@3.1.3':
1937
dependencies:
1938
'@atcute/lexicons': 1.1.1
1939
1940
'@atcute/client@4.0.3':
···
11
'@atcute/atproto':
12
specifier: ^3.1.1
13
version: 3.1.3
14
+
'@atcute/bluesky':
15
+
specifier: ^3.2.2
16
+
version: 3.2.2
17
'@atcute/client':
18
specifier: ^4.0.3
19
version: 4.0.3
···
69
70
'@atcute/atproto@3.1.3':
71
resolution: {integrity: sha512-+5u0l+8E7h6wZO7MM1HLXIPoUEbdwRtr28ZRTgsURp+Md9gkoBj9e5iMx/xM8F2Exfyb65J5RchW/WlF2mw/RQ==}
72
+
73
+
'@atcute/bluesky@3.2.2':
74
+
resolution: {integrity: sha512-L8RrMNeRLGvSHMq2KDIAGXrpuNGA87YOXpXHY1yhmovVCjQ5n55FrR6JoQaxhprdXdKKQiefxNwQQQybDrfgFQ==}
75
76
'@atcute/client@4.0.3':
77
resolution: {integrity: sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw==}
···
1941
1942
'@atcute/atproto@3.1.3':
1943
dependencies:
1944
+
'@atcute/lexicons': 1.1.1
1945
+
1946
+
'@atcute/bluesky@3.2.2':
1947
+
dependencies:
1948
+
'@atcute/atproto': 3.1.3
1949
'@atcute/lexicons': 1.1.1
1950
1951
'@atcute/client@4.0.3':
+109
-64
src/entrypoints/background.ts
+109
-64
src/entrypoints/background.ts
···
3
import {
4
type Fronter,
5
fronterGetSocialAppHrefs,
6
-
fronterGetSocialAppHref,
7
getFronter,
8
getSpFronters,
9
-
memberUriString,
10
putFronter,
11
frontersCache,
12
parseSocialAppPostUrl,
13
displayNameCache,
14
deleteFronter,
15
getPkFronters,
16
} from "@/lib/utils";
17
import {
18
-
parseCanonicalResourceUri,
19
parseResourceUri,
20
ResourceUri,
21
} from "@atcute/lexicons";
22
23
export default defineBackground({
24
persistent: true,
···
92
}
93
// dont write if no names is specified or no sp/pk fronters are fetched
94
if (members.length === 0) return;
95
-
const results = [];
96
for (const result of items) {
97
const resp = await putFronter(result.uri, members, authToken);
98
if (resp.ok) {
99
const parsedUri = await cacheFronter(result.uri, resp.value);
100
results.push({
101
rkey: parsedUri.rkey!,
102
...resp.value,
103
});
···
112
type: "TIMELINE_FRONTER",
113
results: Object.fromEntries(
114
results.flatMap((fronter) =>
115
-
fronterGetSocialAppHrefs(fronter, fronter.rkey).map((href) => [
116
-
href,
117
-
fronter,
118
-
]),
119
),
120
),
121
});
···
124
feed: any[],
125
sender: globalThis.Browser.runtime.MessageSender,
126
) => {
127
-
const handlePost = async (post: any) => {
128
-
const cachedFronter = await frontersCache.get(post.uri);
129
-
if (cachedFronter === null) return;
130
-
const promise = cachedFronter
131
-
? Promise.resolve(cachedFronter)
132
-
: getFronter(post.uri).then(async (fronter) => {
133
-
if (!fronter.ok) {
134
-
await frontersCache.set(post.uri, null);
135
-
return;
136
-
}
137
-
return fronter.value;
138
-
});
139
-
return promise.then(async (fronter) => {
140
-
if (!fronter) return;
141
-
const parsedUri = await cacheFronter(post.uri, fronter);
142
-
return {
143
-
rkey: parsedUri.rkey!,
144
-
...fronter,
145
};
146
-
});
147
-
};
148
-
const allPromises = feed.flatMap((item) => {
149
-
const promises = [handlePost(item.post)];
150
-
if (item.reply?.parent) {
151
-
promises.push(handlePost(item.reply.parent));
152
-
}
153
-
if (item.reply?.root) {
154
-
promises.push(handlePost(item.reply.root));
155
-
}
156
-
return promises;
157
-
});
158
const results = new Map(
159
(await Promise.allSettled(allPromises))
160
.filter((result) => result.status === "fulfilled")
161
.flatMap((result) => result.value ?? [])
162
.flatMap((fronter) =>
163
-
fronterGetSocialAppHrefs(fronter, fronter.rkey).map((href) => [
164
-
href,
165
-
fronter,
166
-
]),
167
),
168
);
169
if (results.size === 0) return;
···
210
}
211
return fronter.value;
212
});
213
-
return promise.then(async (fronter): Promise<any> => {
214
-
if (!fronter) return;
215
-
const parsedUri = await cacheFronter(item.uri, fronter);
216
-
if (isReplyThreadFetch)
217
return {
218
rkey: parsedUri.rkey!,
219
...fronter,
220
};
221
-
if (item.depth === 0) await setTabFronter(item.uri, fronter);
222
-
const displayName = item.value.post.author.displayName;
223
-
// cache display name for later use
224
-
if (fronter.handle)
225
-
await displayNameCache.set(fronter.handle, displayName);
226
-
await displayNameCache.set(fronter.did, displayName);
227
-
return {
228
-
rkey: parsedUri.rkey!,
229
-
displayName,
230
-
depth: item.depth,
231
-
...fronter,
232
-
};
233
-
});
234
});
235
});
236
const results = new Map(
···
238
.filter((result) => result.status === "fulfilled")
239
.flatMap((result) => result.value ?? [])
240
.flatMap((fronter) =>
241
-
fronterGetSocialAppHrefs(fronter, fronter.rkey, fronter.depth).map(
242
-
(href) => [href, fronter],
243
-
),
244
),
245
);
246
if (results.size === 0) return;
···
264
break;
265
case "write":
266
await handleWrite(
267
-
JSON.parse(message.data.body).results,
268
message.data.authToken,
269
sender,
270
);
271
break;
272
-
case "writeOne":
273
await handleWrite(
274
[JSON.parse(message.data.body)],
275
message.data.authToken,
276
sender,
277
);
278
break;
279
case "posts":
280
await handleTimeline(
281
(JSON.parse(message.data.body) as any[]).map((post) => ({ post })),
···
3
import {
4
type Fronter,
5
fronterGetSocialAppHrefs,
6
getFronter,
7
getSpFronters,
8
putFronter,
9
frontersCache,
10
parseSocialAppPostUrl,
11
displayNameCache,
12
deleteFronter,
13
getPkFronters,
14
+
FronterType,
15
+
FronterView,
16
} from "@/lib/utils";
17
import {
18
+
ComAtprotoRepoApplyWrites,
19
+
ComAtprotoRepoCreateRecord,
20
+
} from "@atcute/atproto";
21
+
import { createResultSchema } from "@atcute/atproto/types/repo/applyWrites";
22
+
import { feedViewPostSchema } from "@atcute/bluesky/types/app/feed/defs";
23
+
import {
24
+
InferOutput,
25
+
is,
26
parseResourceUri,
27
ResourceUri,
28
+
safeParse,
29
} from "@atcute/lexicons";
30
+
import { AtprotoDid, parseCanonicalResourceUri } from "@atcute/lexicons/syntax";
31
32
export default defineBackground({
33
persistent: true,
···
101
}
102
// dont write if no names is specified or no sp/pk fronters are fetched
103
if (members.length === 0) return;
104
+
const results: FronterView[] = [];
105
for (const result of items) {
106
const resp = await putFronter(result.uri, members, authToken);
107
if (resp.ok) {
108
const parsedUri = await cacheFronter(result.uri, resp.value);
109
results.push({
110
+
type:
111
+
parsedUri.collection === "app.bsky.feed.repost"
112
+
? "repost"
113
+
: parsedUri.collection === "app.bsky.feed.like"
114
+
? "like"
115
+
: "post",
116
rkey: parsedUri.rkey!,
117
...resp.value,
118
});
···
127
type: "TIMELINE_FRONTER",
128
results: Object.fromEntries(
129
results.flatMap((fronter) =>
130
+
fronterGetSocialAppHrefs(fronter).map((href) => [href, fronter]),
131
),
132
),
133
});
···
136
feed: any[],
137
sender: globalThis.Browser.runtime.MessageSender,
138
) => {
139
+
const allPromises = feed.flatMap(
140
+
(item): Promise<FronterView | undefined>[] => {
141
+
if (!is(feedViewPostSchema, item)) return [];
142
+
const handleUri = async (
143
+
uri: ResourceUri,
144
+
type: "repost" | "post",
145
+
) => {
146
+
const cachedFronter = await frontersCache.get(uri);
147
+
if (cachedFronter === null) return;
148
+
const promise = cachedFronter
149
+
? Promise.resolve(cachedFronter)
150
+
: getFronter(uri).then(async (fronter) => {
151
+
if (!fronter.ok) {
152
+
await frontersCache.set(uri, null);
153
+
return;
154
+
}
155
+
return fronter.value;
156
+
});
157
+
return await promise.then(
158
+
async (fronter): Promise<FronterView | undefined> => {
159
+
if (!fronter) return;
160
+
if (type === "repost") {
161
+
const parsedPostUri = expect(
162
+
parseCanonicalResourceUri(item.post.uri),
163
+
);
164
+
fronter = {
165
+
subject: {
166
+
did: parsedPostUri.repo as AtprotoDid,
167
+
rkey: parsedPostUri.rkey,
168
+
handle:
169
+
item.post.author.handle === "handle.invalid"
170
+
? undefined
171
+
: item.post.author.handle,
172
+
},
173
+
...fronter,
174
+
};
175
+
}
176
+
const parsedUri = await cacheFronter(uri, fronter);
177
+
return {
178
+
type,
179
+
rkey: parsedUri.rkey!,
180
+
...fronter,
181
+
};
182
+
},
183
+
);
184
};
185
+
const promises: ReturnType<typeof handleUri>[] = [];
186
+
promises.push(handleUri(item.post.uri, "post"));
187
+
if (item.reply?.parent) {
188
+
promises.push(handleUri(item.reply.parent.uri, "post"));
189
+
}
190
+
if (item.reply?.root) {
191
+
promises.push(handleUri(item.reply.root.uri, "post"));
192
+
}
193
+
if (
194
+
item.reason &&
195
+
item.reason.$type === "app.bsky.feed.defs#reasonRepost" &&
196
+
item.reason.uri
197
+
) {
198
+
promises.push(handleUri(item.reason.uri, "repost"));
199
+
}
200
+
return promises;
201
+
},
202
+
);
203
const results = new Map(
204
(await Promise.allSettled(allPromises))
205
.filter((result) => result.status === "fulfilled")
206
.flatMap((result) => result.value ?? [])
207
.flatMap((fronter) =>
208
+
fronterGetSocialAppHrefs(fronter).map((href) => [href, fronter]),
209
),
210
);
211
if (results.size === 0) return;
···
252
}
253
return fronter.value;
254
});
255
+
return promise.then(
256
+
async (fronter): Promise<FronterView | undefined> => {
257
+
if (!fronter) return;
258
+
const parsedUri = await cacheFronter(item.uri, fronter);
259
+
if (isReplyThreadFetch)
260
+
return {
261
+
type: "thread_reply",
262
+
rkey: parsedUri.rkey!,
263
+
...fronter,
264
+
};
265
+
if (item.depth === 0) await setTabFronter(item.uri, fronter);
266
+
const displayName = item.value.post.author.displayName;
267
+
// cache display name for later use
268
+
if (fronter.handle)
269
+
await displayNameCache.set(fronter.handle, displayName);
270
+
await displayNameCache.set(fronter.did, displayName);
271
return {
272
+
type: "thread_post",
273
rkey: parsedUri.rkey!,
274
+
displayName,
275
+
depth: item.depth,
276
...fronter,
277
};
278
+
},
279
+
);
280
});
281
});
282
const results = new Map(
···
284
.filter((result) => result.status === "fulfilled")
285
.flatMap((result) => result.value ?? [])
286
.flatMap((fronter) =>
287
+
fronterGetSocialAppHrefs(fronter).map((href) => [href, fronter]),
288
),
289
);
290
if (results.size === 0) return;
···
308
break;
309
case "write":
310
await handleWrite(
311
+
JSON.parse(message.data.body),
312
message.data.authToken,
313
sender,
314
);
315
break;
316
+
case "writeOne": {
317
await handleWrite(
318
[JSON.parse(message.data.body)],
319
message.data.authToken,
320
sender,
321
);
322
break;
323
+
}
324
case "posts":
325
await handleTimeline(
326
(JSON.parse(message.data.body) as any[]).map((post) => ({ post })),
+72
-30
src/entrypoints/content.ts
+72
-30
src/entrypoints/content.ts
···
1
-
import { decodeStorageKey } from "@/lib/cache";
2
-
import { expect } from "@/lib/result";
3
-
import {
4
-
Fronter,
5
-
fronterGetSocialAppHref,
6
-
fronterGetSocialAppHrefs,
7
-
parseSocialAppPostUrl,
8
-
} from "@/lib/utils";
9
-
import { parseResourceUri, ResourceUri } from "@atcute/lexicons";
10
11
const getAuthHeader = (headers: any): string | null => {
12
if (headers instanceof Headers) {
···
146
});
147
respEventSetup.then((name) => (respEventName = name));
148
149
-
const applyFronterName = (el: Element, fronters: Fronter["members"]) => {
150
-
if (el.hasAttribute("data-fronter")) return;
151
const s = fronters.map((f) => f.name).join(", ");
152
el.textContent += ` [f: ${s}]`;
153
el.setAttribute("data-fronter", s);
154
};
155
const applyFrontersToPage = (
156
-
fronters: Map<string, any>,
157
pageChange: boolean,
158
) => {
159
// console.log("applyFrontersToPage", fronters);
···
164
);
165
for (const el of document.querySelectorAll("[data-fronter]")) {
166
const previousFronter = el.getAttribute("data-fronter")!;
167
-
// remove fronter text
168
-
el.textContent = el.textContent.replace(
169
-
` [f: ${previousFronter}]`,
170
-
"",
171
-
);
172
el.removeAttribute("data-fronter");
173
}
174
}
175
console.log("applyFrontersToPage", match, fronters);
176
if (fronters.size === 0) return;
177
for (const el of document.getElementsByTagName("a")) {
178
const path = `/${el.href.split("/").slice(3).join("/")}`;
179
-
const fronter = fronters.get(path);
180
-
if (!fronter || fronter.members?.length === 0) continue;
181
-
if (el.hasAttribute("data-fronter")) continue;
182
-
const isFocusedPost = fronter.depth === 0;
183
-
if (isFocusedPost) if (match && match.rkey !== fronter.rkey) continue;
184
-
if (isFocusedPost) if (el.ariaLabel !== fronter.displayName) continue;
185
-
const displayNameElement = isFocusedPost
186
-
? (el.firstElementChild?.firstElementChild?.firstElementChild
187
-
?.firstElementChild?.firstElementChild ?? null)
188
-
: (el.parentElement?.firstElementChild?.firstElementChild
189
-
?.firstElementChild?.firstElementChild ?? null);
190
-
if (!displayNameElement) continue;
191
-
applyFronterName(displayNameElement, fronter.members);
192
}
193
};
194
let postTabObserver: MutationObserver | null = null;
···
1
+
import { FronterView, parseSocialAppPostUrl } from "@/lib/utils";
2
3
const getAuthHeader = (headers: any): string | null => {
4
if (headers instanceof Headers) {
···
138
});
139
respEventSetup.then((name) => (respEventName = name));
140
141
+
const applyFronterName = (
142
+
el: Element,
143
+
fronters: FronterView["members"],
144
+
) => {
145
+
if (el.hasAttribute("data-fronter")) return false;
146
const s = fronters.map((f) => f.name).join(", ");
147
el.textContent += ` [f: ${s}]`;
148
el.setAttribute("data-fronter", s);
149
+
return true;
150
};
151
const applyFrontersToPage = (
152
+
fronters: Map<string, FronterView | null>,
153
pageChange: boolean,
154
) => {
155
// console.log("applyFrontersToPage", fronters);
···
160
);
161
for (const el of document.querySelectorAll("[data-fronter]")) {
162
const previousFronter = el.getAttribute("data-fronter")!;
163
+
if (previousFronter !== "__set__") {
164
+
// remove fronter text
165
+
el.textContent = el.textContent.replace(
166
+
` [f: ${previousFronter}]`,
167
+
"",
168
+
);
169
+
}
170
el.removeAttribute("data-fronter");
171
}
172
}
173
console.log("applyFrontersToPage", match, fronters);
174
if (fronters.size === 0) return;
175
+
const applyFronterToElement = (el: Element, fronter: FronterView) => {
176
+
let displayNameElement: Element | null = null;
177
+
if (fronter.type === "repost") {
178
+
displayNameElement =
179
+
el.parentElement?.parentElement?.parentElement?.parentElement
180
+
?.parentElement?.firstElementChild?.nextElementSibling
181
+
?.firstElementChild?.nextElementSibling?.firstElementChild
182
+
?.firstElementChild?.nextElementSibling?.firstElementChild
183
+
?.firstElementChild?.firstElementChild?.firstElementChild ?? null;
184
+
// sanity check
185
+
if (displayNameElement?.tagName !== "SPAN") {
186
+
console.log(
187
+
`invalid display element tag ${displayNameElement?.tagName}, expected span:`,
188
+
displayNameElement,
189
+
);
190
+
return;
191
+
}
192
+
} else {
193
+
if (fronter.type === "thread_post" && fronter.depth === 0) {
194
+
if (match && match.rkey !== fronter.rkey) return;
195
+
if (el.ariaLabel !== fronter.displayName) return;
196
+
displayNameElement =
197
+
el.firstElementChild?.firstElementChild?.firstElementChild
198
+
?.firstElementChild?.firstElementChild ?? null;
199
+
// sanity check
200
+
if (displayNameElement?.tagName !== "DIV") {
201
+
console.log(
202
+
`invalid display element tag ${displayNameElement?.tagName}, expected a:`,
203
+
displayNameElement,
204
+
);
205
+
return;
206
+
}
207
+
} else {
208
+
displayNameElement =
209
+
el.parentElement?.firstElementChild?.firstElementChild
210
+
?.firstElementChild?.firstElementChild ?? null;
211
+
// sanity check
212
+
if (displayNameElement?.tagName !== "A") {
213
+
console.log(
214
+
`invalid display element tag ${displayNameElement?.tagName}, expected a:`,
215
+
displayNameElement,
216
+
);
217
+
return;
218
+
}
219
+
}
220
+
}
221
+
if (!displayNameElement) return;
222
+
return applyFronterName(displayNameElement, fronter.members);
223
+
};
224
for (const el of document.getElementsByTagName("a")) {
225
+
if (el.getAttribute("data-fronter")) continue;
226
const path = `/${el.href.split("/").slice(3).join("/")}`;
227
+
const elFronters = [fronters.get(path), fronters.get(`${path}#repost`)];
228
+
for (const fronter of elFronters) {
229
+
if (!fronter || fronter.members?.length === 0) continue;
230
+
if (applyFronterToElement(el, fronter)) {
231
+
el.setAttribute("data-fronter", "__set__");
232
+
}
233
+
}
234
}
235
};
236
let postTabObserver: MutationObserver | null = null;
+39
-18
src/entrypoints/isolated.content.ts
+39
-18
src/entrypoints/isolated.content.ts
···
3
import {
4
displayNameCache,
5
Fronter,
6
fronterGetSocialAppHrefs,
7
frontersCache,
8
parseSocialAppPostUrl,
9
} from "@/lib/utils";
10
import { parseResourceUri, ResourceUri } from "@atcute/lexicons";
···
46
});
47
const updateOnUrlChange = async () => {
48
const fronters = await frontersCache.getAll();
49
-
const updated = new Map<string, any>(
50
-
fronters.entries().flatMap(([storageKey, fronter]) => {
51
-
if (!fronter) return [];
52
-
const uri = decodeStorageKey(storageKey);
53
-
const rkey = expect(parseResourceUri(uri)).rkey!;
54
-
return fronterGetSocialAppHrefs(fronter, rkey).map((href) => [
55
-
href,
56
-
fronter,
57
-
]);
58
-
}),
59
-
);
60
// add entry for current page
61
const match = parseSocialAppPostUrl(document.location.href);
62
if (match && !updated.has(`/profile/${match.actorIdentifier}`)) {
63
const maybeFronter = updated.get(
64
`/profile/${match.actorIdentifier}/post/${match.rkey}`,
65
);
66
-
if (maybeFronter)
67
-
updated.set(`/profile/${match.actorIdentifier}`, {
68
-
depth: 0,
69
-
displayName: await displayNameCache.get(match.actorIdentifier),
70
-
rkey: match.rkey,
71
-
...maybeFronter,
72
-
});
73
}
74
window.postMessage({
75
type: "APPLY_CACHED_FRONTERS",
···
3
import {
4
displayNameCache,
5
Fronter,
6
+
fronterGetSocialAppHref,
7
fronterGetSocialAppHrefs,
8
frontersCache,
9
+
FronterView,
10
parseSocialAppPostUrl,
11
} from "@/lib/utils";
12
import { parseResourceUri, ResourceUri } from "@atcute/lexicons";
···
48
});
49
const updateOnUrlChange = async () => {
50
const fronters = await frontersCache.getAll();
51
+
const updated = new Map<string, FronterView | null>();
52
+
for (const [storageKey, fronter] of fronters.entries()) {
53
+
const uri = decodeStorageKey(storageKey);
54
+
const parsedUri = expect(parseResourceUri(uri));
55
+
if (!fronter) {
56
+
updated.set(
57
+
fronterGetSocialAppHref(parsedUri.repo, parsedUri.rkey!),
58
+
null,
59
+
);
60
+
continue;
61
+
}
62
+
const view: FronterView = {
63
+
type:
64
+
parsedUri.collection === "app.bsky.feed.repost" ? "repost" : "post",
65
+
rkey: parsedUri.rkey!,
66
+
...fronter,
67
+
};
68
+
for (const href of fronterGetSocialAppHrefs(view)) {
69
+
updated.set(href, view);
70
+
}
71
+
}
72
// add entry for current page
73
const match = parseSocialAppPostUrl(document.location.href);
74
if (match && !updated.has(`/profile/${match.actorIdentifier}`)) {
75
const maybeFronter = updated.get(
76
`/profile/${match.actorIdentifier}/post/${match.rkey}`,
77
);
78
+
if (maybeFronter) {
79
+
const displayName = await displayNameCache.get(match.actorIdentifier);
80
+
if (displayName) {
81
+
const view: FronterView = {
82
+
...maybeFronter,
83
+
type: "thread_post",
84
+
depth: 0,
85
+
displayName,
86
+
rkey: match.rkey,
87
+
};
88
+
updated.set(`/profile/${maybeFronter.did}`, view);
89
+
if (maybeFronter.handle) {
90
+
updated.set(`/profile/${maybeFronter.handle}`, view);
91
+
}
92
+
}
93
+
}
94
}
95
window.postMessage({
96
type: "APPLY_CACHED_FRONTERS",
+2
-2
src/entrypoints/popup/App.svelte
+2
-2
src/entrypoints/popup/App.svelte
+40
-9
src/lib/utils.ts
+40
-9
src/lib/utils.ts
···
28
import { getAtprotoHandle, getPdsEndpoint } from "@atcute/identity";
29
import { PersistentCache } from "./cache";
30
31
export type Fronter = {
32
members: {
33
uri?: MemberUri;
···
35
}[];
36
handle: Handle | null;
37
did: AtprotoDid;
38
};
39
40
export const fronterSchema = v.record(
41
v.string(),
···
359
}));
360
};
361
362
-
export const fronterGetSocialAppHrefs = (
363
-
fronter: Fronter,
364
-
rkey: RecordKey,
365
-
depth?: number,
366
-
) => {
367
return [
368
-
fronter.handle
369
-
? [fronterGetSocialAppHref(fronter.handle, rkey, depth)]
370
-
: [],
371
-
fronterGetSocialAppHref(fronter.did, rkey, depth),
372
].flat();
373
};
374
···
28
import { getAtprotoHandle, getPdsEndpoint } from "@atcute/identity";
29
import { PersistentCache } from "./cache";
30
31
+
export type Subject = {
32
+
handle?: Handle;
33
+
did: AtprotoDid;
34
+
rkey: RecordKey;
35
+
};
36
+
37
export type Fronter = {
38
members: {
39
uri?: MemberUri;
···
41
}[];
42
handle: Handle | null;
43
did: AtprotoDid;
44
+
subject?: Subject;
45
};
46
+
47
+
export type FronterView = Fronter & { rkey: RecordKey } & (
48
+
| {
49
+
type: "thread_reply";
50
+
}
51
+
| {
52
+
type: "thread_post";
53
+
displayName: string;
54
+
depth: number;
55
+
}
56
+
| {
57
+
type: "post";
58
+
}
59
+
| {
60
+
type: "like";
61
+
}
62
+
| {
63
+
type: "repost";
64
+
}
65
+
);
66
+
export type FronterType = FronterView["type"];
67
68
export const fronterSchema = v.record(
69
v.string(),
···
387
}));
388
};
389
390
+
export const fronterGetSocialAppHrefs = (view: FronterView) => {
391
+
if (view.type === "repost" && view.subject) {
392
+
const subject = view.subject;
393
+
const handle = subject?.handle;
394
+
return [
395
+
handle ? [`${fronterGetSocialAppHref(handle, subject.rkey)}#repost`] : [],
396
+
`${fronterGetSocialAppHref(subject.did, subject.rkey)}#repost`,
397
+
].flat();
398
+
}
399
+
const depth = view.type === "thread_post" ? view.depth : undefined;
400
return [
401
+
view.handle ? [fronterGetSocialAppHref(view.handle, view.rkey, depth)] : [],
402
+
fronterGetSocialAppHref(view.did, view.rkey, depth),
403
].flat();
404
};
405