+6
-14
src/routes/+page.svelte
+6
-14
src/routes/+page.svelte
···
221
// eslint-disable-next-line svelte/prefer-svelte-reactivity
222
const threadMap = new Map<ResourceUri, ThreadPost[]>();
223
224
-
// Single pass: create posts and group by thread
225
for (const [, timeline] of timelines) {
226
for (const [uri, data] of timeline) {
227
const parsedUri = expect(parseCanonicalResourceUri(uri));
···
250
// eslint-disable-next-line svelte/prefer-svelte-reactivity
251
const childrenMap = new Map<ResourceUri | null, ThreadPost[]>();
252
253
-
// Calculate depth and group by parent
254
for (const post of posts) {
255
let depth = 0;
256
let currentUri = post.parentUri;
···
266
childrenMap.get(post.parentUri)!.push(post);
267
}
268
269
-
// Sort children by time (newest first)
270
childrenMap
271
.values()
272
.forEach((children) => children.sort((a, b) => b.newestTime - a.newestTime));
273
274
-
// Helper to create a thread from posts
275
const createThread = (
276
posts: ThreadPost[],
277
rootUri: ResourceUri,
···
285
};
286
};
287
288
-
// Helper to collect all posts in a subtree
289
const collectSubtree = (startPost: ThreadPost): ThreadPost[] => {
290
const result: ThreadPost[] = [];
291
const addWithChildren = (post: ThreadPost) => {
···
297
return result;
298
};
299
300
-
// Find branching points (posts with 2+ children)
301
const branchingPoints = Array.from(childrenMap.entries())
302
.filter(([, children]) => children.length > 1)
303
.map(([uri]) => uri);
304
305
if (branchingPoints.length === 0) {
306
-
// No branches - single thread
307
const roots = childrenMap.get(null) || [];
308
const allPosts = roots.flatMap((root) => collectSubtree(root));
309
threads.push(createThread(allPosts, rootUri));
310
} else {
311
-
// Has branches - split into separate threads
312
for (const branchParentUri of branchingPoints) {
313
const branches = childrenMap.get(branchParentUri) || [];
314
315
-
// Sort branches oldest to newest for processing
316
const sortedBranches = [...branches].sort((a, b) => a.newestTime - b.newestTime);
317
318
sortedBranches.forEach((branchRoot, index) => {
319
const isOldestBranch = index === 0;
320
const branchPosts: ThreadPost[] = [];
321
322
-
// If oldest branch, include parent chain
323
if (isOldestBranch && branchParentUri !== null) {
324
const parentChain: ThreadPost[] = [];
325
let currentUri: ResourceUri | null = branchParentUri;
···
330
branchPosts.push(...parentChain);
331
}
332
333
-
// Add branch posts
334
branchPosts.push(...collectSubtree(branchRoot));
335
336
-
// Recalculate depths for display
337
const minDepth = Math.min(...branchPosts.map((p) => p.depth));
338
branchPosts.forEach((p) => (p.depth = p.depth - minDepth));
339
···
349
}
350
}
351
352
-
// Sort threads by newest time (descending) so older branches appear first
353
threads.sort((a, b) => b.newestTime - a.newestTime);
354
355
// console.log(threads);
···
357
return threads;
358
};
359
360
-
// Filtering functions
361
const isOwnPost = (post: ThreadPost, accounts: Account[]) =>
362
accounts.some((account) => account.did === post.did);
363
const hasNonOwnPost = (posts: ThreadPost[], accounts: Account[]) =>
···
221
// eslint-disable-next-line svelte/prefer-svelte-reactivity
222
const threadMap = new Map<ResourceUri, ThreadPost[]>();
223
224
+
// group posts by root uri into "thread" chains
225
for (const [, timeline] of timelines) {
226
for (const [uri, data] of timeline) {
227
const parsedUri = expect(parseCanonicalResourceUri(uri));
···
250
// eslint-disable-next-line svelte/prefer-svelte-reactivity
251
const childrenMap = new Map<ResourceUri | null, ThreadPost[]>();
252
253
+
// calculate depths
254
for (const post of posts) {
255
let depth = 0;
256
let currentUri = post.parentUri;
···
266
childrenMap.get(post.parentUri)!.push(post);
267
}
268
269
childrenMap
270
.values()
271
.forEach((children) => children.sort((a, b) => b.newestTime - a.newestTime));
272
273
const createThread = (
274
posts: ThreadPost[],
275
rootUri: ResourceUri,
···
283
};
284
};
285
286
const collectSubtree = (startPost: ThreadPost): ThreadPost[] => {
287
const result: ThreadPost[] = [];
288
const addWithChildren = (post: ThreadPost) => {
···
294
return result;
295
};
296
297
+
// find posts with >2 children to split them into separate chains
298
const branchingPoints = Array.from(childrenMap.entries())
299
.filter(([, children]) => children.length > 1)
300
.map(([uri]) => uri);
301
302
if (branchingPoints.length === 0) {
303
const roots = childrenMap.get(null) || [];
304
const allPosts = roots.flatMap((root) => collectSubtree(root));
305
threads.push(createThread(allPosts, rootUri));
306
} else {
307
for (const branchParentUri of branchingPoints) {
308
const branches = childrenMap.get(branchParentUri) || [];
309
310
const sortedBranches = [...branches].sort((a, b) => a.newestTime - b.newestTime);
311
312
sortedBranches.forEach((branchRoot, index) => {
313
const isOldestBranch = index === 0;
314
const branchPosts: ThreadPost[] = [];
315
316
+
// the oldest branch has the full context
317
+
// todo: consider letting the user decide this..?
318
if (isOldestBranch && branchParentUri !== null) {
319
const parentChain: ThreadPost[] = [];
320
let currentUri: ResourceUri | null = branchParentUri;
···
325
branchPosts.push(...parentChain);
326
}
327
328
branchPosts.push(...collectSubtree(branchRoot));
329
330
const minDepth = Math.min(...branchPosts.map((p) => p.depth));
331
branchPosts.forEach((p) => (p.depth = p.depth - minDepth));
332
···
342
}
343
}
344
345
threads.sort((a, b) => b.newestTime - a.newestTime);
346
347
// console.log(threads);
···
349
return threads;
350
};
351
352
+
// todo: add more filtering options
353
const isOwnPost = (post: ThreadPost, accounts: Account[]) =>
354
accounts.some((account) => account.did === post.did);
355
const hasNonOwnPost = (posts: ThreadPost[], accounts: Account[]) =>