diff --git a/src/components/BskyPost.svelte b/src/components/BskyPost.svelte
index a8b1af8..4f3a5ed 100644
--- a/src/components/BskyPost.svelte
+++ b/src/components/BskyPost.svelte
@@ -40,7 +40,7 @@
import { settings } from '$lib/settings';
import RichText from './RichText.svelte';
import { getRelativeTime } from '$lib/date';
- import { likeSource, repostSource } from '$lib';
+ import { likeSource, repostSource, toCanonicalUri } from '$lib';
import ProfileInfo from './ProfileInfo.svelte';
interface Props {
@@ -72,7 +72,7 @@
const selectedDid = $derived(client.user?.did ?? null);
const isLoggedInUser = $derived($accounts.some((acc) => acc.did === did));
- const aturi = $derived(`at://${did}/app.bsky.feed.post/${rkey}` as CanonicalResourceUri);
+ const aturi = $derived(toCanonicalUri({ did, collection: 'app.bsky.feed.post', rkey }));
const color = $derived(generateColorForDid(did));
let handle: ActorIdentifier = $state('handle.invalid');
@@ -384,50 +384,69 @@
{#snippet postControls(post: PostWithUri)}
{@const myRepost = findBacklinksBy(post.uri, repostSource, selectedDid!).length > 0}
{@const myLike = findBacklinksBy(post.uri, likeSource, selectedDid!).length > 0}
- {#snippet control(
- name: string,
- icon: string,
- onClick: (e: MouseEvent) => void,
- isFull?: boolean,
- hasSolid?: boolean
- )}
+ {#snippet control({
+ name,
+ icon,
+ onClick,
+ isFull,
+ hasSolid,
+ canBeDisabled = true
+ }: {
+ name: string;
+ icon: string;
+ onClick: (e: MouseEvent) => void;
+ isFull?: boolean;
+ hasSolid?: boolean;
+ canBeDisabled?: boolean;
+ })}
{/snippet}
- {@render control('reply', 'heroicons:chat-bubble-left', () => onReply?.(post), false, true)}
- {@render control(
- 'repost',
- 'heroicons:arrow-path-rounded-square-20-solid',
- () => {
+ {@render control({
+ name: 'reply',
+ icon: 'heroicons:chat-bubble-left',
+ hasSolid: true,
+ onClick: () => onReply?.(post)
+ })}
+ {@render control({
+ name: 'repost',
+ icon: 'heroicons:arrow-path-rounded-square-20-solid',
+ onClick: () => {
if (!selectedDid) return;
if (myRepost) deletePostBacklink(client, post, repostSource);
else createPostBacklink(client, post, repostSource);
},
- myRepost
- )}
- {@render control('quote', 'heroicons:paper-clip-20-solid', () => onQuote?.(post), false)}
- {@render control(
- 'like',
- 'heroicons:star',
- () => {
+ isFull: myRepost
+ })}
+ {@render control({
+ name: 'quote',
+ icon: 'heroicons:paper-clip-20-solid',
+ onClick: () => onQuote?.(post)
+ })}
+ {@render control({
+ name: 'like',
+ icon: 'heroicons:star',
+ onClick: () => {
if (!selectedDid) return;
if (myLike) deletePostBacklink(client, post, likeSource);
else createPostBacklink(client, post, likeSource);
},
- myLike,
- true
- )}
+ isFull: myLike,
+ hasSolid: true
+ })}
- {@render control('actions', 'heroicons:ellipsis-horizontal-16-solid', (e) => {
- e.stopPropagation();
- actionsOpen = !actionsOpen;
- actionsPos = { x: 0, y: 0 };
+ {@render control({
+ name: 'actions',
+ icon: 'heroicons:ellipsis-horizontal-16-solid',
+ onClick: (e: MouseEvent) => {
+ e.stopPropagation();
+ actionsOpen = !actionsOpen;
+ actionsPos = { x: 0, y: 0 };
+ },
+ canBeDisabled: false
})}
{/snippet}
diff --git a/src/components/FollowingView.svelte b/src/components/FollowingView.svelte
index 8be3191..40a7aff 100644
--- a/src/components/FollowingView.svelte
+++ b/src/components/FollowingView.svelte
@@ -10,16 +10,17 @@
type Sort
} from '$lib/following';
import FollowingItem from './FollowingItem.svelte';
+ import NotLoggedIn from './NotLoggedIn.svelte';
interface Props {
- selectedDid: Did;
- selectedClient: AtpClient;
+ client: AtpClient | undefined;
followingSort: Sort;
}
- let { selectedDid, selectedClient, followingSort = $bindable('active') }: Props = $props();
+ let { client, followingSort = $bindable('active') }: Props = $props();
- const followsMap = $derived(follows.get(selectedDid));
+ const selectedDid = $derived(client?.user?.did);
+ const followsMap = $derived(selectedDid ? follows.get(selectedDid) : undefined);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let sortedFollowing = $state<{ did: Did; data: any }[]>([]);
@@ -36,7 +37,7 @@
if (calculationTimer) clearTimeout(calculationTimer);
isLongCalculation = false;
- if (!followsMap) {
+ if (!followsMap || !selectedDid) {
sortedFollowing = [];
return;
}
@@ -137,7 +138,9 @@
- {#if sortedFollowing.length === 0 || isLongCalculation}
+ {#if !client}
+
+ {:else if sortedFollowing.length === 0 || isLongCalculation}
diff --git a/src/components/NotLoggedIn.svelte b/src/components/NotLoggedIn.svelte
new file mode 100644
index 0000000..dd7964e
--- /dev/null
+++ b/src/components/NotLoggedIn.svelte
@@ -0,0 +1,5 @@
+
+
+ x_x
no accounts are logged in!
+
+
diff --git a/src/components/PostComposer.svelte b/src/components/PostComposer.svelte
index b51fdcb..86e632d 100644
--- a/src/components/PostComposer.svelte
+++ b/src/components/PostComposer.svelte
@@ -67,13 +67,10 @@
}
});
- if (!res) {
- return err('failed to post: not logged in');
- }
+ if (!res) return err('failed to post: not logged in');
- if (!res.ok) {
+ if (!res.ok)
return err(`failed to post: ${res.data.error}: ${res.data.message ?? 'no details'}`);
- }
return ok({
uri: res.data.uri,
@@ -99,15 +96,16 @@
postText = '';
info = 'posted!';
unfocus();
- setTimeout(() => (info = ''), 1000 * 0.8);
+ setTimeout(() => (info = ''), 800);
} else {
- // todo: add a way to clear error
info = res.error;
+ setTimeout(() => (info = ''), 3000);
}
});
};
$effect(() => {
+ if (!client.atcute) info = 'not logged in';
document.documentElement.style.setProperty('--acc-color', color);
if (isFocused && textareaEl) textareaEl.focus();
});
diff --git a/src/components/ProfilePicture.svelte b/src/components/ProfilePicture.svelte
index 45903eb..c17efa1 100644
--- a/src/components/ProfilePicture.svelte
+++ b/src/components/ProfilePicture.svelte
@@ -1,5 +1,5 @@
@@ -24,11 +24,7 @@
let avatarUrl = $state
(avatarCache.get(did) ?? null);
const loadProfile = async (targetDid: Did) => {
- // If we already have it in cache, we might want to re-validate eventually,
- // but for UI stability, using the cache is priority.
- // However, we still need to handle the case where we don't have it.
- if (avatarCache.has(targetDid)) avatarUrl = avatarCache.get(targetDid) ?? null;
- else avatarUrl = null;
+ avatarUrl = avatarCache.get(targetDid) ?? null;
try {
const profile = await client.getProfile(targetDid);
@@ -46,8 +42,6 @@
avatarCache.set(targetDid, null);
}
} else {
- // Don't cache errors aggressively, or maybe cache 'null' to stop retrying?
- // For now, just set local state.
avatarUrl = null;
}
} catch (e) {
diff --git a/src/components/ProfileView.svelte b/src/components/ProfileView.svelte
index 882adb2..6ddde04 100644
--- a/src/components/ProfileView.svelte
+++ b/src/components/ProfileView.svelte
@@ -1,6 +1,11 @@