From 1cf773d27c81f03e2122b89d58ae2374890cb3b7 Mon Sep 17 00:00:00 2001 From: BlowaterNostr <127284497+BlowaterNostr@users.noreply.github.com> Date: Sun, 1 Oct 2023 22:21:55 +0000 Subject: [PATCH] Bring Back Pin Conversation List(#205) without CRDT yet --- UI/app.tsx | 5 ++++ UI/app_update.tsx | 24 ++++++++++++---- UI/config-other.test.ts | 6 ++-- UI/config-other.ts | 61 ++++++++++++++++++++++++++++++++++++---- UI/conversation-list.ts | 6 ---- UI/conversation-list.tsx | 26 ++++++++++++----- UI/dm.tsx | 1 + UI/message-panel.tsx | 6 ++-- UI/relay-config.ts | 1 - group-chat.ts | 1 - nostr.ts | 10 +++---- 11 files changed, 110 insertions(+), 37 deletions(-) diff --git a/UI/app.tsx b/UI/app.tsx index 1001474..434d309 100644 --- a/UI/app.tsx +++ b/UI/app.tsx @@ -27,6 +27,7 @@ import { ProfileSyncer } from "../features/profile.ts"; import { Popover, PopOverInputChannel } from "./components/popover.tsx"; import { Channel } from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts"; import { GroupChatController } from "../group-chat.ts"; +import { OtherConfig } from "./config-other.ts"; export async function Start(database: DexieDatabase) { console.log("Start the application"); @@ -92,6 +93,7 @@ export class App { public readonly conversationLists: ConversationLists; public readonly relayConfig: RelayConfig; public readonly groupChatController: GroupChatController; + public readonly otherConfig: OtherConfig = new OtherConfig(); constructor( public readonly database: Database_Contextual_View, @@ -155,6 +157,8 @@ export class App { } })(); + this.otherConfig.syncFromRelay(this.pool, this.ctx); + // create group synchronization (async () => { const stream = await this.pool.newSub("group creations", { @@ -382,6 +386,7 @@ export function AppComponent(props: { allUserInfo: app.conversationLists, profilesSyncer: app.profileSyncer, eventSyncer: app.eventSyncer, + pinListGetter: app.otherConfig, })} ); diff --git a/UI/app_update.tsx b/UI/app_update.tsx index 10902d7..9a36f4c 100644 --- a/UI/app_update.tsx +++ b/UI/app_update.tsx @@ -31,10 +31,10 @@ import { DirectedMessage_Event, Encrypted_Event, getTags, - PinContact, + PinConversation, Profile_Nostr_Event, Text_Note_Event, - UnpinContact, + UnpinConversation, } from "../nostr.ts"; import { MessageThread } from "./dm.tsx"; import { DexieDatabase } from "./dexie-db.ts"; @@ -58,8 +58,8 @@ export type UI_Interaction_Event = | DirectMessagePanelUpdate | BackToContactList | MyProfileUpdate - | PinContact - | UnpinContact + | PinConversation + | UnpinConversation | SignInEvent | RelayConfigChange | CreateGroupChat @@ -194,8 +194,20 @@ export async function* UI_Interaction_Update(args: { model.dm.currentSelectedContact = undefined; } else if (event.type == "SelectConversationType") { model.dm.selectedContactGroup = event.group; - } else if (event.type == "PinContact" || event.type == "UnpinContact") { - console.log("todo: handle", event.type); + } else if (event.type == "PinConversation") { + app.otherConfig.addPin(event.pubkey); + const err = app.otherConfig.saveToRelay(pool, app.ctx); + if (err instanceof Error) { + console.error(err); + continue; + } + } else if (event.type == "UnpinConversation") { + app.otherConfig.removePin(event.pubkey); + const err = app.otherConfig.saveToRelay(pool, app.ctx); + if (err instanceof Error) { + console.error(err); + continue; + } } // // // Editor diff --git a/UI/config-other.test.ts b/UI/config-other.test.ts index 46c53de..3040396 100644 --- a/UI/config-other.test.ts +++ b/UI/config-other.test.ts @@ -7,7 +7,7 @@ import { PrivateKey } from "../lib/nostr-ts/key.ts"; Deno.test("Other Configs", async () => { { const config = OtherConfig.Empty(); - assertEquals(config.pinList, new Set()); + assertEquals(config.getPinList(), new Set()); } const ctx = InMemoryAccountContext.Generate(); @@ -25,7 +25,7 @@ Deno.test("Other Configs", async () => { }); const config = await OtherConfig.FromNostrEvent(event, ctx); if (config instanceof Error) fail(config.message); - assertEquals(config.pinList, new Set([pub.bech32(), pub2.bech32()])); + assertEquals(config.getPinList(), new Set([pub.hex, pub2.hex])); // encode back to events const event_2 = await config.toNostrEvent(ctx); @@ -33,6 +33,6 @@ Deno.test("Other Configs", async () => { const config_2 = await OtherConfig.FromNostrEvent(event_2, ctx); if (config_2 instanceof Error) fail(config_2.message); - assertEquals(config.pinList, config_2.pinList); + assertEquals(config.getPinList(), config_2.getPinList()); } }); diff --git a/UI/config-other.ts b/UI/config-other.ts index a75b97d..b80f65f 100644 --- a/UI/config-other.ts +++ b/UI/config-other.ts @@ -1,13 +1,26 @@ import { prepareParameterizedEvent } from "../lib/nostr-ts/event.ts"; import { PublicKey } from "../lib/nostr-ts/key.ts"; import { NostrAccountContext, NostrEvent, NostrKind } from "../lib/nostr-ts/nostr.ts"; +import { ConnectionPool } from "../lib/nostr-ts/relay.ts"; +import { PinListGetter } from "./conversation-list.tsx"; -export class OtherConfig { +export class OtherConfig implements PinListGetter { static Empty() { return new OtherConfig(); } - readonly pinList = new Set(); // set of pubkeys in npub format + private pinList = new Set(); // set of pubkeys in npub format + getPinList(): Set { + return this.pinList; + } + + addPin(pubkey: string) { + this.pinList.add(pubkey); + } + + removePin(pubkey: string) { + this.pinList.delete(pubkey); + } static async FromNostrEvent(event: NostrEvent, ctx: NostrAccountContext) { const decrypted = await ctx.decrypt(ctx.publicKey.hex, event.content); @@ -17,11 +30,11 @@ export class OtherConfig { const pinList = JSON.parse(decrypted); const c = new OtherConfig(); for (const pin of pinList) { - const pubkey = PublicKey.FromBech32(pin); + const pubkey = PublicKey.FromString(pin); if (pubkey instanceof Error) { continue; } - c.pinList.add(pubkey.bech32()); + c.pinList.add(pubkey.hex); } return c; } @@ -38,8 +51,46 @@ export class OtherConfig { content: encryptedContent, d: OtherConfig.name, kind: NostrKind.Custom_App_Data, - created_at: Date.now() / 1000, }); return event; } + + async saveToRelay(pool: ConnectionPool, ctx: NostrAccountContext) { + const nostrEvent = await this.toNostrEvent(ctx); + if (nostrEvent instanceof Error) { + return nostrEvent; + } + const err = pool.sendEvent(nostrEvent); + if (err instanceof Error) { + return err; + } + } + + async syncFromRelay(pool: ConnectionPool, ctx: NostrAccountContext) { + const stream = await pool.newSub(OtherConfig.name, { + "#d": [OtherConfig.name], + authors: [ctx.publicKey.hex], + kinds: [NostrKind.Custom_App_Data], + }); + if (stream instanceof Error) { + throw stream; // impossible + } + for await (const msg of stream.chan) { + if (msg.res.type == "EOSE") { + continue; + } + console.log("pin list", msg); + const config = await OtherConfig.FromNostrEvent( + // @ts-ignore + msg.res.event, + ctx, + ); + if (config instanceof Error) { + console.error(config); + continue; + } + this.pinList = config.pinList; + console.log(this.pinList); + } + } } diff --git a/UI/conversation-list.ts b/UI/conversation-list.ts index 4058904..956f36b 100644 --- a/UI/conversation-list.ts +++ b/UI/conversation-list.ts @@ -8,10 +8,6 @@ export interface ConversationSummary { profile: Profile_Nostr_Event | undefined; newestEventSendByMe: NostrEvent | undefined; newestEventReceivedByMe: NostrEvent | undefined; - pinEvent: { - readonly created_at: number; - readonly content: CustomAppData; - } | undefined; } export function getConversationSummaryFromPublicKey(k: PublicKey, users: Map) { @@ -78,7 +74,6 @@ export class ConversationLists implements ConversationListRetriever { } } else { const newUserInfo: ConversationSummary = { - pinEvent: undefined, pubkey: PublicKey.FromHex(event.pubkey) as PublicKey, newestEventReceivedByMe: undefined, newestEventSendByMe: undefined, @@ -139,7 +134,6 @@ export class ConversationLists implements ConversationListRetriever { } else { const newUserInfo: ConversationSummary = { pubkey: PublicKey.FromHex(whoAm_I_TalkingTo) as PublicKey, - pinEvent: undefined, newestEventReceivedByMe: undefined, newestEventSendByMe: undefined, profile: undefined, diff --git a/UI/conversation-list.tsx b/UI/conversation-list.tsx index 54024f1..24e857d 100644 --- a/UI/conversation-list.tsx +++ b/UI/conversation-list.tsx @@ -8,11 +8,12 @@ import { emitFunc } from "../event-bus.ts"; import { PinIcon, UnpinIcon } from "./icons/mod.tsx"; import { SearchUpdate } from "./search_model.ts"; import { PublicKey } from "../lib/nostr-ts/key.ts"; -import { PinContact, UnpinContact } from "../nostr.ts"; +import { PinConversation, UnpinConversation } from "../nostr.ts"; import { PrimaryTextColor } from "./style/colors.ts"; import { ButtonGroup } from "./components/button-group.tsx"; import { ChatIcon } from "./icons2/chat-icon.tsx"; import { StartCreateGroupChat } from "./create-group.tsx"; +import { OtherConfig } from "./config-other.ts"; export interface ConversationListRetriever { getContacts: () => Iterable; @@ -25,8 +26,8 @@ export type ConversationType = "Contacts" | "Strangers" | "Group"; export type ContactUpdate = | SelectConversationType | SearchUpdate - | PinContact - | UnpinContact + | PinConversation + | UnpinConversation | StartCreateGroupChat; export type SelectConversationType = { @@ -39,6 +40,7 @@ type Props = { convoListRetriever: ConversationListRetriever; currentSelected: PublicKey | undefined; selectedContactGroup: ConversationType; + pinListGetter: PinListGetter; hasNewMessages: Set; }; export function ConversationList(props: Props) { @@ -153,15 +155,21 @@ export function ConversationList(props: Props) { ); } +export interface PinListGetter { + getPinList(): Set; +} + type ConversationListProps = { contacts: { userInfo: ConversationSummary; isMarked: boolean }[]; currentSelected: PublicKey | undefined; + pinListGetter: PinListGetter; emit: emitFunc; }; @@ -170,10 +178,11 @@ function ContactGroup(props: ConversationListProps) { props.contacts.sort((a, b) => { return sortUserInfo(a.userInfo, b.userInfo); }); + const pinList = props.pinListGetter.getPinList(); const pinned = []; const unpinned = []; for (const contact of props.contacts) { - if (contact.userInfo.pinEvent && contact.userInfo.pinEvent.content.type == "PinContact") { + if (pinList.has(contact.userInfo.pubkey.hex)) { pinned.push(contact); } else { unpinned.push(contact); @@ -201,6 +210,7 @@ function ContactGroup(props: ConversationListProps) {