Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/improsocial/impro
An extensible Bluesky client for web impro.social
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183import { Signal, SignalMap, ReactiveStore } from "/js/signals.js";import { getQuotedPost, embedViewRecordToPostView } from "/js/dataHelpers.js";
// The store saves canonical data from the server. Patches are layered on top of this.export class DataStore extends ReactiveStore { constructor(sessionState) { super("dataStore"); this.sessionState = sessionState; // Single-value signals this.$currentUser = new Signal.State(null); this.$profileSearchResults = new Signal.State(null); this.$chatRecipientSearchResults = new Signal.State(null); this.$searchTypeaheadResults = new Signal.State(null); this.$sidebarSearchTypeaheadResults = new Signal.State(null); this.$feedSearchResults = new Signal.State(null); this.$notifications = new Signal.State(null); this.$mentionNotifications = new Signal.State(null); this.$notificationsLastSeenAt = new Signal.State(null); this.$pinnedItems = new Signal.State(null); this.$selectedFeedUri = this.sessionState.$selectedFeedUri; this.$bookmarks = new Signal.State(null); this.$drafts = new Signal.State(null); this.$convoList = new Signal.State(null); this.$convoRequestList = new Signal.State(null); this.$chatActorStatus = new Signal.State(null); this.$blockedProfiles = new Signal.State(null); this.$mutedProfiles = new Signal.State(null); this.$latestProfileSearchRequestTime = new Signal.State(null); this.$latestChatRecipientSearchRequestTime = new Signal.State(null); this.$latestSearchTypeaheadRequestTime = new Signal.State(null); this.$latestSidebarSearchTypeaheadRequestTime = new Signal.State(null); this.$latestFeedSearchRequestTime = new Signal.State(null); this.$trends = new Signal.State(null); this.$postSearchResultsTop = new Signal.State(null); this.$postSearchResultsLatest = new Signal.State(null); this.$latestPostSearchRequestTimeTop = new Signal.State(null); this.$latestPostSearchRequestTimeLatest = new Signal.State(null); this.$gifResults = new Signal.State(null); this.$latestGifRequestTime = new Signal.State(null); // Keyed signals this.$showLessInteractions = new SignalMap(); this.$showMoreInteractions = new SignalMap(); this.$feeds = new SignalMap(); this.$posts = new SignalMap(); this.$embeddedPosts = new SignalMap(); this.$postThreads = new SignalMap(); this.$postThreadOthers = new SignalMap(); this.$profiles = new SignalMap(); this.$detailedProfiles = new SignalMap(); this.$authorFeeds = new SignalMap(); this.$unavailablePosts = new SignalMap(); this.$reposts = new SignalMap(); this.$convos = new SignalMap(); this.$convoMemberLists = new SignalMap(); this.$convoMessages = new SignalMap(); this.$messages = new SignalMap(); this.$postLikes = new SignalMap(); this.$postQuotes = new SignalMap(); this.$postReposts = new SignalMap(); this.$feedGenerators = new SignalMap(); this.$lists = new SignalMap(); this.$starterPacks = new SignalMap(); this.$listMembers = new SignalMap(); this.$actorFeeds = new SignalMap(); this.$actorLists = new SignalMap(); this.$listsWithMembershipByActor = new SignalMap(); this.$hashtagFeeds = new SignalMap(); this.$profileFollowers = new SignalMap(); this.$profileFollows = new SignalMap(); this.$knownFollowers = new SignalMap(); this.$profileChatStatus = new SignalMap(); this.$labelerInfo = new SignalMap(); this.$joinLinkPreviewsByCode = new SignalMap(); }
setPosts(posts) { const seenQuotedPostUris = new Set(); const setQuotedPost = (quotedPost) => { if ( quotedPost?.$type !== "app.bsky.embed.record#viewRecord" || seenQuotedPostUris.has(quotedPost.uri) ) { return; } seenQuotedPostUris.add(quotedPost.uri);
const normalizedQuotedPost = embedViewRecordToPostView(quotedPost); this.mergeProfile(normalizedQuotedPost.author); if (!this.$posts.has(quotedPost.uri)) { this.$embeddedPosts.set(quotedPost.uri, normalizedQuotedPost); } setQuotedPost(getQuotedPost(normalizedQuotedPost)); };
for (const post of posts) { this.mergeProfile(post.author); this.$posts.set(post.uri, post); // Delete matching embedded post, since they're only used as previews this.$embeddedPosts.delete(post.uri); setQuotedPost(getQuotedPost(post)); } }
// Merge into existing profile object if present mergeProfile(profile) { if (!profile?.did || !profile.handle) { return; } const existing = this.$profiles.get(profile.did); const merged = existing ? { ...existing, ...profile } : profile; if (existing && JSON.stringify(merged) === JSON.stringify(existing)) { return; } this.$profiles.set(profile.did, merged); }
setDetailedProfile(profile) { if (!profile?.did) return; this.$profiles.set(profile.did, profile); this.$detailedProfiles.set(profile.did, profile); }
setProfiles(profiles) { for (const profile of profiles) { this.mergeProfile(profile); } }
// Save the convo and sync convo lists if necessary setConvo(convo) { this.$convos.set(convo.id, convo); const isRequest = convo.status === "request"; const destinationSignal = isRequest ? this.$convoRequestList : this.$convoList; const destinationList = destinationSignal.get(); if (destinationList) { const inList = destinationList.convos.some( (listConvo) => listConvo.id === convo.id, ); destinationSignal.set({ convos: inList ? destinationList.convos.map((listConvo) => listConvo.id === convo.id ? convo : listConvo, ) : [convo, ...destinationList.convos], cursor: destinationList.cursor, }); } // Remove accepted convo from the request list if it's there if (!isRequest) { const requestList = this.$convoRequestList.get(); if ( requestList && requestList.convos.some((listConvo) => listConvo.id === convo.id) ) { this.$convoRequestList.set({ convos: requestList.convos.filter( (listConvo) => listConvo.id !== convo.id, ), cursor: requestList.cursor, }); } } }
// All pinned item writes go through here so the selected feed can't dangle: // a selection that's no longer pinned falls back to the first pinned item. setPinnedItems(pinnedItems) { this.$pinnedItems.set(pinnedItems); const selectedFeedUri = this.$selectedFeedUri.get(); if (!selectedFeedUri) { return; } const isPinned = pinnedItems.some( (item) => item.data.uri === selectedFeedUri, ); if (!isPinned) { this.$selectedFeedUri.set(pinnedItems[0]?.data.uri ?? null); } }}