···1The zlib License
23-Copyright (c) 2025 Jakub Suder
45This software is provided 'as-is', without any express or implied
6warranty. In no event will the authors be held liable for any damages
···1The zlib License
23+Copyright (c) 2026 Jakub Suder
45This software is provided 'as-is', without any express or implied
6warranty. In no event will the authors be held liable for any damages
+2-5
README.md
···4445## Running
4647-You can access Skythread at:
48-49-- [skythread.mackuba.eu](https://skythread.mackuba.eu) โ new version rewritten in Svelte
50-- [blue.mackuba.eu/skythread](https://blue.mackuba.eu/skythread/) โ old stable version in vanilla JS
5152You can also download a zipped copy of this repo or clone it and use it locally โ just open the `index.html` at the root of the project, no need to start any servers!
53···5960## Credits
6162-Copyright ยฉ 2025 [Kuba Suder](https://mackuba.eu) (<a href="https://bsky.app/profile/mackuba.eu">@mackuba.eu</a> on Bluesky). Licensed under [zlib license](https://choosealicense.com/licenses/zlib/) (permissive, similar to MIT).
6364Pull requests, bug reports and suggestions are welcome :)
···4445## Running
4647+You can access the public Skythread site at [skythread.mackuba.eu](https://skythread.mackuba.eu).
0004849You can also download a zipped copy of this repo or clone it and use it locally โ just open the `index.html` at the root of the project, no need to start any servers!
50···5657## Credits
5859+Copyright ยฉ 2026 [Kuba Suder](https://mackuba.eu) (<a href="https://bsky.app/profile/did:plc:oio4hkxaop4ao4wz2pp3f4cr">@mackuba.eu</a> on Bluesky). Licensed under [zlib license](https://choosealicense.com/licenses/zlib/) (permissive, similar to MIT).
6061Pull requests, bug reports and suggestions are welcome :)
···167 async loadHiddenReplies(post: Post): Promise<(json | null)[]> {
168 let expectedReplyURIs = await constellationAPI.getReplies(post.uri);
169 let missingReplyURIs = expectedReplyURIs.filter(r => !post.replies.some(x => x.uri === r));
0000000170 let promises = missingReplyURIs.map(uri => this.loadThreadByAtURI(uri));
171 let responses = await Promise.allSettled(promises);
172
···167 async loadHiddenReplies(post: Post): Promise<(json | null)[]> {
168 let expectedReplyURIs = await constellationAPI.getReplies(post.uri);
169 let missingReplyURIs = expectedReplyURIs.filter(r => !post.replies.some(x => x.uri === r));
170+171+ missingReplyURIs.sort((a, b) => {
172+ let arkey = a.split('/').at(-1)!
173+ let brkey = b.split('/').at(-1)!
174+ return arkey.localeCompare(brkey);
175+ });
176+177 let promises = missingReplyURIs.map(uri => this.loadThreadByAtURI(uri));
178 let responses = await Promise.allSettled(promises);
179
+1-1
src/components/LoginDialog.svelte
···79 {#if loginInfoVisible}
80 <div class="info-box">
81 <p>Skythread doesn't support OAuth yet. For now, you need to use an "app password" here, which you can generate in the Bluesky app settings.</p>
82- <p>The password you enter here is only passed to the Bluesky API (PDS) and isn't saved anywhere. The returned access token is only stored in your browser's local storage. You can see the complete source code of this app <a href="http://tangled.org/@mackuba.eu/skythread" target="_blank">on Tangled</a>.</p>
83 </div>
84 {/if}
85
···79 {#if loginInfoVisible}
80 <div class="info-box">
81 <p>Skythread doesn't support OAuth yet. For now, you need to use an "app password" here, which you can generate in the Bluesky app settings.</p>
82+ <p>The password you enter here is only passed to the Bluesky API (PDS) and isn't saved anywhere. The returned access token is only stored in your browser's local storage. You can see the complete source code of this app <a href="http://tangled.org/mackuba.eu/skythread" target="_blank">on Tangled</a>.</p>
83 </div>
84 {/if}
85
+6-1
src/components/RichTextFromFacets.svelte
···1<script lang="ts">
2 import { RichText, type Facet } from '../../lib/rich_text_lite.js';
3 import { linkToHashtagPage } from '../router.js';
045 let { text, facets }: { text: string, facets: Facet[] } = $props();
6···12 {#if segment.mention}
13 <a href="https://bsky.app/profile/{segment.mention.did}">{segment.text}</a>
14 {:else if segment.link}
15- <a href="{segment.link.uri}">{segment.text}</a>
000016 {:else if segment.tag}
17 <a href={linkToHashtagPage(segment.tag.tag)}>{segment.text}</a>
18 {:else}
···1<script lang="ts">
2 import { RichText, type Facet } from '../../lib/rich_text_lite.js';
3 import { linkToHashtagPage } from '../router.js';
4+ import { isValidURL } from '../utils.js';
56 let { text, facets }: { text: string, facets: Facet[] } = $props();
7···13 {#if segment.mention}
14 <a href="https://bsky.app/profile/{segment.mention.did}">{segment.text}</a>
15 {:else if segment.link}
16+ {#if isValidURL(segment.link.uri)}
17+ <a href="{segment.link.uri}">{segment.text}</a>
18+ {:else}
19+ [{segment.text}]({segment.link.uri})
20+ {/if}
21 {:else if segment.tag}
22 <a href={linkToHashtagPage(segment.tag.tag)}>{segment.text}</a>
23 {:else}