diff --git a/DevOps/uniqueKeys.ts b/DevOps/uniqueKeys.ts index cb06b9b..d84a754 100644 --- a/DevOps/uniqueKeys.ts +++ b/DevOps/uniqueKeys.ts @@ -1,7 +1,6 @@ import { DB } from "https://deno.land/x/sqlite@v3.7.2/mod.ts"; -import { SQL } from "./types.ts"; -const query: SQL = `SELECT DISTINCT(pubkey) From stats`; +const query = `SELECT DISTINCT(pubkey) From stats`; const db = new DB("stats.sqlite"); const res = db.query(query); diff --git a/UI/app.tsx b/UI/app.tsx index d068f36..9f96e43 100644 --- a/UI/app.tsx +++ b/UI/app.tsx @@ -354,6 +354,7 @@ export function AppComponent(props: { myPublicKey={myAccountCtx.publicKey} messages={socialPosts} rightPanelModel={app.model.rightPanelModel} + db={app.database} eventEmitter={app.eventBus} /> diff --git a/UI/app_update.ts b/UI/app_update.ts index d65ddf1..1514a34 100644 --- a/UI/app_update.ts +++ b/UI/app_update.ts @@ -109,7 +109,7 @@ export async function* UI_Interaction_Update( const pubkey = PublicKey.FromString(event.text); if (pubkey instanceof PublicKey) { await profileSyncer.add(pubkey.hex); - const profile = getProfileEvent(app.database, pubkey.hex); + const profile = getProfileEvent(app.database, pubkey); app.model.dm.search.searchResults = [{ pubkey: pubkey, profile: profile?.content, @@ -430,7 +430,7 @@ export async function* Database_Update( if (model.dm.search.searchResults.length > 0) { const previous = model.dm.search.searchResults; model.dm.search.searchResults = previous.map((profile) => { - const profileEvent = getProfileEvent(database, profile.pubkey.hex); + const profileEvent = getProfileEvent(database, profile.pubkey); return { pubkey: profile.pubkey, profile: profileEvent?.content, @@ -439,14 +439,19 @@ export async function* Database_Update( } // my profile update if (ctx && e.pubkey == ctx.publicKey.hex) { - const newProfile = getProfileEvent(database, ctx.publicKey.hex); + const newProfile = getProfileEvent(database, ctx.publicKey); if (newProfile == undefined) { throw new Error("impossible"); } model.myProfile = newProfile.content; } } else if (e.kind == NostrKind.DIRECT_MESSAGE) { - const author = getProfileEvent(database, e.pubkey); + const pubkey = PublicKey.FromHex(e.pubkey); + if (pubkey instanceof Error) { + console.error(pubkey); + continue; + } + const author = getProfileEvent(database, pubkey); if (e.pubkey != ctx.publicKey.hex) { notify( author?.content.name ? author.content.name : "", diff --git a/UI/components/tw.ts b/UI/components/tw.ts index cdb9491..565b990 100644 --- a/UI/components/tw.ts +++ b/UI/components/tw.ts @@ -3,7 +3,7 @@ import { DividerBackgroundColor, PlaceholderColor, PrimaryTextColor } from "../s export const CenterClass = "flex items-center justify-center"; export const NoOutlineClass = "focus:outline-none focus-visible:outline-none"; export const inputBorderClass = `border-[2px] border-[${DividerBackgroundColor}]`; -export const DividerClass = `h-[0.0625rem] bg-[${DividerBackgroundColor}] mt-[1.5rem] mb-[1.5rem]`; +export const DividerClass = `h-[0.0625rem] bg-[${DividerBackgroundColor}] my-[1.5rem]`; export const LinearGradientsClass = "bg-gradient-to-r from-[#FF762C] via-[#FF3A5E] to-[#FF01A9]"; export const InputClass = `w-full px-[1rem] py-[0.75rem] rounded-lg resize-y bg-transparent ${NoOutlineClass} ${inputBorderClass} text-[${PrimaryTextColor}] placeholder-[${PlaceholderColor}]`; diff --git a/UI/dm.tsx b/UI/dm.tsx index 4ef1fd2..ac142ea 100644 --- a/UI/dm.tsx +++ b/UI/dm.tsx @@ -45,7 +45,7 @@ export function DirectMessageContainer(props: DirectMessageContainerProps) { for (const [v, editor] of props.editors.entries()) { if (v == currentConversation.hex) { currentEditorModel = editor; - const profile = getProfileEvent(props.db, currentConversation.hex); + const profile = getProfileEvent(props.db, currentConversation); currentEditorModel.target.receiver = { pubkey: currentConversation, name: profile?.content.name, @@ -106,6 +106,7 @@ export function DirectMessageContainer(props: DirectMessageContainerProps) { eventEmitter: props.eventEmitter, editorModel: currentEditorModel, focusedContent: focusedContent, + db: props.db, }); } diff --git a/UI/message-panel.tsx b/UI/message-panel.tsx index 57b9932..77396e4 100644 --- a/UI/message-panel.tsx +++ b/UI/message-panel.tsx @@ -5,7 +5,7 @@ import { Editor, EditorEvent, EditorModel } from "./editor.tsx"; import { CloseIcon, LeftArrowIcon, ReplyIcon } from "./icons/mod.tsx"; import { Avatar } from "./components/avatar.tsx"; -import { IconButtonClass } from "./components/tw.ts"; +import { DividerClass, IconButtonClass } from "./components/tw.ts"; import { sleep } from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts"; import { EventEmitter } from "../event-bus.ts"; @@ -23,10 +23,12 @@ import { NostrKind, } from "https://raw.githubusercontent.com/BlowaterNostr/nostr.ts/main/nostr.ts"; import { PinContact, UnpinContact } from "../nostr.ts"; -import { ProfileData } from "../features/profile.ts"; +import { getProfileEvent, ProfileData } from "../features/profile.ts"; import { MessageThread } from "./dm.tsx"; import { UserDetail } from "./user-detail.tsx"; import { MessageThreadPanel } from "./message-thread-panel.tsx"; +import { Database } from "../database.ts"; +import { DividerBackgroundColor, PrimaryBackgroundColor, PrimaryTextColor } from "./style/colors.ts"; interface DirectMessagePanelProps { myPublicKey: PublicKey; @@ -45,6 +47,7 @@ interface DirectMessagePanelProps { rightPanelModel: RightPanelModel; + db: Database; eventEmitter: EventEmitter< EditorEvent | DirectMessagePanelUpdate | PinContact | UnpinContact >; @@ -91,6 +94,7 @@ export function MessagePanel(props: DirectMessagePanelProps) { eventEmitter={props.eventEmitter} messages={[props.focusedContent.data.root, ...props.focusedContent.data.replies]} myPublicKey={props.myPublicKey} + db={props.db} editorModel={props.focusedContent.editor} /> ); @@ -127,6 +131,7 @@ export function MessagePanel(props: DirectMessagePanelProps) { myPublicKey={props.myPublicKey} threads={props.messages} eventEmitter={props.eventEmitter} + db={props.db} /> } { @@ -170,6 +175,7 @@ export function MessagePanel(props: DirectMessagePanelProps) { interface MessageListProps { myPublicKey: PublicKey; threads: MessageThread[]; + db: Database; eventEmitter?: EventEmitter; } @@ -241,6 +247,7 @@ export class MessageList extends Component { }), myPublicKey: this.props.myPublicKey, eventEmitter: this.props.eventEmitter, + db: this.props.db, }), ); } @@ -292,6 +299,7 @@ function MessageBoxGroup(props: { replyCount: number; }[]; myPublicKey: PublicKey; + db: Database; eventEmitter?: EventEmitter; }) { // const t = Date.now(); @@ -333,7 +341,7 @@ function MessageBoxGroup(props: {
-                                {ParseMessageContent(msg.msg)}
+                                {ParseMessageContent(msg.msg, props.db)}
                             
{msg.replyCount > 0 ? ( @@ -419,7 +427,7 @@ export function NameAndTime(message: ChatMessage, index: number, myPublicKey: Pu } } -export function ParseMessageContent(message: ChatMessage) { +export function ParseMessageContent(message: ChatMessage, db: Database) { let vnode: VNode | VNode[]; if (isImage(message)) { vnode = ; @@ -427,15 +435,20 @@ export function ParseMessageContent(message: ChatMessage) { const items = Array.from(parseContent(message.content)); vnode = [

{message.content}

]; for (const item of items) { + const itemStr = message.content.slice(item.start, item.end + 1); switch (item.type) { case "url": - const url = message.content.slice(item.start, item.end + 1); - if (urlIsImage(url)) { - vnode.push(); + if (urlIsImage(itemStr)) { + vnode.push(); } break; case "npub": - // todo + const pubkey = PublicKey.FromBech32(itemStr); + const profile = getProfileEvent(db, pubkey); + console.log(profile); + if (profile) { + vnode.push(ProfileCard(profile.content, profile.pubkey)); + } break; case "tag": // todo @@ -443,10 +456,22 @@ export function ParseMessageContent(message: ChatMessage) { } } } - return vnode; } +function ProfileCard(profile: ProfileData, pubkey: string) { + return ( +
+
+ +

{profile.name || pubkey}

+
+
+

{profile.about}

+
+ ); +} + type RightPanelProps = { eventEmitter: EventEmitter; rightPanelModel: RightPanelModel; diff --git a/UI/message-thread-panel.tsx b/UI/message-thread-panel.tsx index 245a073..78f13b0 100644 --- a/UI/message-thread-panel.tsx +++ b/UI/message-thread-panel.tsx @@ -11,11 +11,13 @@ import { import { PublicKey } from "https://raw.githubusercontent.com/BlowaterNostr/nostr.ts/main/key.ts"; import { ChatMessage, groupContinuousMessages } from "./message.ts"; import { Editor, EditorEvent, EditorModel } from "./editor.tsx"; +import { Database } from "../database.ts"; interface MessageThreadProps { eventEmitter: EventEmitter; messages: ChatMessage[]; myPublicKey: PublicKey; + db: Database; editorModel: EditorModel; } @@ -36,6 +38,7 @@ export function MessageThreadPanel(props: MessageThreadProps) { @@ -52,6 +55,7 @@ export function MessageThreadPanel(props: MessageThreadProps) { function MessageThreadList(props: { myPublicKey: PublicKey; messages: ChatMessage[]; + db: Database; }) { let groups = groupContinuousMessages(props.messages, (pre, cur) => { const sameAuthor = pre.event.pubkey == cur.event.pubkey; @@ -64,6 +68,7 @@ function MessageThreadList(props: { , ); } @@ -79,6 +84,7 @@ function MessageThreadList(props: { function MessageThreadBoxGroup(props: { messages: ChatMessage[]; myPublicKey: PublicKey; + db: Database; }) { const vnode = (
    @@ -96,7 +102,7 @@ function MessageThreadBoxGroup(props: {
    -                                {ParseMessageContent(msg)}
    +                                {ParseMessageContent(msg, props.db)}
                                 
    diff --git a/UI/message.test.ts b/UI/message.test.ts index 7cffa26..62d5c33 100644 --- a/UI/message.test.ts +++ b/UI/message.test.ts @@ -69,7 +69,7 @@ Deno.test("inline parse", async (t) => { input: `nostr:npub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4ylログボ`, output: [{ type: "npub", - start: 0, + start: 6, end: 68, }], }, diff --git a/UI/message.ts b/UI/message.ts index 6237b33..faaa97e 100644 --- a/UI/message.ts +++ b/UI/message.ts @@ -7,7 +7,7 @@ export function* parseContent(content: string) { yield* match(/https?:\/\/[^\s]+/g, content, "url"); // npubs - yield* match(/nostr:npub[0-9a-z]{59}/g, content, "npub"); + yield* match(/npub[0-9a-z]{59}/g, content, "npub"); // tags yield* match(/#\[[0-9]+\]/g, content, "tag"); diff --git a/deno.lock b/deno.lock index b12401a..7c8efd8 100644 --- a/deno.lock +++ b/deno.lock @@ -6,6 +6,16 @@ "https://deno.land/std@0.176.0/testing/_diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea", "https://deno.land/std@0.176.0/testing/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", "https://deno.land/std@0.176.0/testing/asserts.ts": "984ab0bfb3faeed92ffaa3a6b06536c66811185328c5dd146257c702c41b01ab", + "https://deno.land/x/sqlite@v3.7.2/build/sqlite.d.ts": "d724a21a588a0e19ae46a3476349fe5d75e896735e7362ea8cf997ffad35d0f9", + "https://deno.land/x/sqlite@v3.7.2/build/sqlite.js": "c59f109f100c2bae0b9342f04e0d400583e2e3211d08bb71095177a4109ee5bf", + "https://deno.land/x/sqlite@v3.7.2/build/vfs.js": "08533cc78fb29b9d9bd62f6bb93e5ef333407013fed185776808f11223ba0e70", + "https://deno.land/x/sqlite@v3.7.2/mod.ts": "e09fc79d8065fe222578114b109b1fd60077bff1bb75448532077f784f4d6a83", + "https://deno.land/x/sqlite@v3.7.2/src/constants.ts": "90f3be047ec0a89bcb5d6fc30db121685fc82cb00b1c476124ff47a4b0472aa9", + "https://deno.land/x/sqlite@v3.7.2/src/db.ts": "62f3ad4d593c07e7c64fbbd97da6b4579ea695f8d11d52d899661e9d42a400fd", + "https://deno.land/x/sqlite@v3.7.2/src/error.ts": "f7a15cb00d7c3797da1aefee3cf86d23e0ae92e73f0ba3165496c3816ab9503a", + "https://deno.land/x/sqlite@v3.7.2/src/function.ts": "e4c83b8ec64bf88bafad2407376b0c6a3b54e777593c70336fb40d43a79865f2", + "https://deno.land/x/sqlite@v3.7.2/src/query.ts": "d58abda928f6582d77bad685ecf551b1be8a15e8e38403e293ec38522e030cad", + "https://deno.land/x/sqlite@v3.7.2/src/wasm.ts": "e79d0baa6e42423257fb3c7cc98091c54399254867e0f34a09b5bdef37bd9487", "https://esm.sh/preact@10.11.3": "ad3c24796c4132c84b4b392f812228d53a0fd600fa64648f27aebd05ec09b24e", "https://esm.sh/stable/preact@10.11.3/denonext/preact.mjs": "c828d9020ea26f07ba07b2f0a3ef97fd7ceb1c2a772c380ca05e2647ac8d3923", "https://esm.sh/twind@0.16.16": "7f93bf4dc6102cef9a66689807b09f6bf35cbd8dd397b550fc97525c3e71e196", diff --git a/features/profile.ts b/features/profile.ts index 313dfe5..7ee631d 100644 --- a/features/profile.ts +++ b/features/profile.ts @@ -72,9 +72,9 @@ export async function saveProfile( pool.sendEvent(event); } -export function getProfileEvent(db: Database, pubkey: string): ProfileEvent | undefined { +export function getProfileEvent(db: Database, pubkey: PublicKey): ProfileEvent | undefined { const events = Array.from(db.filterEvents((e) => { - return e.kind === NostrKind.META_DATA && e.pubkey === pubkey; + return e.kind === NostrKind.META_DATA && e.pubkey === pubkey.hex; })); if (events.length == 0) { return undefined; @@ -110,7 +110,7 @@ export function getProfiles( ): Map { const contacts: Map = new Map(); for (const key of pubkeys) { - const event = getProfileEvent(db, key); + const event = getProfileEvent(db, PublicKey.FromHex(key) as PublicKey); contacts.set(key, event); } return contacts;