diff --git a/deno.lock b/deno.lock index 0121c80..89f0ed7 100644 --- a/deno.lock +++ b/deno.lock @@ -8,6 +8,7 @@ "npm:@atcute/client@^4.1.1": "4.1.1", "npm:@atcute/identity-resolver@^1.2.1": "1.2.1_@atcute+identity@1.1.3", "npm:@atcute/identity@^1.1.3": "1.1.3", + "npm:@atcute/jetstream@^1.1.2": "1.1.2", "npm:@atcute/lexicons@^1.2.5": "1.2.5", "npm:@atcute/oauth-browser-client@^2.0.3": "2.0.3_@atcute+identity@1.1.3", "npm:@atcute/tid@^1.0.3": "1.0.3", @@ -23,6 +24,7 @@ "npm:@tailwindcss/vite@^4.1.18": "4.1.18_vite@7.3.0__@types+node@25.0.3__picomatch@4.0.3_@types+node@25.0.3", "npm:@types/node@^25.0.3": "25.0.3", "npm:@wora/cache-persist@^2.2.1": "2.2.1", + "npm:async-cache-dedupe@^3.4.0": "3.4.0", "npm:eslint-config-prettier@^10.1.8": "10.1.8_eslint@9.39.2", "npm:eslint-plugin-svelte@^3.13.1": "3.13.1_eslint@9.39.2_svelte@5.46.1__acorn@8.15.0_postcss@8.5.6", "npm:eslint@^9.39.2": "9.39.2", @@ -94,6 +96,18 @@ "@badrap/valita" ] }, + "@atcute/jetstream@1.1.2": { + "integrity": "sha512-u6p/h2xppp7LE6W/9xErAJ6frfN60s8adZuCKtfAaaBBiiYbb1CfpzN8Uc+2qtJZNorqGvuuDb5572Jmh7yHBQ==", + "dependencies": [ + "@atcute/lexicons", + "@badrap/valita", + "@mary-ext/event-iterator", + "@mary-ext/simple-event-emitter", + "partysocket", + "type-fest", + "yocto-queue@1.2.2" + ] + }, "@atcute/lexicons@1.2.5": { "integrity": "sha512-9yO9WdgxW8jZ7SbzUycH710z+JmsQ9W9n5S6i6eghYju32kkluFmgBeS47r8e8p2+Dv4DemS7o/3SUGsX9FR5Q==", "dependencies": [ @@ -405,6 +419,15 @@ "@jridgewell/sourcemap-codec" ] }, + "@mary-ext/event-iterator@1.0.0": { + "integrity": "sha512-l6gCPsWJ8aRCe/s7/oCmero70kDHgIK5m4uJvYgwEYTqVxoBOIXbKr5tnkLqUHEg6mNduB4IWvms3h70Hp9ADQ==", + "dependencies": [ + "yocto-queue@1.2.2" + ] + }, + "@mary-ext/simple-event-emitter@1.0.0": { + "integrity": "sha512-meA/zJZKIN1RVBNEYIbjufkUrW7/tRjHH60FjolpG1ixJKo76TB208qefQLNdOVDA7uIG0CGEDuhmMirtHKLAg==" + }, "@polka/url@1.0.0-next.29": { "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==" }, @@ -843,6 +866,13 @@ "aria-query@5.3.2": { "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==" }, + "async-cache-dedupe@3.4.0": { + "integrity": "sha512-RkQr21CpltqMpbYpRaEAmF1BdUO5jnnS/scZkectmLiuWQ81w8u4lYraipbQf8zQ0yYvb3U0N1ozNAYmI4jQ3g==", + "dependencies": [ + "mnemonist", + "safe-stable-stringify" + ] + }, "axobject-query@4.1.0": { "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==" }, @@ -1087,6 +1117,9 @@ "esutils@2.0.3": { "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, + "event-target-polyfill@0.0.4": { + "integrity": "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==" + }, "fast-deep-equal@3.1.3": { "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, @@ -1344,6 +1377,12 @@ "brace-expansion@2.0.2" ] }, + "mnemonist@0.40.3": { + "integrity": "sha512-Vjyr90sJ23CKKH/qPAgUKicw/v6pRoamxIEDFOF8uSgFME7DqPRpHgRTejWVjkdGg5dXj0/NyxZHZ9bcjH+2uQ==", + "dependencies": [ + "obliterator" + ] + }, "mri@1.2.0": { "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==" }, @@ -1364,6 +1403,9 @@ "natural-compare@1.4.0": { "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, + "obliterator@2.0.5": { + "integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==" + }, "optionator@0.9.4": { "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dependencies": [ @@ -1378,7 +1420,7 @@ "p-limit@3.1.0": { "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dependencies": [ - "yocto-queue" + "yocto-queue@0.1.0" ] }, "p-locate@5.0.0": { @@ -1393,6 +1435,12 @@ "callsites" ] }, + "partysocket@1.1.10": { + "integrity": "sha512-ACfn0P6lQuj8/AqB4L5ZDFcIEbpnIteNNObrlxqV1Ge80GTGhjuJ2sNKwNQlFzhGi4kI7fP/C1Eqh8TR78HjDQ==", + "dependencies": [ + "event-target-polyfill" + ] + }, "path-exists@4.0.0": { "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, @@ -1514,6 +1562,9 @@ "mri" ] }, + "safe-stable-stringify@2.5.0": { + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==" + }, "semver@7.7.3": { "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "bin": true @@ -1658,6 +1709,9 @@ "prelude-ls" ] }, + "type-fest@4.41.0": { + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==" + }, "typescript-eslint@8.50.1_eslint@9.39.2_typescript@5.9.3_@typescript-eslint+parser@8.50.1__eslint@9.39.2__typescript@5.9.3": { "integrity": "sha512-ytTHO+SoYSbhAH9CrYnMhiLx8To6PSSvqnvXyPUgPETCvB6eBKmTI9w6XMPS3HsBRGkwTVBX+urA8dYQx6bHfQ==", "dependencies": [ @@ -1729,6 +1783,9 @@ "yocto-queue@0.1.0": { "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, + "yocto-queue@1.2.2": { + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==" + }, "zimmerframe@1.1.4": { "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==" } @@ -1743,6 +1800,7 @@ "npm:@atcute/client@^4.1.1", "npm:@atcute/identity-resolver@^1.2.1", "npm:@atcute/identity@^1.1.3", + "npm:@atcute/jetstream@^1.1.2", "npm:@atcute/lexicons@^1.2.5", "npm:@atcute/oauth-browser-client@^2.0.3", "npm:@atcute/tid@^1.0.3", @@ -1758,6 +1816,7 @@ "npm:@tailwindcss/vite@^4.1.18", "npm:@types/node@^25.0.3", "npm:@wora/cache-persist@^2.2.1", + "npm:async-cache-dedupe@^3.4.0", "npm:eslint-config-prettier@^10.1.8", "npm:eslint-plugin-svelte@^3.13.1", "npm:eslint@^9.39.2", diff --git a/package.json b/package.json index dcade0d..5db45f0 100644 --- a/package.json +++ b/package.json @@ -21,12 +21,14 @@ "@atcute/client": "^4.1.1", "@atcute/identity": "^1.1.3", "@atcute/identity-resolver": "^1.2.1", + "@atcute/jetstream": "^1.1.2", "@atcute/lexicons": "^1.2.5", "@atcute/oauth-browser-client": "^2.0.3", "@atcute/tid": "^1.0.3", "@floating-ui/dom": "^1.7.4", "@soffinal/websocket": "^0.2.1", "@wora/cache-persist": "^2.2.1", + "async-cache-dedupe": "^3.4.0", "hash-wasm": "^4.12.0", "lru-cache": "^11.2.4", "svelte-device-info": "^1.0.6", diff --git a/src/components/AccountSelector.svelte b/src/components/AccountSelector.svelte index c05b581..9863164 100644 --- a/src/components/AccountSelector.svelte +++ b/src/components/AccountSelector.svelte @@ -1,6 +1,6 @@ + +
+
+
+

following

+
+
+
+
+
+
+ {#each ['recent', 'active'] as Sort[] as type (type)} + + {/each} +
+
+ +
+ {#if sortedFollowing.length === 0} +
+
+
+ {:else} + {#each sortedFollowing as user (user.did)} + {@const lastPostAt = user.lastPostAt} + {@const relTime = getRelativeTime(lastPostAt)} + {@const color = generateColorForDid(user.did)} + {@const isHighlighted = highlightedDid === user.did} + {@const displayName = getClient(user.did as AtprotoDid) + .then((client) => client.getProfile()) + .then((profile) => { + if (profile.ok) return profile.value.displayName; + return null; + })} + {@const handle = resolveDidDoc(user.did).then((doc) => { + if (doc.ok) return doc.value.handle; + return 'handle.invalid'; + })} + +
(highlightedDid = user.did)} + onmouseleave={() => (highlightedDid = undefined)} + > + +
+
+ {#await Promise.all([displayName, handle]) then [displayName, handle]} + {displayName || handle} + @{handle} + {/await} +
+
+ + posted {relTime} + {relTime !== 'now' ? 'ago' : ''} + + {#if user.postsInBurst > 0} + + {user.postsInBurst} posts / 1h + + {/if} +
+
+
+ {/each} + {/if} +
+
diff --git a/src/components/PostComposer.svelte b/src/components/PostComposer.svelte index 56e8e6f..b51fdcb 100644 --- a/src/components/PostComposer.svelte +++ b/src/components/PostComposer.svelte @@ -36,7 +36,7 @@ }); // Parse rich text (mentions, links, tags) - const rt = await parseToRichText(client, text); + const rt = await parseToRichText(text); const record: AppBskyFeedPost.Main = { $type: 'app.bsky.feed.post', diff --git a/src/components/ProfilePicture.svelte b/src/components/ProfilePicture.svelte index 86e2e02..c28834c 100644 --- a/src/components/ProfilePicture.svelte +++ b/src/components/ProfilePicture.svelte @@ -29,6 +29,7 @@ {#if isBlob(record.avatar)} avatar for {did} - import type { AtpClient } from '$lib/at/client'; import { parseToRichText } from '$lib/richtext'; import { settings } from '$lib/settings'; import type { BakedRichtext } from '@atcute/bluesky-richtext-builder'; @@ -8,13 +7,12 @@ interface Props { text: string; facets?: Facet[]; - client: AtpClient; } - const { text, facets, client }: Props = $props(); + const { text, facets }: Props = $props(); const richtext: Promise = $derived( - facets ? Promise.resolve({ text, facets }) : parseToRichText(client, text) + facets ? Promise.resolve({ text, facets }) : parseToRichText(text) ); diff --git a/src/components/SettingsView.svelte b/src/components/SettingsView.svelte index 31b8ee7..7d8906d 100644 --- a/src/components/SettingsView.svelte +++ b/src/components/SettingsView.svelte @@ -1,10 +1,10 @@ -{#snippet divider()} -
-{/snippet} - {#snippet advancedTab()}
@@ -62,6 +56,7 @@ {@render _input('slingshot', 'slingshot url (for fetching records & resolving identity)')} {@render _input('spacedust', 'spacedust url (for notifications)')} {@render _input('constellation', 'constellation url (for backlinks)')} + {@render _input('jetstream', 'jetstream url (for real-time updates)')}
@@ -161,7 +156,9 @@
- {#each tabs as tab, idx (tab)} + {#each tabs as tab (tab)} {@const isActive = activeTab === tab}