feat: support nip 89
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { LumeStorage } from "@lume/storage";
|
import { LumeStorage } from "@lume/storage";
|
||||||
import { type NDKEventWithReplies, type NIP05 } from "@lume/types";
|
import { type NDKEventWithReplies, type NIP05 } from "@lume/types";
|
||||||
import NDK, {
|
import NDK, {
|
||||||
|
NDKAppHandlerEvent,
|
||||||
NDKEvent,
|
NDKEvent,
|
||||||
NDKFilter,
|
NDKFilter,
|
||||||
NDKKind,
|
NDKKind,
|
||||||
@@ -519,19 +520,26 @@ export class Ark {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async replyTo({
|
public async getAppRecommend({
|
||||||
content,
|
unknownKind,
|
||||||
event,
|
author,
|
||||||
}: { content: string; event: NDKEvent }) {
|
}: { unknownKind: string; author?: string }) {
|
||||||
try {
|
const event = await this.ndk.fetchEvent({
|
||||||
const replyEvent = new NDKEvent(this.ndk);
|
kinds: [NDKKind.AppRecommendation],
|
||||||
replyEvent.content = content;
|
"#d": [unknownKind],
|
||||||
replyEvent.kind = NDKKind.Text;
|
authors: this.#storage.account.contacts || [author],
|
||||||
replyEvent.tag(event, "reply");
|
});
|
||||||
|
|
||||||
return await replyEvent.publish();
|
if (event) return event.tags.filter((item) => item[0] !== "d");
|
||||||
} catch (e) {
|
|
||||||
throw new Error(e);
|
const altEvent = await this.ndk.fetchEvent({
|
||||||
}
|
kinds: [NDKKind.AppHandler],
|
||||||
|
"#k": [unknownKind],
|
||||||
|
authors: this.#storage.account.contacts || [author],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (altEvent) return altEvent.tags.filter((item) => item[0] !== "d");
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
54
packages/ark/src/components/note/appHandler.tsx
Normal file
54
packages/ark/src/components/note/appHandler.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { NDKAppHandlerEvent } from "@nostr-dev-kit/ndk";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { useArk } from "../../provider";
|
||||||
|
|
||||||
|
export function AppHandler({ tag }: { tag: string[] }) {
|
||||||
|
const ark = useArk();
|
||||||
|
|
||||||
|
const { isLoading, isError, data } = useQuery({
|
||||||
|
queryKey: ["app-handler", tag[1]],
|
||||||
|
queryFn: async () => {
|
||||||
|
const ref = tag[1].split(":");
|
||||||
|
const event = await ark.getEventByFilter({
|
||||||
|
filter: {
|
||||||
|
kinds: [Number(ref[0])],
|
||||||
|
authors: [ref[1]],
|
||||||
|
"#d": [ref[2]],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!event) return null;
|
||||||
|
|
||||||
|
const app = NDKAppHandlerEvent.from(event);
|
||||||
|
return await app.fetchProfile();
|
||||||
|
},
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
<div>Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError || !data) {
|
||||||
|
return <div>Error</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2 p-2 rounded-md bg-neutral-200 dark:bg-neutral-800 hover:ring-1 hover:ring-blue-500">
|
||||||
|
<img
|
||||||
|
src={data?.picture || data?.image}
|
||||||
|
alt={data.pubkey}
|
||||||
|
decoding="async"
|
||||||
|
className="object-cover bg-white rounded-lg h-9 w-9 shrink-0 ring-1 ring-neutral-200/50 dark:ring-neutral-800/50"
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="max-w-[15rem] truncate font-semibold text-neutral-950 dark:text-neutral-50">
|
||||||
|
{data.name}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-neutral-600 dark:text-neutral-400 line-clamp-1">
|
||||||
|
{data.about}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { cn } from "@lume/utils";
|
import { cn } from "@lume/utils";
|
||||||
import { NDKKind } from "@nostr-dev-kit/ndk";
|
import { NDKKind } from "@nostr-dev-kit/ndk";
|
||||||
import { useNoteContext, useRichContent } from "../..";
|
import { useNoteContext, useRichContent } from "../..";
|
||||||
|
import { NIP89 } from "./nip89";
|
||||||
|
|
||||||
export function NoteContent({
|
export function NoteContent({
|
||||||
className,
|
className,
|
||||||
@@ -10,18 +11,7 @@ export function NoteContent({
|
|||||||
const event = useNoteContext();
|
const event = useNoteContext();
|
||||||
const { parsedContent } = useRichContent(event.content);
|
const { parsedContent } = useRichContent(event.content);
|
||||||
|
|
||||||
if (event.kind === NDKKind.Text) {
|
if (event.kind !== NDKKind.Text) return <NIP89 className={className} />;
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"break-p select-text whitespace-pre-line text-balance leading-normal",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{parsedContent}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -30,7 +20,7 @@ export function NoteContent({
|
|||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Unsupported kind
|
{parsedContent}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
53
packages/ark/src/components/note/nip89.tsx
Normal file
53
packages/ark/src/components/note/nip89.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { useArk } from "../../provider";
|
||||||
|
import { AppHandler } from "./appHandler";
|
||||||
|
import { useNoteContext } from "./provider";
|
||||||
|
|
||||||
|
export function NIP89({ className }: { className?: string }) {
|
||||||
|
const ark = useArk();
|
||||||
|
const event = useNoteContext();
|
||||||
|
|
||||||
|
const { isLoading, isError, data } = useQuery({
|
||||||
|
queryKey: ["app-recommend", event.id],
|
||||||
|
queryFn: () => {
|
||||||
|
return ark.getAppRecommend({
|
||||||
|
unknownKind: event.kind.toString(),
|
||||||
|
author: event.pubkey,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
refetchOnMount: false,
|
||||||
|
staleTime: Infinity,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
<div>Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError || !data) {
|
||||||
|
return <div>Error</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<div className="flex flex-col rounded-lg bg-neutral-100 dark:bg-neutral-900">
|
||||||
|
<div className="inline-flex items-center justify-between h-10 px-3 border-b shrink-0 border-neutral-200 dark:border-neutral-800">
|
||||||
|
<p className="text-sm font-medium text-amber-400">
|
||||||
|
Lume isn't support this event
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||||
|
{event.kind}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col flex-1 gap-2 px-3 py-3">
|
||||||
|
<span className="text-sm font-medium uppercase text-neutral-600 dark:text-neutral-400">
|
||||||
|
Open with
|
||||||
|
</span>
|
||||||
|
{data.map((item, index) => (
|
||||||
|
<AppHandler key={item[1] + index} tag={item} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ import { NDKCacheAdapterTauri } from "@lume/ndk-cache-tauri";
|
|||||||
import { LumeStorage } from "@lume/storage";
|
import { LumeStorage } from "@lume/storage";
|
||||||
import { QUOTES, delay, sendNativeNotification } from "@lume/utils";
|
import { QUOTES, delay, sendNativeNotification } from "@lume/utils";
|
||||||
import NDK, {
|
import NDK, {
|
||||||
NDKKind,
|
|
||||||
NDKNip46Signer,
|
NDKNip46Signer,
|
||||||
NDKPrivateKeySigner,
|
NDKPrivateKeySigner,
|
||||||
NDKRelay,
|
NDKRelay,
|
||||||
|
|||||||
Reference in New Issue
Block a user