diff --git a/src/lib/blah/crypto/signedPayload.ts b/src/lib/blah/crypto/signedPayload.ts index 50d590c..808ac66 100644 --- a/src/lib/blah/crypto/signedPayload.ts +++ b/src/lib/blah/crypto/signedPayload.ts @@ -3,6 +3,7 @@ export type BlahPayloadSignee

= { payload: P; timestamp: number; user: string; + act_key?: string; }; export type BlahSignedPayload

= { diff --git a/src/lib/blah/structures/roomInfo.ts b/src/lib/blah/structures/roomInfo.ts index 9061585..573ac1c 100644 --- a/src/lib/blah/structures/roomInfo.ts +++ b/src/lib/blah/structures/roomInfo.ts @@ -2,7 +2,7 @@ import type { BlahSignedPayload } from '../crypto'; import type { BlahMessage } from './message'; export type BlahRoomInfo = { - ruuid: string; + rid: string; title: string; last_chat?: BlahSignedPayload; }; diff --git a/src/lib/chatList.ts b/src/lib/chatList.ts index 3685c98..1eff0ef 100644 --- a/src/lib/chatList.ts +++ b/src/lib/chatList.ts @@ -13,8 +13,8 @@ export class ChatListManager { private sortChats(chatList: Chat[]) { chatList.sort( (a, b) => - (b.lastMessage?.date ?? new Date(1970, 0, 1)).getTime() ?? - -(a.lastMessage?.date ?? new Date(1970, 0, 1)).getTime() + (b.lastMessage?.date ?? new Date(0)).getTime() ?? + -(a.lastMessage?.date ?? new Date(0)).getTime() ); } @@ -23,7 +23,7 @@ export class ChatListManager { for (const chat of chats) { const newChat = chatFromBlah(chat, serverEndpoint); - const existing = chatList.find((c) => c.id === chat.ruuid); + const existing = chatList.find((c) => c.id === newChat.id); if (existing) { existing.name = newChat.name; existing.lastMessage = newChat.lastMessage ?? existing.lastMessage; @@ -43,7 +43,7 @@ export class ChatListManager { const chat = chatList.find((c) => c.id === message.signee.payload.room); if (chat) { const newChat = chatFromBlah( - { ruuid: chat.id, title: chat.name, last_chat: message }, + { rid: chat.id, title: chat.name, last_chat: message }, serverEndpoint ); chat.lastMessage = newChat.lastMessage ?? chat.lastMessage; diff --git a/src/lib/chatServers.ts b/src/lib/chatServers.ts index e13b73c..1d56479 100644 --- a/src/lib/chatServers.ts +++ b/src/lib/chatServers.ts @@ -5,6 +5,7 @@ import { BlahKeyPair, type EncodedBlahKeyPair } from './blah/crypto'; import { currentKeyPair } from './keystore'; import { ChatListManager } from './chatList'; import { browser } from '$app/environment'; +import { GlobalSearchManager } from './globalSearch'; export const chatServers = persisted('weblah-chat-servers', ['https://blah.oxa.li/api']); @@ -12,6 +13,7 @@ class ChatServerConnectionPool { private connections: Map = new Map(); private keypair: BlahKeyPair | null = null; chatList: ChatListManager = new ChatListManager(); + searchManager: GlobalSearchManager = new GlobalSearchManager(this.connections); constructor() { if (browser) { diff --git a/src/lib/components/InputFrame.svelte b/src/lib/components/InputFrame.svelte index 9238959..ee07782 100644 --- a/src/lib/components/InputFrame.svelte +++ b/src/lib/components/InputFrame.svelte @@ -5,11 +5,11 @@ export { className as class }; -

-
+ diff --git a/src/lib/globalSearch.ts b/src/lib/globalSearch.ts new file mode 100644 index 0000000..02fe468 --- /dev/null +++ b/src/lib/globalSearch.ts @@ -0,0 +1,56 @@ +import type { BlahChatServerConnection } from './blah/connection/chatServer'; +import { chatFromBlah, type Chat } from './types'; + +export class GlobalSearchManager { + private connections: Map; + + constructor(connections: Map) { + this.connections = connections; + } + + public async searchChats(query: string): Promise<{ joined: Chat[]; public: Chat[] }> { + let jobs: Promise<['joined' | 'public', Chat[]]>[] = []; + + for (const [endpoint, connection] of this.connections.entries()) { + const fetchInJoinedRooms = async (): Promise<['joined' | 'public', Chat[]]> => [ + 'joined', + (await connection.fetchJoinedRooms()).map((r) => chatFromBlah(r, endpoint)) + ]; + const fetchInPublicRooms = async (): Promise<['joined' | 'public', Chat[]]> => [ + 'public', + (await connection.discoverRooms()).map((r) => chatFromBlah(r, endpoint)) + ]; + + jobs = jobs.concat([fetchInJoinedRooms(), fetchInPublicRooms()]); + } + + const results = await Promise.allSettled(jobs); + console.log(results); + + const chats: { joined: Chat[]; public: Chat[] } = { joined: [], public: [] }; + for (const result of results) { + console.log(result); + + if (result.status === 'rejected') continue; + + const [type, chatList] = result.value; + for (const chat of chatList) { + if (!chat.name.includes(query)) continue; // TODO: Actual backend search + if (chats[type].find((c) => c.id === chat.id)) continue; // Dedupe in its own type + if (type !== 'joined' && chats.joined.find((c) => c.id === chat.id)) continue; // If already in joined, don't add to public + + // Insert in last message date order + const date = chat.lastMessage?.date; + if (!date) { + chats[type].push(chat); + continue; + } + let idx = chats[type].findIndex((c) => (c.lastMessage ? c.lastMessage?.date < date : true)); + if (idx === -1) idx = 0; + chats[type].splice(idx, 0, chat); + } + } + + return chats; + } +} diff --git a/src/lib/types/chat.ts b/src/lib/types/chat.ts index e5697c6..5b8537e 100644 --- a/src/lib/types/chat.ts +++ b/src/lib/types/chat.ts @@ -14,7 +14,7 @@ export type Chat = { export function chatFromBlah(room: BlahRoomInfo, serverEndpoint: string): Chat { return { server: serverEndpoint, - id: room.ruuid, + id: room.rid, name: room.title, type: 'group', lastMessage: room.last_chat ? messageFromBlah(room.last_chat) : undefined diff --git a/src/routes/(app)/ChatList.svelte b/src/routes/(app)/ChatList.svelte index 452365b..a1e9a84 100644 --- a/src/routes/(app)/ChatList.svelte +++ b/src/routes/(app)/ChatList.svelte @@ -1,16 +1,21 @@ -
- -
+ +
    {#if $chatList} {#each $chatList as chat} @@ -18,5 +23,13 @@ {/each} {/if}
+ {#if isSearchFocused} +
+ +
+ {/if}
diff --git a/src/routes/(app)/ChatListHeader.svelte b/src/routes/(app)/ChatListHeader.svelte index 882e41d..5a0d51b 100644 --- a/src/routes/(app)/ChatListHeader.svelte +++ b/src/routes/(app)/ChatListHeader.svelte @@ -1,45 +1,51 @@
- - - - - + + + (isSearchFocused = true)} + on:blur={async () => { + await tick(); + isSearchFocused = false; + }} /> - - + +
diff --git a/src/routes/(app)/IdentityMenu.svelte b/src/routes/(app)/IdentityMenu.svelte index 2ba3146..f68eb56 100644 --- a/src/routes/(app)/IdentityMenu.svelte +++ b/src/routes/(app)/IdentityMenu.svelte @@ -4,6 +4,9 @@ import { keyStore, currentKeyIndex, currentKeyPair } from '$lib/keystore'; import { BlahKeyPair, generateName } from '$lib/blah/crypto'; + let className: string = ''; + export { className as class }; + let currentKeyId: string | undefined; let currentKeyName: string | null; $: { @@ -24,10 +27,10 @@ - + {#if currentKeyId} {#key currentKeyId} - + {/key} Using identity {currentKeyName} {:else} diff --git a/src/routes/(app)/SearchChatResultSection.svelte b/src/routes/(app)/SearchChatResultSection.svelte new file mode 100644 index 0000000..7f2b49d --- /dev/null +++ b/src/routes/(app)/SearchChatResultSection.svelte @@ -0,0 +1,21 @@ + + +
  • +

    + {name} +

    +
      + {#each results as chat} + + {/each} +
    +
  • diff --git a/src/routes/(app)/SearchPanel.svelte b/src/routes/(app)/SearchPanel.svelte new file mode 100644 index 0000000..7236cc9 --- /dev/null +++ b/src/routes/(app)/SearchPanel.svelte @@ -0,0 +1,34 @@ + + +{#await search(searchQuery)} +
    + +
    +{:then results} + {#if results.joined.length === 0 && results.public.length === 0} +
    +

    No results found

    +
    + {:else} +
      + {#if results.joined.length > 0} + + {/if} + {#if results.public.length > 0} + + {/if} +
    + {/if} +{/await}