migrate to ndk

This commit is contained in:
Ren Amamiya
2023-06-08 18:09:36 +07:00
parent 75a33d205a
commit 0ba9877785
53 changed files with 2749 additions and 930 deletions

View File

@@ -10,7 +10,7 @@ export function User({ pubkey }: { pubkey: string }) {
<div className="flex items-center gap-2">
<div className="relative h-11 w-11 shrink rounded-md">
<Image
src={user?.picture || DEFAULT_AVATAR}
src={user?.image || DEFAULT_AVATAR}
alt={pubkey}
className="h-11 w-11 rounded-md object-cover"
decoding="async"
@@ -18,7 +18,7 @@ export function User({ pubkey }: { pubkey: string }) {
</div>
<div className="flex w-full flex-1 flex-col items-start text-start">
<span className="truncate font-medium leading-tight text-white">
{user?.display_name || user?.name}
{user?.displayName || user?.name}
</span>
<span className="text-base leading-tight text-zinc-400">
{user?.nip05?.toLowerCase() || shortenKey(pubkey)}

View File

@@ -1,15 +1,15 @@
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { AvatarUploader } from "@shared/avatarUploader";
import { Image } from "@shared/image";
import { RelayContext } from "@shared/relayProvider";
import { useActiveAccount } from "@stores/accounts";
import { DEFAULT_AVATAR, WRITEONLY_RELAYS } from "@stores/constants";
import { getEventHash, getSignature } from "nostr-tools";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useContext, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { navigate } from "vite-plugin-ssr/client/router";
export function Page() {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const account = useActiveAccount((state: any) => state.account);
const [image, setImage] = useState(DEFAULT_AVATAR);
@@ -25,28 +25,30 @@ export function Page() {
const onSubmit = (data: any) => {
setLoading(true);
const event: any = {
content: JSON.stringify(data),
created_at: Math.floor(Date.now() / 1000),
kind: 0,
pubkey: account.pubkey,
tags: [],
};
try {
const signer = new NDKPrivateKeySigner(account.privkey);
ndk.signer = signer;
event.id = getEventHash(event);
event.sig = getSignature(event, account.privkey);
const event = new NDKEvent(ndk);
// build event
event.content = JSON.stringify(data);
event.kind = 0;
event.pubkey = account.pubkey;
event.tags = [];
// publish event
event.publish();
// publish
pool.publish(event, WRITEONLY_RELAYS);
// redirect to step 3
setTimeout(
() =>
navigate("/app/auth/create/step-3", {
overwriteLastHistoryEntry: true,
}),
2000,
);
// redirect to step 3
setTimeout(
() =>
navigate("/app/auth/create/step-3", {
overwriteLastHistoryEntry: true,
}),
2000,
);
} catch {
console.log("error");
}
};
useEffect(() => {
@@ -94,7 +96,7 @@ export function Page() {
<div className="relative w-full shrink-0 overflow-hidden before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-fuchsia-500 before:opacity-0 before:ring-2 before:ring-fuchsia-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-fuchsia-500/100 dark:focus-within:after:shadow-fuchsia-500/20">
<input
type={"text"}
{...register("display_name", {
{...register("displayName", {
required: true,
minLength: 4,
})}
@@ -105,11 +107,11 @@ export function Page() {
</div>
<div className="flex flex-col gap-1">
<label className="text-base font-semibold uppercase tracking-wider text-zinc-400">
About
Bio
</label>
<div className="relative h-20 w-full shrink-0 overflow-hidden before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-fuchsia-500 before:opacity-0 before:ring-2 before:ring-fuchsia-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-fuchsia-500/100 dark:focus-within:after:shadow-fuchsia-500/20">
<textarea
{...register("about")}
{...register("bio")}
spellCheck={false}
className="relative h-20 w-full resize-none rounded-lg border border-black/5 px-3 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-white dark:shadow-black/10 dark:placeholder:text-zinc-500"
/>
@@ -119,7 +121,7 @@ export function Page() {
<button
type="submit"
disabled={!isDirty || !isValid}
className="w-full transform rounded-lg bg-fuchsia-500 px-3.5 py-2.5 font-medium text-white shadow-button hover:bg-fuchsia-600 active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-70"
className="inline-flex h-10 w-full transform items-center justify-center rounded-lg bg-fuchsia-500 px-3.5 font-medium text-white shadow-button hover:bg-fuchsia-600 active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-70"
>
{loading ? (
<svg

View File

@@ -1,10 +1,9 @@
import { User } from "@app/auth/components/user";
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { CheckCircleIcon } from "@shared/icons";
import { RelayContext } from "@shared/relayProvider";
import { useActiveAccount } from "@stores/accounts";
import { WRITEONLY_RELAYS } from "@stores/constants";
import { arrayToNIP02 } from "@utils/transform";
import { getEventHash, getSignature } from "nostr-tools";
import { useContext, useState } from "react";
import { navigate } from "vite-plugin-ssr/client/router";
@@ -108,7 +107,7 @@ const initialList = [
];
export function Page() {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const [account, updateFollows] = useActiveAccount((state: any) => [
state.account,
@@ -129,33 +128,34 @@ export function Page() {
const submit = async () => {
setLoading(true);
// update account follows
updateFollows(follows);
try {
const tags = arrayToNIP02(follows);
const signer = new NDKPrivateKeySigner(account.privkey);
ndk.signer = signer;
const tags = arrayToNIP02(follows);
const event = new NDKEvent(ndk);
// build event
event.content = "";
event.kind = 3;
event.pubkey = account.pubkey;
event.tags = tags;
// publish event
event.publish();
const event: any = {
content: "",
created_at: Math.floor(Date.now() / 1000),
kind: 3,
pubkey: account.pubkey,
tags: tags,
};
// update account follows
updateFollows(follows);
event.id = getEventHash(event);
event.sig = getSignature(event, account.privkey);
// publish
pool.publish(event, WRITEONLY_RELAYS);
// redirect to step 3
setTimeout(
() =>
navigate("/", {
overwriteLastHistoryEntry: true,
}),
2000,
);
// redirect to step 3
setTimeout(
() =>
navigate("/", {
overwriteLastHistoryEntry: true,
}),
2000,
);
} catch {
console.log("error");
}
};
return (

View File

@@ -1,53 +1,41 @@
import { User } from "@app/auth/components/user";
import { RelayContext } from "@shared/relayProvider";
import { useActiveAccount } from "@stores/accounts";
import { METADATA_RELAY } from "@stores/constants";
import { nip02ToArray } from "@utils/transform";
import { setToArray } from "@utils/transform";
import { useContext, useState } from "react";
import useSWRSubscription from "swr/subscription";
import { navigate } from "vite-plugin-ssr/client/router";
export function Page() {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const [loading, setLoading] = useState(false);
const [account, updateFollows] = useActiveAccount((state: any) => [
state.account,
state.updateFollows,
]);
const [loading, setLoading] = useState(false);
const [follows, setFollows] = useState(null);
useSWRSubscription(account ? ["follows", account.pubkey] : null, () => {
const unsubscribe = pool.subscribe(
[
{
kinds: [3],
authors: [account.pubkey],
},
],
METADATA_RELAY,
(event: any) => {
setFollows(event.tags);
},
);
return () => {
unsubscribe();
};
});
const submit = () => {
const submit = async () => {
// show loading indicator
setLoading(true);
// follows as list
const followsList = nip02ToArray(follows);
try {
const user = ndk.getUser({ hexpubkey: account.pubkey });
const follows = await user.follows();
// update account follows in store
updateFollows(followsList);
// follows as list
const followsList = setToArray(follows);
// redirect to home
setTimeout(() => navigate("/", { overwriteLastHistoryEntry: true }), 2000);
// update account follows in store
updateFollows(followsList);
// redirect to home
setTimeout(
() => navigate("/", { overwriteLastHistoryEntry: true }),
2000,
);
} catch {
console.log("error");
}
};
return (

View File

@@ -1,19 +1,19 @@
import { Dialog, Transition } from "@headlessui/react";
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { AvatarUploader } from "@shared/avatarUploader";
import { CancelIcon, PlusIcon } from "@shared/icons";
import { Image } from "@shared/image";
import { RelayContext } from "@shared/relayProvider";
import { useActiveAccount } from "@stores/accounts";
import { DEFAULT_AVATAR, WRITEONLY_RELAYS } from "@stores/constants";
import { DEFAULT_AVATAR } from "@stores/constants";
import { dateToUnix } from "@utils/date";
import { createChannel } from "@utils/storage";
import { getEventHash, getSignature } from "nostr-tools";
import { Fragment, useContext, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { navigate } from "vite-plugin-ssr/client/router";
export function ChannelCreateModal() {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const account = useActiveAccount((state: any) => state.account);
const [isOpen, setIsOpen] = useState(false);
@@ -39,20 +39,20 @@ export function ChannelCreateModal() {
const onSubmit = (data: any) => {
setLoading(true);
if (account) {
const event: any = {
content: JSON.stringify(data),
created_at: dateToUnix(),
kind: 40,
pubkey: account.pubkey,
tags: [],
};
try {
const signer = new NDKPrivateKeySigner(account.privkey);
ndk.signer = signer;
event.id = getEventHash(event);
event.sig = getSignature(event, account.privkey);
const event = new NDKEvent(ndk);
// build event
event.content = JSON.stringify(data);
event.kind = 40;
event.created_at = dateToUnix();
event.pubkey = account.pubkey;
event.tags = [];
// publish channel
pool.publish(event, WRITEONLY_RELAYS);
// publish event
event.publish();
// insert to database
createChannel(event.id, event.pubkey, event.content, event.created_at);
@@ -65,9 +65,9 @@ export function ChannelCreateModal() {
setIsOpen(false);
// redirect to channel page
navigate(`/app/channel?id=${event.id}`);
}, 2000);
} else {
console.log("error");
}, 1000);
} catch (e) {
console.log("error: ", e);
}
};

View File

@@ -12,7 +12,7 @@ export function Member({ pubkey }: { pubkey: string }) {
) : (
<Image
className="inline-block h-8 w-8 rounded-md bg-white ring-2 ring-zinc-950 transition-all duration-150 ease-in-out"
src={user?.picture || DEFAULT_AVATAR}
src={user?.image || DEFAULT_AVATAR}
alt={user?.pubkey || "user avatar"}
/>
)}

View File

@@ -1,15 +1,14 @@
import { UserReply } from "@app/channel/components/messages/userReply";
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { CancelIcon } from "@shared/icons";
import { RelayContext } from "@shared/relayProvider";
import { useActiveAccount } from "@stores/accounts";
import { useChannelMessages } from "@stores/channels";
import { WRITEONLY_RELAYS } from "@stores/constants";
import { dateToUnix } from "@utils/date";
import { getEventHash, getSignature } from "nostr-tools";
import { useContext, useState } from "react";
export function ChannelMessageForm({ channelID }: { channelID: string }) {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const account = useActiveAccount((state: any) => state.account);
const [value, setValue] = useState("");
@@ -31,19 +30,20 @@ export function ChannelMessageForm({ channelID }: { channelID: string }) {
tags = [["e", channelID, "", "root"]];
}
const event: any = {
content: value,
created_at: dateToUnix(),
kind: 42,
pubkey: account.pubkey,
tags: tags,
};
const signer = new NDKPrivateKeySigner(account.privkey);
ndk.signer = signer;
event.id = getEventHash(event);
event.sig = getSignature(event, account.privkey);
const event = new NDKEvent(ndk);
// build event
event.content = value;
event.kind = 42;
event.created_at = dateToUnix();
event.pubkey = account.pubkey;
event.tags = tags;
// publish event
event.publish();
// publish note
pool.publish(event, WRITEONLY_RELAYS);
// reset state
setValue("");
};

View File

@@ -1,16 +1,15 @@
import { Dialog, Transition } from "@headlessui/react";
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { CancelIcon, HideIcon } from "@shared/icons";
import { RelayContext } from "@shared/relayProvider";
import { Tooltip } from "@shared/tooltip";
import { useActiveAccount } from "@stores/accounts";
import { useChannelMessages } from "@stores/channels";
import { WRITEONLY_RELAYS } from "@stores/constants";
import { dateToUnix } from "@utils/date";
import { getEventHash, getSignature } from "nostr-tools";
import { Fragment, useContext, useState } from "react";
export function MessageHideButton({ id }: { id: string }) {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const account = useActiveAccount((state: any) => state.account);
const hide = useChannelMessages((state: any) => state.hideMessage);
@@ -25,19 +24,19 @@ export function MessageHideButton({ id }: { id: string }) {
};
const hideMessage = () => {
const event: any = {
content: "",
created_at: dateToUnix(),
kind: 43,
pubkey: account.pubkey,
tags: [["e", id]],
};
const signer = new NDKPrivateKeySigner(account.privkey);
ndk.signer = signer;
event.id = getEventHash(event);
event.sig = getSignature(event, account.privkey);
const event = new NDKEvent(ndk);
// build event
event.content = "";
event.kind = 43;
event.created_at = dateToUnix();
event.pubkey = account.pubkey;
event.tags = [["e", id]];
// publish note
pool.publish(event, WRITEONLY_RELAYS);
// publish event
event.publish();
// update state
hide(id);

View File

@@ -1,16 +1,15 @@
import { Dialog, Transition } from "@headlessui/react";
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { CancelIcon, MuteIcon } from "@shared/icons";
import { RelayContext } from "@shared/relayProvider";
import { Tooltip } from "@shared/tooltip";
import { useActiveAccount } from "@stores/accounts";
import { useChannelMessages } from "@stores/channels";
import { WRITEONLY_RELAYS } from "@stores/constants";
import { dateToUnix } from "@utils/date";
import { getEventHash, getSignature } from "nostr-tools";
import { Fragment, useContext, useState } from "react";
export function MessageMuteButton({ pubkey }: { pubkey: string }) {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const account = useActiveAccount((state: any) => state.account);
const mute = useChannelMessages((state: any) => state.muteUser);
@@ -25,19 +24,19 @@ export function MessageMuteButton({ pubkey }: { pubkey: string }) {
};
const muteUser = () => {
const event: any = {
content: "",
created_at: dateToUnix(),
kind: 44,
pubkey: account.pubkey,
tags: [["p", pubkey]],
};
const signer = new NDKPrivateKeySigner(account.privkey);
ndk.signer = signer;
event.id = getEventHash(event);
event.sig = getSignature(event, account.privkey);
const event = new NDKEvent(ndk);
// build event
event.content = "";
event.kind = 44;
event.created_at = dateToUnix();
event.pubkey = account.pubkey;
event.tags = [["p", pubkey]];
// publish note
pool.publish(event, WRITEONLY_RELAYS);
// publish event
event.publish();
// update state
mute(pubkey);

View File

@@ -31,7 +31,7 @@ export function ChannelMessageUser({
<>
<div className="relative h-11 w-11 shrink-0 rounded-md">
<Image
src={user?.picture || DEFAULT_AVATAR}
src={user?.image || DEFAULT_AVATAR}
alt={pubkey}
className="h-11 w-11 rounded-md object-cover"
/>

View File

@@ -24,7 +24,7 @@ export function ChannelMessageUserMute({
<>
<div className="relative h-11 w-11 shrink-0 rounded-md">
<Image
src={user?.picture || DEFAULT_AVATAR}
src={user?.image || DEFAULT_AVATAR}
alt={pubkey}
className="h-11 w-11 rounded-md object-cover"
/>

View File

@@ -17,7 +17,7 @@ export function UserReply({ pubkey }: { pubkey: string }) {
<>
<div className="relative h-9 w-9 shrink overflow-hidden rounded">
<Image
src={user?.picture || DEFAULT_AVATAR}
src={user?.image || DEFAULT_AVATAR}
alt={pubkey}
className="h-9 w-9 rounded object-cover"
/>

View File

@@ -22,7 +22,7 @@ export function ChannelMetadata({
<div className="flex flex-col gap-2">
<div className="relative shrink-0 rounded-md h-11 w-11">
<Image
src={metadata?.picture || DEFAULT_AVATAR}
src={metadata?.image || DEFAULT_AVATAR}
alt={id}
className="h-11 w-11 rounded-md object-contain bg-zinc-900"
/>

View File

@@ -41,14 +41,14 @@ export function MutedItem({ data }: { data: any }) {
<div className="flex items-center gap-1.5">
<div className="relative h-9 w-9 shrink rounded-md">
<Image
src={user?.picture || DEFAULT_AVATAR}
src={user?.image || DEFAULT_AVATAR}
alt={data.content}
className="h-9 w-9 rounded-md object-cover"
/>
</div>
<div className="flex w-full flex-1 flex-col items-start gap-0.5 text-start">
<span className="truncate text-base font-medium leading-none text-white">
{user?.display_name || user?.name || "Pleb"}
{user?.displayName || user?.name || "Pleb"}
</span>
<span className="text-base leading-none text-zinc-400">
{shortenKey(data.content)}

View File

@@ -1,18 +1,18 @@
import { Dialog, Transition } from "@headlessui/react";
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { AvatarUploader } from "@shared/avatarUploader";
import { CancelIcon, EditIcon } from "@shared/icons";
import { Image } from "@shared/image";
import { RelayContext } from "@shared/relayProvider";
import { useActiveAccount } from "@stores/accounts";
import { DEFAULT_AVATAR, WRITEONLY_RELAYS } from "@stores/constants";
import { DEFAULT_AVATAR } from "@stores/constants";
import { dateToUnix } from "@utils/date";
import { getChannel } from "@utils/storage";
import { getEventHash, getSignature } from "nostr-tools";
import { Fragment, useContext, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
export function ChannelUpdateModal({ id }: { id: string }) {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const account = useActiveAccount((state: any) => state.account);
const [isOpen, setIsOpen] = useState(false);
@@ -38,7 +38,7 @@ export function ChannelUpdateModal({ id }: { id: string }) {
const channel = await getChannel(id);
const metadata = JSON.parse(channel.metadata);
// update image state
setImage(metadata.picture);
setImage(metadata.image);
// set default values
return metadata;
},
@@ -47,28 +47,28 @@ export function ChannelUpdateModal({ id }: { id: string }) {
const onSubmit = (data: any) => {
setLoading(true);
if (account) {
const event: any = {
content: JSON.stringify(data),
created_at: dateToUnix(),
kind: 41,
pubkey: account.pubkey,
tags: [["e", id]],
};
try {
const signer = new NDKPrivateKeySigner(account.privkey);
ndk.signer = signer;
event.id = getEventHash(event);
event.sig = getSignature(event, account.privkey);
const event = new NDKEvent(ndk);
// build event
event.content = JSON.stringify(data);
event.kind = 41;
event.created_at = dateToUnix();
event.pubkey = account.pubkey;
event.tags = [["e", id]];
// publish channel
pool.publish(event, WRITEONLY_RELAYS);
// publish event
event.publish();
// reset form
reset();
// close modal
setIsOpen(false);
setLoading(false);
} else {
console.log("error");
} catch (e) {
console.log("error: ", e);
}
};

View File

@@ -7,7 +7,6 @@ import { ChannelUpdateModal } from "@app/channel/components/updateModal";
import { RelayContext } from "@shared/relayProvider";
import { useActiveAccount } from "@stores/accounts";
import { useChannelMessages } from "@stores/channels";
import { READONLY_RELAYS } from "@stores/constants";
import { dateToUnix, getHourAgo } from "@utils/date";
import { usePageContext } from "@utils/hooks/usePageContext";
import { getActiveBlacklist, getBlacklist } from "@utils/storage";
@@ -29,7 +28,7 @@ const fetchHided = async ([, id]) => {
};
export function Page() {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const pageContext = usePageContext();
const searchParams: any = pageContext.urlParsed.search;
@@ -57,40 +56,36 @@ export function Page() {
account && channelID && muted && hided ? ["channel", channelID] : null,
() => {
// subscribe to channel
const unsubscribe = pool.subscribe(
[
{
"#e": [channelID],
kinds: [42],
since: dateToUnix(getHourAgo(24, now.current)),
limit: 20,
},
],
READONLY_RELAYS,
(event: { id: string; pubkey: string }) => {
const message: any = event;
const sub = ndk.subscribe({
"#e": [channelID],
kinds: [42],
since: dateToUnix(getHourAgo(24, now.current)),
limit: 20,
});
// handle hide message
if (hided.includes(event.id)) {
message["hide"] = true;
} else {
message["hide"] = false;
}
sub.addListener("event", (event: { id: string; pubkey: string }) => {
const message: any = event;
// handle mute user
if (muted.array.includes(event.pubkey)) {
message["mute"] = true;
} else {
message["mute"] = false;
}
// handle hide message
if (hided.includes(event.id)) {
message["hide"] = true;
} else {
message["hide"] = false;
}
// add to store
addMessage(message);
},
);
// handle mute user
if (muted.array.includes(event.pubkey)) {
message["mute"] = true;
} else {
message["mute"] = false;
}
// add to store
addMessage(message);
});
return () => {
unsubscribe();
sub.stop();
clear();
};
},

View File

@@ -34,7 +34,7 @@ export function ChatsListItem({ data }: { data: any }) {
>
<div className="relative h-5 w-5 shrink-0 rounded">
<Image
src={user?.picture || DEFAULT_AVATAR}
src={user?.image || DEFAULT_AVATAR}
alt={data.sender_pubkey}
className="h-5 w-5 rounded bg-white object-cover"
/>
@@ -42,7 +42,9 @@ export function ChatsListItem({ data }: { data: any }) {
<div className="w-full inline-flex items-center justify-between">
<div className="inline-flex items-baseline gap-1">
<h5 className="max-w-[9rem] truncate font-medium text-zinc-200 group-hover:text-white">
{user?.nip05 || user?.name || shortenKey(data.sender_pubkey)}
{user?.nip05 ||
user?.displayName ||
shortenKey(data.sender_pubkey)}
</h5>
</div>
<div className="flex items-center">

View File

@@ -1,10 +1,10 @@
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { EnterIcon } from "@shared/icons";
import { MediaUploader } from "@shared/mediaUploader";
import { RelayContext } from "@shared/relayProvider";
import { useChatMessages } from "@stores/chats";
import { WRITEONLY_RELAYS } from "@stores/constants";
import { dateToUnix } from "@utils/date";
import { getEventHash, getSignature, nip04 } from "nostr-tools";
import { nip04 } from "nostr-tools";
import { useCallback, useContext, useState } from "react";
export function ChatMessageForm({
@@ -12,8 +12,9 @@ export function ChatMessageForm({
userPubkey,
userPrivkey,
}: { receiverPubkey: string; userPubkey: string; userPrivkey: string }) {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const addMessage = useChatMessages((state: any) => state.add);
const [value, setValue] = useState("");
const encryptMessage = useCallback(async () => {
@@ -23,19 +24,19 @@ export function ChatMessageForm({
const submit = async () => {
const message = await encryptMessage();
const event: any = {
content: message,
created_at: dateToUnix(),
kind: 4,
pubkey: userPubkey,
tags: [["p", receiverPubkey]],
};
const signer = new NDKPrivateKeySigner(userPrivkey);
ndk.signer = signer;
event.id = getEventHash(event);
event.sig = getSignature(event, userPrivkey);
const event = new NDKEvent(ndk);
// build event
event.content = message;
event.kind = 4;
event.created_at = dateToUnix();
event.pubkey = userPubkey;
event.tags = [["p", receiverPubkey]];
// publish message
pool.publish(event, WRITEONLY_RELAYS);
// publish event
event.publish();
// add message to store
addMessage(receiverPubkey, event);

View File

@@ -28,7 +28,7 @@ export function ChatMessageUser({
<>
<div className="relative h-11 w-11 shrink rounded-md">
<Image
src={user?.picture || DEFAULT_AVATAR}
src={user?.image || DEFAULT_AVATAR}
alt={pubkey}
className="h-11 w-11 rounded-md object-cover"
/>

View File

@@ -34,7 +34,7 @@ export function ChatsListSelfItem({ data }: { data: any }) {
>
<div className="relative h-5 w-5 shrink-0 rounded">
<Image
src={user?.picture || DEFAULT_AVATAR}
src={user?.image || DEFAULT_AVATAR}
alt={data.pubkey}
className="h-5 w-5 rounded bg-white object-cover"
/>

View File

@@ -11,7 +11,7 @@ export function ChatSidebar({ pubkey }: { pubkey: string }) {
<div className="flex flex-col gap-3">
<div className="relative h-11 w-11 shrink rounded-md">
<Image
src={user?.picture || DEFAULT_AVATAR}
src={user?.image || DEFAULT_AVATAR}
alt={pubkey}
className="h-11 w-11 rounded-md object-cover"
/>
@@ -19,10 +19,10 @@ export function ChatSidebar({ pubkey }: { pubkey: string }) {
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-1">
<h3 className="leading-none text-lg font-semibold">
{user?.display_name || user?.name}
{user?.displayName || user?.name}
</h3>
<h5 className="leading-none text-zinc-400">
{user?.nip05 || user?.username || shortenKey(pubkey)}
{user?.nip05 || shortenKey(pubkey)}
</h5>
</div>
<div>

View File

@@ -4,14 +4,13 @@ import { ChatMessageForm } from "@app/chat/components/messages/form";
import { RelayContext } from "@shared/relayProvider";
import { useActiveAccount } from "@stores/accounts";
import { useChatMessages } from "@stores/chats";
import { READONLY_RELAYS } from "@stores/constants";
import { dateToUnix } from "@utils/date";
import { usePageContext } from "@utils/hooks/usePageContext";
import { useContext, useEffect } from "react";
import useSWRSubscription from "swr/subscription";
export function Page() {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const account = useActiveAccount((state: any) => state.account);
const pageContext = usePageContext();
@@ -25,23 +24,19 @@ export function Page() {
const add = useChatMessages((state: any) => state.add);
useSWRSubscription(account !== pubkey ? ["chat", pubkey] : null, () => {
const unsubscribe = pool.subscribe(
[
{
kinds: [4],
authors: [pubkey],
"#p": [account.pubkey],
since: dateToUnix(),
},
],
READONLY_RELAYS,
(event: any) => {
add(account.pubkey, event);
},
);
const sub = ndk.subscribe({
kinds: [4],
authors: [pubkey],
"#p": [account.pubkey],
since: dateToUnix(),
});
sub.addListener("event", (event: any) => {
add(account.pubkey, event);
});
return () => {
unsubscribe();
sub.stop();
};
});

View File

@@ -14,7 +14,7 @@ export function MentionUser(props: { children: any[] }) {
return (
<span className="text-fuchsia-500">
@{user?.name || user?.display_name || shortenKey(pubkey)}
@{user?.name || user?.displayName || shortenKey(pubkey)}
</span>
);
}

View File

@@ -1,9 +1,9 @@
import { NoteReply } from "@app/note/components/metadata/reply";
import { NoteRepost } from "@app/note/components/metadata/repost";
import { NoteZap } from "@app/note/components/metadata/zap";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { NDKSubscription } from "@nostr-dev-kit/ndk";
import { RelayContext } from "@shared/relayProvider";
import { useActiveAccount } from "@stores/accounts";
import { READONLY_RELAYS } from "@stores/constants";
import { createReplyNote } from "@utils/storage";
import { decode } from "light-bolt11-decoder";
import { useContext, useState } from "react";
@@ -16,65 +16,57 @@ export function NoteMetadata({
id: string;
eventPubkey: string;
}) {
const pool: any = useContext(RelayContext);
const account = useActiveAccount((state: any) => state.account);
const ndk = useContext(RelayContext);
const [replies, setReplies] = useState(0);
const [reposts, setReposts] = useState(0);
const [zaps, setZaps] = useState(0);
useSWRSubscription(id ? ["note-metadata", id] : null, ([, key]) => {
const unsubscribe = pool.subscribe(
[
{
"#e": [key],
kinds: [1, 6, 9735],
limit: 20,
},
],
READONLY_RELAYS,
(event: any) => {
switch (event.kind) {
case 1:
setReplies((replies) => replies + 1);
createReplyNote(
event.id,
account.id,
event.pubkey,
event.kind,
event.tags,
event.content,
event.created_at,
key,
);
break;
case 6:
setReposts((reposts) => reposts + 1);
break;
case 9735: {
const bolt11 = event.tags.find((tag) => tag[0] === "bolt11")[1];
if (bolt11) {
const decoded = decode(bolt11);
const amount = decoded.sections.find(
(item) => item.name === "amount",
);
setZaps(amount.value / 1000);
}
break;
}
default:
break;
}
},
undefined,
undefined,
useSWRSubscription(id ? ["note-metadata", id] : null, () => {
const sub: NDKSubscription = ndk.subscribe(
{
unsubscribeOnEose: true,
"#e": [id],
kinds: [1, 6, 9735],
limit: 20,
},
{ closeOnEose: false },
);
sub.addListener("event", (event: NDKEvent) => {
switch (event.kind) {
case 1:
setReplies((replies) => replies + 1);
createReplyNote(
event.id,
event.pubkey,
event.kind,
event.tags,
event.content,
event.created_at,
id,
);
break;
case 6:
setReposts((reposts) => reposts + 1);
break;
case 9735: {
const bolt11 = event.tags.find((tag) => tag[0] === "bolt11")[1];
if (bolt11) {
const decoded = decode(bolt11);
const amount = decoded.sections.find(
(item) => item.name === "amount",
);
setZaps(amount.value / 1000);
}
break;
}
default:
break;
}
});
return () => {
unsubscribe();
sub.stop();
};
});

View File

@@ -1,62 +0,0 @@
import { LikeIcon } from "@shared/icons";
import { RelayContext } from "@shared/relayProvider";
import { useActiveAccount } from "@stores/accounts";
import { WRITEONLY_RELAYS } from "@stores/constants";
import { dateToUnix } from "@utils/date";
import { getEventHash, getSignature } from "nostr-tools";
import { useContext, useEffect, useState } from "react";
export function NoteLike({
id,
pubkey,
likes,
}: { id: string; pubkey: string; likes: number }) {
const pool: any = useContext(RelayContext);
const account = useActiveAccount((state: any) => state.account);
const [count, setCount] = useState(0);
const submitEvent = (e: any) => {
e.stopPropagation();
const event: any = {
content: "+",
kind: 7,
tags: [
["e", id],
["p", pubkey],
],
created_at: dateToUnix(),
pubkey: account.pubkey,
};
event.id = getEventHash(event);
event.sig = getSignature(event, account.privkey);
// publish event to all relays
pool.publish(event, WRITEONLY_RELAYS);
// update state
setCount(count + 1);
};
useEffect(() => {
setCount(likes);
}, [likes]);
return (
<button
type="button"
onClick={(e) => submitEvent(e)}
className="group inline-flex items-center gap-1.5"
>
<LikeIcon
width={16}
height={16}
className="text-zinc-400 group-hover:text-rose-400"
/>
<span className="text-base leading-none text-zinc-400 group-hover:text-white">
{count}
</span>
</button>
);
}

View File

@@ -1,16 +1,15 @@
import { Dialog, Transition } from "@headlessui/react";
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { ReplyIcon } from "@shared/icons";
import { Image } from "@shared/image";
import { RelayContext } from "@shared/relayProvider";
import { useActiveAccount } from "@stores/accounts";
import { WRITEONLY_RELAYS } from "@stores/constants";
import { dateToUnix } from "@utils/date";
import { compactNumber } from "@utils/number";
import { getEventHash, getSignature } from "nostr-tools";
import { Fragment, useContext, useEffect, useState } from "react";
export function NoteReply({ id, replies }: { id: string; replies: number }) {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const account = useActiveAccount((state: any) => state.account);
const [count, setCount] = useState(0);
@@ -26,19 +25,19 @@ export function NoteReply({ id, replies }: { id: string; replies: number }) {
};
const submitEvent = () => {
const event: any = {
content: value,
created_at: dateToUnix(),
kind: 1,
pubkey: account.pubkey,
tags: [["e", id]],
};
const signer = new NDKPrivateKeySigner(account.privkey);
ndk.signer = signer;
event.id = getEventHash(event);
event.sig = getSignature(event, account.privkey);
const event = new NDKEvent(ndk);
// build event
event.content = value;
event.kind = 1;
event.created_at = dateToUnix();
event.pubkey = account.pubkey;
event.tags = [["e", id]];
// publish event
pool.publish(event, WRITEONLY_RELAYS);
event.publish();
// close modal
setIsOpen(false);
@@ -96,7 +95,7 @@ export function NoteReply({ id, replies }: { id: string; replies: number }) {
<div>
<div className="relative h-11 w-11 shrink-0 overflow-hidden rounded-md border border-white/10">
<Image
src={account?.picture}
src={account?.image}
alt="user's avatar"
className="h-11 w-11 rounded-md object-cover"
/>

View File

@@ -1,10 +1,9 @@
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { RepostIcon } from "@shared/icons";
import { RelayContext } from "@shared/relayProvider";
import { useActiveAccount } from "@stores/accounts";
import { WRITEONLY_RELAYS } from "@stores/constants";
import { dateToUnix } from "@utils/date";
import { compactNumber } from "@utils/number";
import { getEventHash, getSignature } from "nostr-tools";
import { useContext, useEffect, useState } from "react";
export function NoteRepost({
@@ -12,7 +11,7 @@ export function NoteRepost({
pubkey,
reposts,
}: { id: string; pubkey: string; reposts: number }) {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const account = useActiveAccount((state: any) => state.account);
const [count, setCount] = useState(0);
@@ -20,22 +19,22 @@ export function NoteRepost({
const submitEvent = (e: any) => {
e.stopPropagation();
const event: any = {
content: "",
kind: 6,
tags: [
["e", id],
["p", pubkey],
],
created_at: dateToUnix(),
pubkey: account.pubkey,
};
const signer = new NDKPrivateKeySigner(account.privkey);
ndk.signer = signer;
event.id = getEventHash(event);
event.sig = getSignature(event, account.privkey);
const event = new NDKEvent(ndk);
// build event
event.content = "";
event.kind = 6;
event.created_at = dateToUnix();
event.pubkey = account.pubkey;
event.tags = [
["e", id],
["p", pubkey],
];
// publish event to all relays
pool.publish(event, WRITEONLY_RELAYS);
// publish event
event.publish();
// update state
setCount(count + 1);

View File

@@ -17,11 +17,13 @@ export function LinkPreview({ urls }: { urls: string[] }) {
target="_blank"
rel="noreferrer"
>
<Image
src={data["og:image"]}
alt={urls[0]}
className="w-full h-auto border-t-lg object-cover"
/>
{data["og:image"] && (
<Image
src={data["og:image"]}
alt={urls[0]}
className="w-full h-auto border-t-lg object-cover"
/>
)}
<div className="flex flex-col gap-2 px-3 py-3">
<h5 className="leading-none font-medium text-zinc-200">
{data["og:title"]}

View File

@@ -1,32 +1,29 @@
import { Image } from "@shared/image";
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { RelayContext } from "@shared/relayProvider";
import { useActiveAccount } from "@stores/accounts";
import { WRITEONLY_RELAYS } from "@stores/constants";
import { dateToUnix } from "@utils/date";
import { useProfile } from "@utils/hooks/useProfile";
import { getEventHash, getSignature } from "nostr-tools";
import { useContext, useState } from "react";
export function NoteReplyForm({ id }: { id: string }) {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const account = useActiveAccount((state: any) => state.account);
const [value, setValue] = useState("");
const submitEvent = () => {
const event: any = {
content: value,
created_at: dateToUnix(),
kind: 1,
pubkey: account.pubkey,
tags: [["e", id]],
};
const signer = new NDKPrivateKeySigner(account.privkey);
ndk.signer = signer;
event.id = getEventHash(event);
event.sig = getSignature(event, account.privkey);
const event = new NDKEvent(ndk);
// build event
event.content = value;
event.kind = 1;
event.created_at = dateToUnix();
event.pubkey = account.pubkey;
event.tags = [["e", id]];
// publish note
pool.publish(event, WRITEONLY_RELAYS);
// publish event
event.publish();
// reset form
setValue("");

View File

@@ -1,34 +1,35 @@
import { NoteReplyForm } from "@app/note/components/replies/form";
import { Reply } from "@app/note/components/replies/item";
import { NostrEvent } from "@nostr-dev-kit/ndk";
import { RelayContext } from "@shared/relayProvider";
import { READONLY_RELAYS } from "@stores/constants";
import { sortEvents } from "@utils/transform";
import { useContext } from "react";
import useSWRSubscription from "swr/subscription";
export function RepliesList({ id }: { id: string }) {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const { data, error } = useSWRSubscription(
id ? ["note-replies", id] : null,
([, key], { next }) => {
// subscribe to note
const unsubscribe = pool.subscribe(
[
{
"#e": [key],
kinds: [1],
limit: 20,
},
],
READONLY_RELAYS,
(event: any) => {
next(null, (prev: any) => (prev ? [...prev, event] : [event]));
const sub = ndk.subscribe(
{
"#e": [key],
kinds: [1],
limit: 20,
},
{
closeOnEose: true,
},
);
sub.addListener("event", (event: NostrEvent) => {
next(null, (prev: any) => (prev ? [...prev, event] : [event]));
});
return () => {
unsubscribe();
sub.stop();
};
},
);

View File

@@ -3,8 +3,8 @@ import { Kind1063 } from "@app/note/components/kind1063";
import { NoteMetadata } from "@app/note/components/metadata";
import { NoteSkeleton } from "@app/note/components/skeleton";
import { NoteDefaultUser } from "@app/note/components/user/default";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { RelayContext } from "@shared/relayProvider";
import { READONLY_RELAYS } from "@stores/constants";
import { noteParser } from "@utils/parser";
import { memo, useContext } from "react";
import useSWRSubscription from "swr/subscription";
@@ -23,31 +23,22 @@ export const RootNote = memo(function RootNote({
id,
fallback,
}: { id: string; fallback?: any }) {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const parseFallback = isJSON(fallback) ? JSON.parse(fallback) : null;
const { data, error } = useSWRSubscription(
parseFallback ? null : id,
(key, { next }) => {
const unsubscribe = pool.subscribe(
[
{
ids: [key],
},
],
READONLY_RELAYS,
(event: any) => {
next(null, event);
},
undefined,
undefined,
{
unsubscribeOnEose: true,
},
);
const sub = ndk.subscribe({
ids: [key],
});
sub.addListener("event", (event: NDKEvent) => {
next(null, event);
});
return () => {
unsubscribe();
sub.stop();
};
},
);

View File

@@ -19,7 +19,7 @@ export function NoteDefaultUser({
<Popover className="relative flex items-start gap-3">
<Popover.Button className="h-11 w-11 shrink-0 overflow-hidden rounded-md bg-zinc-900">
<Image
src={user?.picture || DEFAULT_AVATAR}
src={user?.image || DEFAULT_AVATAR}
alt={pubkey}
className="h-11 w-11 object-cover"
/>
@@ -50,14 +50,14 @@ export function NoteDefaultUser({
>
<div className="flex items-start gap-2.5 border-b border-zinc-800 px-3 py-3">
<Image
src={user?.picture || DEFAULT_AVATAR}
src={user?.image || DEFAULT_AVATAR}
alt={pubkey}
className="h-14 w-14 shrink-0 rounded-lg object-cover"
/>
<div className="flex w-full flex-1 flex-col gap-2">
<div className="inline-flex w-2/3 flex-col gap-0.5">
<h5 className="text-base font-semibold leading-none">
{user?.display_name || user?.name || (
{user?.displayName || user?.name || (
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
)}
</h5>

View File

@@ -20,7 +20,7 @@ export function NoteQuoteUser({
<div className="group flex items-center gap-2">
<div className="relative h-6 w-6 shrink-0 rounded">
<Image
src={user?.picture || DEFAULT_AVATAR}
src={user?.image || DEFAULT_AVATAR}
alt={pubkey}
className="h-6 w-6 rounded object-cover"
/>

View File

@@ -20,7 +20,7 @@ export function NoteReplyUser({
<div className="group flex items-start gap-2.5">
<div className="relative h-11 w-11 shrink-0 rounded-md">
<Image
src={user?.picture || DEFAULT_AVATAR}
src={user?.image || DEFAULT_AVATAR}
alt={pubkey}
className="h-11 w-11 rounded-md object-cover"
/>

View File

@@ -19,7 +19,7 @@ export function NoteRepostUser({
<Popover className="relative flex items-start gap-3">
<Popover.Button className="h-11 w-11 shrink-0 overflow-hidden rounded-md bg-zinc-900">
<Image
src={user?.picture || DEFAULT_AVATAR}
src={user?.image || DEFAULT_AVATAR}
alt={pubkey}
className="h-11 w-11 rounded-md object-cover"
/>
@@ -54,14 +54,14 @@ export function NoteRepostUser({
>
<div className="flex items-start gap-2.5 border-b border-zinc-800 px-3 py-3">
<Image
src={user?.picture || DEFAULT_AVATAR}
src={user?.image || DEFAULT_AVATAR}
alt={pubkey}
className="h-14 w-14 shrink-0 rounded-lg object-cover"
/>
<div className="flex w-full flex-1 flex-col gap-2">
<div className="inline-flex w-2/3 flex-col gap-0.5">
<h5 className="text-base font-semibold leading-none">
{user?.display_name || user?.name || (
{user?.displayName || user?.name || (
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
)}
</h5>

View File

@@ -2,41 +2,19 @@ import { Kind1 } from "@app/note/components/kind1";
import { NoteMetadata } from "@app/note/components/metadata";
import { RepliesList } from "@app/note/components/replies/list";
import { NoteDefaultUser } from "@app/note/components/user/default";
import { RelayContext } from "@shared/relayProvider";
import { READONLY_RELAYS } from "@stores/constants";
import { usePageContext } from "@utils/hooks/usePageContext";
import { noteParser } from "@utils/parser";
import { useContext } from "react";
import useSWRSubscription from "swr/subscription";
import { getNoteByID } from "@utils/storage";
import useSWR from "swr";
const fetcher = ([, id]) => getNoteByID(id);
export function Page() {
const pool: any = useContext(RelayContext);
const pageContext = usePageContext();
const searchParams: any = pageContext.urlParsed.search;
const noteID = searchParams.id;
const { data, error } = useSWRSubscription(
noteID ? ["note", noteID] : null,
([, key], { next }) => {
// subscribe to note
const unsubscribe = pool.subscribe(
[
{
ids: [key],
},
],
READONLY_RELAYS,
(event: any) => {
next(null, event);
},
);
return () => {
unsubscribe();
};
},
);
const { data, error } = useSWR(["note", noteID], fetcher);
const content = !error && data ? noteParser(data) : null;

View File

@@ -1,7 +1,7 @@
import { NDKFilter } from "@nostr-dev-kit/ndk";
import { LumeIcon } from "@shared/icons";
import { RelayContext } from "@shared/relayProvider";
import { useActiveAccount } from "@stores/accounts";
import { READONLY_RELAYS } from "@stores/constants";
import { dateToUnix, getHourAgo } from "@utils/date";
import {
addToBlacklist,
@@ -9,9 +9,7 @@ import {
createChat,
createNote,
} from "@utils/storage";
import { getParentID } from "@utils/transform";
import { useCallback, useContext, useRef } from "react";
import useSWRSubscription from "swr/subscription";
import { useContext, useEffect, useRef } from "react";
import { navigate } from "vite-plugin-ssr/client/router";
let totalNotes: number;
@@ -21,127 +19,69 @@ if (typeof window !== "undefined") {
}
export function Page() {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const now = useRef(new Date());
const [account, lastLogin] = useActiveAccount((state: any) => [
state.account,
state.lastLogin,
]);
const now = useRef(new Date());
const eose = useRef(0);
async function fetchNotes() {
try {
const follows = JSON.parse(account.follows);
let queryNoteSince: number;
const getQuery = useCallback(() => {
const query = [];
const follows = JSON.parse(account.follows);
let queryNoteSince: number;
if (totalNotes === 0) {
queryNoteSince = dateToUnix(getHourAgo(48, now.current));
} else {
if (lastLogin > 0) {
queryNoteSince = lastLogin;
} else {
if (totalNotes === 0) {
queryNoteSince = dateToUnix(getHourAgo(48, now.current));
} else {
if (lastLogin > 0) {
queryNoteSince = lastLogin;
} else {
queryNoteSince = dateToUnix(getHourAgo(48, now.current));
}
}
const filter: NDKFilter = {
kinds: [1, 6],
authors: follows,
since: queryNoteSince,
};
const events = await ndk.fetchEvents(filter);
events.forEach((event) => {
createNote(
event.id,
event.pubkey,
event.kind,
event.tags,
event.content,
event.created_at,
);
});
return true;
} catch (e) {
console.log("error: ", e);
}
}
// kind 1 (notes) query
query.push({
kinds: [1, 6],
authors: follows,
since: queryNoteSince,
});
async function fetchChannelBlacklist() {
try {
const filter: NDKFilter = {
authors: [account.pubkey],
kinds: [43, 44],
since: lastLogin,
};
// kind 4 (chats) query
query.push({
kinds: [4],
"#p": [account.pubkey],
since: lastLogin,
});
// kind 4 (chats) query
query.push({
kinds: [4],
authors: [account.pubkey],
since: lastLogin,
});
// kind 43, 43 (mute user, hide message) query
query.push({
authors: [account.pubkey],
kinds: [43, 44],
since: lastLogin,
});
return query;
}, [account]);
useSWRSubscription(account ? "prefetch" : null, () => {
let timeout: string | number | NodeJS.Timeout;
const query = getQuery();
const unsubscribe = pool.subscribe(
query,
READONLY_RELAYS,
(event: any) => {
const events = await ndk.fetchEvents(filter);
events.forEach((event) => {
switch (event.kind) {
// short text note
case 1: {
// insert event to local database
createNote(
event.id,
account.id,
event.pubkey,
event.kind,
event.tags,
event.content,
event.created_at,
);
break;
}
// chat
case 4: {
if (event.pubkey === account.pubkey) {
const receiver = event.tags.find((t) => t[0] === "p")[1];
createChat(
event.id,
receiver,
event.pubkey,
event.content,
event.tags,
event.created_at,
);
} else {
createChat(
event.id,
account.pubkey,
event.pubkey,
event.content,
event.tags,
event.created_at,
);
}
break;
}
// repost
case 6:
createNote(
event.id,
account.id,
event.pubkey,
event.kind,
event.tags,
event.content,
event.created_at,
);
break;
// hide message (channel only)
case 43:
if (event.tags[0][0] === "e") {
addToBlacklist(account.id, event.tags[0][1], 43, 1);
}
break;
// mute user (channel only)
case 44:
if (event.tags[0][0] === "p") {
addToBlacklist(account.id, event.tags[0][1], 44, 1);
@@ -150,37 +90,85 @@ export function Page() {
default:
break;
}
},
undefined,
() => {
eose.current += 1;
if (eose.current === READONLY_RELAYS.length) {
timeout = setTimeout(
() => navigate("/app/space", { overwriteLastHistoryEntry: true }),
2000,
);
}
},
{
unsubscribeOnEose: true,
},
);
});
return () => {
unsubscribe();
clearTimeout(timeout);
};
});
return true;
} catch (e) {
console.log("error: ", e);
}
}
async function fetchReceiveMessages() {
try {
const filter: NDKFilter = {
kinds: [4],
"#p": [account.pubkey],
since: lastLogin,
};
const events = await ndk.fetchEvents(filter);
events.forEach((event) => {
createChat(
event.id,
account.pubkey,
event.pubkey,
event.content,
event.tags,
event.created_at,
);
});
return true;
} catch (e) {
console.log("error: ", e);
}
}
async function fetchSendMessages() {
try {
const filter: NDKFilter = {
kinds: [4],
authors: [account.pubkey],
since: lastLogin,
};
const events = await ndk.fetchEvents(filter);
events.forEach((event) => {
const receiver = event.tags.find((t) => t[0] === "p")[1];
createChat(
event.id,
receiver,
account.pubkey,
event.content,
event.tags,
event.created_at,
);
});
return true;
} catch (e) {
console.log("error: ", e);
}
}
useEffect(() => {
async function prefetch() {
const notes = await fetchNotes();
if (notes) {
navigate("/app/space", { overwriteLastHistoryEntry: true });
}
}
prefetch();
}, []);
return (
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-black dark:text-white">
<div className="relative h-full overflow-hidden">
{/* dragging area */}
<div
data-tauri-drag-region
className="absolute left-0 top-0 z-20 h-16 w-full bg-transparent"
/>
{/* end dragging area */}
<div className="relative flex h-full flex-col items-center justify-center">
<div className="flex flex-col items-center gap-2">
<LumeIcon className="h-16 w-16 text-black dark:text-white" />

View File

@@ -1,24 +1,23 @@
import { Dialog, Transition } from "@headlessui/react";
import { CancelIcon, ImageIcon } from "@shared/icons";
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { CancelIcon } from "@shared/icons";
import { Image } from "@shared/image";
import { RelayContext } from "@shared/relayProvider";
import { useActiveAccount } from "@stores/accounts";
import { WRITEONLY_RELAYS } from "@stores/constants";
import { open } from "@tauri-apps/api/dialog";
import { Body, fetch } from "@tauri-apps/api/http";
import { createBlobFromFile } from "@utils/createBlobFromFile";
import { dateToUnix } from "@utils/date";
import { getEventHash, getSignature } from "nostr-tools";
import { Fragment, useContext, useEffect, useRef, useState } from "react";
import { useForm } from "react-hook-form";
export function AddImageBlock({ parentState }: { parentState: any }) {
const pool: any = useContext(RelayContext);
const ndk = useContext(RelayContext);
const [account, addBlock] = useActiveAccount((state: any) => [
state.account,
state.addBlock,
]);
const [loading, setLoading] = useState(false);
const [isOpen, setIsOpen] = useState(true);
const [image, setImage] = useState("");
@@ -91,19 +90,19 @@ export function AddImageBlock({ parentState }: { parentState: any }) {
const onSubmit = (data: any) => {
setLoading(true);
const event: any = {
content: data.title,
created_at: dateToUnix(),
kind: 1063,
pubkey: account.pubkey,
tags: tags.current,
};
const signer = new NDKPrivateKeySigner(account.privkey);
ndk.signer = signer;
event.id = getEventHash(event);
event.sig = getSignature(event, account.privkey);
const event = new NDKEvent(ndk);
// build event
event.content = data.title;
event.kind = 1063;
event.created_at = dateToUnix();
event.pubkey = account.pubkey;
event.tags = tags.current;
// publish channel
pool.publish(event, WRITEONLY_RELAYS);
// publish event
event.publish();
// insert to database
addBlock(0, data.title, data.content);