Bring Back Pin Conversation List(#205)

without CRDT yet
This commit is contained in:
BlowaterNostr 2023-10-01 22:21:55 +00:00 committed by GitHub
parent b8616ed8bf
commit 1cf773d27c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 110 additions and 37 deletions

View File

@ -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,
})}
</div>
);

View File

@ -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

View File

@ -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());
}
});

View File

@ -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<string>(); // set of pubkeys in npub format
private pinList = new Set<string>(); // set of pubkeys in npub format
getPinList(): Set<string> {
return this.pinList;
}
addPin(pubkey: string) {
this.pinList.add(pubkey);
}
removePin(pubkey: string) {
this.pinList.delete(pubkey);
}
static async FromNostrEvent(event: NostrEvent<NostrKind.Custom_App_Data>, 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);
}
}
}

View File

@ -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<string, ConversationSummary>) {
@ -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,

View File

@ -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<ConversationSummary>;
@ -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<string>;
};
export function ConversationList(props: Props) {
@ -153,15 +155,21 @@ export function ConversationList(props: Props) {
<ContactGroup
contacts={Array.from(convoListToRender.values())}
currentSelected={props.currentSelected}
pinListGetter={props.pinListGetter}
emit={props.emit}
/>
</div>
);
}
export interface PinListGetter {
getPinList(): Set<string>;
}
type ConversationListProps = {
contacts: { userInfo: ConversationSummary; isMarked: boolean }[];
currentSelected: PublicKey | undefined;
pinListGetter: PinListGetter;
emit: emitFunc<ContactUpdate>;
};
@ -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) {
<ConversationListItem
userInfo={contact.userInfo}
isMarked={contact.isMarked}
isPinned={true}
/>
<button
@ -211,7 +221,7 @@ function ContactGroup(props: ConversationListProps) {
onClick={(e) => {
e.stopPropagation();
props.emit({
type: "UnpinContact",
type: "UnpinConversation",
pubkey: contact.userInfo.pubkey.hex,
});
}}
@ -246,6 +256,7 @@ function ContactGroup(props: ConversationListProps) {
<ConversationListItem
userInfo={contact.userInfo}
isMarked={contact.isMarked}
isPinned={false}
/>
<button
@ -256,7 +267,7 @@ function ContactGroup(props: ConversationListProps) {
onClick={(e) => {
e.stopPropagation();
props.emit({
type: "PinContact",
type: "PinConversation",
pubkey: contact.userInfo.pubkey.hex,
});
}}
@ -280,6 +291,7 @@ function ContactGroup(props: ConversationListProps) {
type ListItemProps = {
userInfo: ConversationSummary;
isMarked: boolean;
isPinned: boolean;
};
function ConversationListItem(props: ListItemProps) {
@ -316,7 +328,7 @@ function ConversationListItem(props: ListItemProps) {
</span>
)
: undefined}
{props.userInfo.pinEvent != undefined && props.userInfo.pinEvent.content.type == "PinContact"
{props.isPinned
? (
<PinIcon
class={tw`w-3 h-3 absolute top-0 right-0`}

View File

@ -28,6 +28,7 @@ type DirectMessageContainerProps = {
allUserInfo: ConversationLists;
profilesSyncer: ProfileSyncer;
eventSyncer: EventSyncer;
pinListGetter: cl.PinListGetter;
} & DM_Model;
export type MessageThread = {

View File

@ -15,10 +15,10 @@ import { NostrEvent, NostrKind } from "../lib/nostr-ts/nostr.ts";
import {
DirectedMessage_Event,
Parsed_Event,
PinContact,
PinConversation,
Profile_Nostr_Event,
Text_Note_Event,
UnpinContact,
UnpinConversation,
} from "../nostr.ts";
import { ProfileData, ProfileSyncer } from "../features/profile.ts";
import { MessageThread } from "./dm.tsx";
@ -83,7 +83,7 @@ interface DirectMessagePanelProps {
db: Database_Contextual_View;
emit: emitFunc<
EditorEvent | DirectMessagePanelUpdate | PinContact | UnpinContact
EditorEvent | DirectMessagePanelUpdate | PinConversation | UnpinConversation
>;
profilesSyncer: ProfileSyncer;
eventSyncer: EventSyncer;

View File

@ -102,7 +102,6 @@ export class RelayConfig {
this.config = Automerge.change(this.config, "add", (config) => {
config[url] = true;
});
const hex = secp256k1.utils.bytesToHex(this.save());
}
async remove(url: string) {

View File

@ -43,7 +43,6 @@ export class GroupChatController {
this.conversationLists.groupChatSummaries.set(args.groupKey.bech32, {
newestEventReceivedByMe: undefined,
newestEventSendByMe: undefined,
pinEvent: undefined,
profile: undefined,
pubkey: args.groupKey.toPublicKey(),
});

View File

@ -55,15 +55,15 @@ export type DirectedMessage_Event = Parsed_Event<NostrKind.DIRECT_MESSAGE> & {
};
export type Encrypted_Event = DirectedMessage_Event;
export type CustomAppData = PinContact | UnpinContact | UserLogin;
export type CustomAppData = PinConversation | UnpinConversation | UserLogin;
export type PinContact = {
type: "PinContact";
export type PinConversation = {
type: "PinConversation";
pubkey: string;
};
export type UnpinContact = {
type: "UnpinContact";
export type UnpinConversation = {
type: "UnpinConversation";
pubkey: string;
};