chatStores.js raw

   1  import { writable, derived } from 'svelte/store';
   2  
   3  // ==================== Chat Navigation ====================
   4  
   5  // Active chat sub-tab: "inbox" or "channels"
   6  export const activeChatTab = writable(localStorage.getItem("activeChatTab") || "inbox");
   7  activeChatTab.subscribe(v => localStorage.setItem("activeChatTab", v));
   8  
   9  // ==================== Inbox (DMs) ====================
  10  
  11  // Map of pubkey -> { messages: [], lastRead: number, unreadCount: number, protocol: "nip04"|"nip17" }
  12  export const conversations = writable(new Map());
  13  
  14  // Currently selected conversation partner pubkey (null = no conversation open)
  15  export const selectedConversation = writable(null);
  16  
  17  // Loading state for DM fetch
  18  export const inboxLoading = writable(false);
  19  
  20  // ==================== Channels (NIP-28) ====================
  21  
  22  // Map of channelId -> { metadata: {}, messages: [], lastRead: number, unreadCount: number, joined: boolean }
  23  export const channels = writable(new Map());
  24  
  25  // Set of joined channel IDs (persisted)
  26  const storedJoined = localStorage.getItem("joinedChannels");
  27  export const joinedChannels = writable(new Set(storedJoined ? JSON.parse(storedJoined) : []));
  28  joinedChannels.subscribe(set => localStorage.setItem("joinedChannels", JSON.stringify([...set])));
  29  
  30  // Currently selected channel ID (null = no channel open)
  31  export const selectedChannel = writable(null);
  32  
  33  // Channel discovery loading state
  34  export const channelsLoading = writable(false);
  35  
  36  // ==================== Derived ====================
  37  
  38  // Total unread DM count
  39  export const totalUnreadDMs = derived(conversations, $convs => {
  40      let count = 0;
  41      for (const conv of $convs.values()) {
  42          count += conv.unreadCount || 0;
  43      }
  44      return count;
  45  });
  46  
  47  // Total unread channel messages
  48  export const totalUnreadChannels = derived(channels, $chans => {
  49      let count = 0;
  50      for (const chan of $chans.values()) {
  51          if (chan.joined) count += chan.unreadCount || 0;
  52      }
  53      return count;
  54  });
  55  
  56  // ==================== Actions ====================
  57  
  58  /**
  59   * Mark a conversation as read
  60   * @param {string} pubkey - Conversation partner pubkey
  61   */
  62  export function markConversationRead(pubkey) {
  63      conversations.update(map => {
  64          const conv = map.get(pubkey);
  65          if (conv) {
  66              conv.lastRead = Date.now();
  67              conv.unreadCount = 0;
  68              map.set(pubkey, conv);
  69          }
  70          return new Map(map);
  71      });
  72  }
  73  
  74  /**
  75   * Mark a channel as read
  76   * @param {string} channelId - Channel ID
  77   */
  78  export function markChannelRead(channelId) {
  79      channels.update(map => {
  80          const chan = map.get(channelId);
  81          if (chan) {
  82              chan.lastRead = Date.now();
  83              chan.unreadCount = 0;
  84              map.set(channelId, chan);
  85          }
  86          return new Map(map);
  87      });
  88      // Persist last-read timestamps
  89      localStorage.setItem(`channel-lastread-${channelId}`, Date.now().toString());
  90  }
  91  
  92  /**
  93   * Join a channel
  94   * @param {string} channelId - Channel ID
  95   */
  96  export function joinChannel(channelId) {
  97      joinedChannels.update(set => {
  98          set.add(channelId);
  99          return new Set(set);
 100      });
 101  }
 102  
 103  /**
 104   * Leave a channel
 105   * @param {string} channelId - Channel ID
 106   */
 107  export function leaveChannel(channelId) {
 108      joinedChannels.update(set => {
 109          set.delete(channelId);
 110          return new Set(set);
 111      });
 112      selectedChannel.update(sel => sel === channelId ? null : sel);
 113  }
 114  
 115  /**
 116   * Reset all chat state (on logout)
 117   */
 118  export function resetChatState() {
 119      conversations.set(new Map());
 120      selectedConversation.set(null);
 121      channels.set(new Map());
 122      selectedChannel.set(null);
 123      inboxLoading.set(false);
 124      channelsLoading.set(false);
 125  }
 126