appview-less bluesky client
1<script lang="ts">
2 import { parseToRichText } from '$lib/richtext';
3 import { settings } from '$lib/settings';
4 import { router } from '$lib/state.svelte';
5 import type { BakedRichtext } from '@atcute/bluesky-richtext-builder';
6 import { segmentize, type Facet, type RichtextSegment } from '@atcute/bluesky-richtext-segmenter';
7
8 interface Props {
9 text: string;
10 facets?: Facet[];
11 }
12
13 const { text, facets }: Props = $props();
14
15 const richtext: Promise<BakedRichtext> = $derived(
16 facets ? Promise.resolve({ text, facets }) : parseToRichText(text)
17 );
18
19 const handleProfileClick = (e: MouseEvent, did: string) => {
20 e.preventDefault();
21 router.navigate(`/profile/${did}`);
22 };
23</script>
24
25{#snippet plainText(text: string)}
26 {#each text.split(/(\s)/) as line, idx (idx)}
27 {#if line === '\n'}
28 <br />
29 {:else}
30 {line}
31 {/if}
32 {/each}
33{/snippet}
34
35{#snippet segments(segments: RichtextSegment[])}
36 {#each segments as segment, idx (idx)}
37 {@const { text, features: _features } = segment}
38 {@const features = _features ?? []}
39 {#if features.length > 0}
40 <!-- eslint-disable svelte/no-navigation-without-resolve -->
41 {#each features as feature, idx (idx)}
42 {#if feature.$type === 'app.bsky.richtext.facet#mention'}
43 <a
44 class="text-(--nucleus-accent2) hover:cursor-pointer hover:underline"
45 href={`/profile/${feature.did}`}
46 onclick={(e) => handleProfileClick(e, feature.did)}>{@render plainText(text)}</a
47 >
48 {:else if feature.$type === 'app.bsky.richtext.facet#link'}
49 {@const uri = new URL(feature.uri)}
50 {@const text = `${!uri.protocol.startsWith('http') ? `${uri.protocol}//` : ''}${uri.host}${uri.hash.length === 0 && uri.search.length === 0 && uri.pathname === '/' ? '' : uri.pathname}${uri.search}${uri.hash}`}
51 <a
52 class="text-(--nucleus-accent2)"
53 href={uri.href}
54 target="_blank"
55 rel="noopener noreferrer"
56 >{@render plainText(`${text.substring(0, 40)}${text.length > 40 ? '...' : ''}`)}</a
57 >
58 {:else if feature.$type === 'app.bsky.richtext.facet#tag'}
59 <a
60 class="text-(--nucleus-accent2)"
61 href={`${$settings.socialAppUrl}/search?q=${encodeURIComponent('#' + feature.tag)}`}
62 target="_blank"
63 rel="noopener noreferrer">{@render plainText(text)}</a
64 >
65 {:else}
66 <span>{@render plainText(text)}</span>
67 {/if}
68 {/each}
69 {:else}
70 <span>{@render plainText(text)}</span>
71 {/if}
72 {/each}
73{/snippet}
74
75{#await richtext}
76 {@render plainText(text)}
77{:then richtext}
78 {@render segments(segmentize(richtext.text, richtext.facets))}
79{/await}