Add follows view with followers/following tabs and new follower badges

Followers tab fetches kind 3 events referencing the user, following tab
shows the contact list. Each row has avatar, NIP-05 badge, follow/unfollow
button, and "follows you" indicator. New follower notifications from the
background poller increment a sidebar badge that clears on view open.
This commit is contained in:
Jure
2026-03-25 10:21:18 +01:00
parent 4e04ad38c3
commit 255faefbdc
8 changed files with 224 additions and 4 deletions

View File

@@ -1,6 +1,6 @@
export { getNDK, getNDKUptimeMs, connectToRelays, ensureConnected, resetNDK, getStoredRelayUrls, addRelay, removeRelay, fetchWithTimeout, withTimeout, FEED_TIMEOUT, THREAD_TIMEOUT, SINGLE_TIMEOUT } from "./core";
export { fetchGlobalFeed, fetchFollowFeed, fetchUserNotes, fetchUserNotesNIP65, fetchNoteById, fetchReplies, publishNote, publishReply, publishRepost, publishQuote, fetchHashtagFeed, fetchThreadEvents, fetchAncestors } from "./notes";
export { publishProfile, publishContactList, fetchProfile, fetchFollowSuggestions, fetchMentions, fetchNewFollowers } from "./social";
export { publishProfile, publishContactList, fetchProfile, fetchFollowSuggestions, fetchMentions, fetchFollowers, fetchNewFollowers } from "./social";
export { publishArticle, fetchArticle, fetchAuthorArticles, fetchArticleFeed, searchArticles, fetchByAddr } from "./articles";
export { publishReaction, fetchReactionCount, fetchReplyCount, fetchZapCount, fetchBatchEngagement, fetchZapsReceived, fetchZapsSent } from "./engagement";
export { fetchDMConversations, fetchDMThread, sendDM, decryptDM } from "./dms";

View File

@@ -82,6 +82,20 @@ export async function fetchMentions(pubkey: string, since: number, limit = 50):
return Array.from(events).sort((a, b) => (b.created_at ?? 0) - (a.created_at ?? 0));
}
export async function fetchFollowers(pubkey: string, limit = 200): Promise<string[]> {
const instance = getNDK();
const events = await fetchWithTimeout(
instance,
{ kinds: [3 as NDKKind], "#p": [pubkey], limit },
FEED_TIMEOUT,
);
const followerPubkeys = new Set<string>();
for (const e of events) {
if (e.pubkey !== pubkey) followerPubkeys.add(e.pubkey);
}
return Array.from(followerPubkeys);
}
export async function fetchNewFollowers(pubkey: string, since: number, limit = 20): Promise<NDKEvent[]> {
const instance = getNDK();
const filter: NDKFilter = {

View File

@@ -98,6 +98,7 @@ async function pollOnce(pubkey: string) {
recentlySeen.add(e.id!);
const name = await getProfileName(e.pubkey);
notifyFollower(name).catch(() => {});
useNotificationsStore.getState().incrementNewFollowers();
}
if (followers.length > 0) ts.followers = now;
} catch { /* non-critical */ }