mirror of
https://github.com/lumehq/lume.git
synced 2025-03-17 21:32:32 +01:00
migrate to ndk
This commit is contained in:
parent
75a33d205a
commit
0ba9877785
@ -15,6 +15,7 @@
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.23.1",
|
||||
"@headlessui/react": "^1.7.15",
|
||||
"@nostr-dev-kit/ndk": "^0.4.4",
|
||||
"@tanstack/react-virtual": "3.0.0-beta.54",
|
||||
"@tauri-apps/api": "^1.3.0",
|
||||
"@vidstack/react": "^0.4.5",
|
||||
@ -29,7 +30,7 @@
|
||||
"react-hook-form": "^7.44.3",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-resizable-panels": "^0.0.48",
|
||||
"react-virtuoso": "^4.3.8",
|
||||
"react-virtuoso": "^4.3.9",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"slate": "^0.94.1",
|
||||
"slate-history": "^0.93.0",
|
||||
@ -44,7 +45,7 @@
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@tauri-apps/cli": "^1.3.1",
|
||||
"@types/node": "^18.16.16",
|
||||
"@types/react": "^18.2.8",
|
||||
"@types/react": "^18.2.9",
|
||||
"@types/react-dom": "^18.2.4",
|
||||
"@types/youtube-player": "^5.5.7",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
|
2220
pnpm-lock.yaml
generated
2220
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -19,17 +19,16 @@ CREATE TABLE
|
||||
plebs (
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
npub TEXT NOT NULL UNIQUE,
|
||||
display_name TEXT,
|
||||
name TEXT,
|
||||
username TEXT,
|
||||
about TEXT,
|
||||
bio TEXT,
|
||||
website TEXT,
|
||||
picture TEXT,
|
||||
displayName TEXT,
|
||||
image TEXT,
|
||||
banner TEXT,
|
||||
bio TEXT,
|
||||
nip05 TEXT,
|
||||
lud06 TEXT,
|
||||
lud16 TEXT,
|
||||
about TEXT,
|
||||
zapService TEXT,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
|
@ -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)}
|
||||
|
@ -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
|
||||
|
@ -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 (
|
||||
|
@ -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 (
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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"}
|
||||
/>
|
||||
)}
|
||||
|
@ -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("");
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
/>
|
||||
|
@ -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"
|
||||
/>
|
||||
|
@ -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"
|
||||
/>
|
||||
|
@ -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"
|
||||
/>
|
||||
|
@ -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)}
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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();
|
||||
};
|
||||
},
|
||||
|
@ -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">
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
/>
|
||||
|
@ -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"
|
||||
/>
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
@ -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"
|
||||
/>
|
||||
|
@ -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);
|
||||
|
@ -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"]}
|
||||
|
@ -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("");
|
||||
|
@ -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();
|
||||
};
|
||||
},
|
||||
);
|
||||
|
@ -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();
|
||||
};
|
||||
},
|
||||
);
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
/>
|
||||
|
@ -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"
|
||||
/>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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" />
|
||||
|
@ -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);
|
||||
|
15
src/libs/ndk.tsx
Normal file
15
src/libs/ndk.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import NDK, { NDKConstructorParams } from "@nostr-dev-kit/ndk";
|
||||
import { FULL_RELAYS } from "@stores/constants";
|
||||
|
||||
export async function initNDK(
|
||||
relays?: string[],
|
||||
cache?: boolean,
|
||||
): Promise<NDK> {
|
||||
const opts: NDKConstructorParams = {};
|
||||
opts.explicitRelayUrls = relays || FULL_RELAYS;
|
||||
|
||||
const ndk = new NDK(opts);
|
||||
await ndk.connect();
|
||||
|
||||
return ndk;
|
||||
}
|
@ -3,17 +3,15 @@ import { RelayContext } from "@shared/relayProvider";
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { useChannels } from "@stores/channels";
|
||||
import { useChatMessages, useChats } from "@stores/chats";
|
||||
import { DEFAULT_AVATAR, READONLY_RELAYS } from "@stores/constants";
|
||||
import { dateToUnix } from "@utils/date";
|
||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||
import { usePageContext } from "@utils/hooks/usePageContext";
|
||||
import { useProfile } from "@utils/hooks/useProfile";
|
||||
import { sendNativeNotification } from "@utils/notification";
|
||||
import { createNote } from "@utils/storage";
|
||||
import { useContext } from "react";
|
||||
import useSWRSubscription from "swr/subscription";
|
||||
|
||||
export function ActiveAccount({ data }: { data: any }) {
|
||||
const pool: any = useContext(RelayContext);
|
||||
const ndk = useContext(RelayContext);
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
|
||||
const pageContext = usePageContext();
|
||||
@ -35,60 +33,38 @@ export function ActiveAccount({ data }: { data: any }) {
|
||||
() => {
|
||||
const follows = JSON.parse(account.follows);
|
||||
// subscribe to channel
|
||||
const unsubscribe = pool.subscribe(
|
||||
[
|
||||
{
|
||||
kinds: [1, 6],
|
||||
authors: follows,
|
||||
since: dateToUnix(),
|
||||
},
|
||||
{
|
||||
"#p": [data.pubkey],
|
||||
since: lastLogin,
|
||||
},
|
||||
],
|
||||
READONLY_RELAYS,
|
||||
(event) => {
|
||||
switch (event.kind) {
|
||||
case 1:
|
||||
case 6: {
|
||||
createNote(
|
||||
event.id,
|
||||
account.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at,
|
||||
);
|
||||
break;
|
||||
const sub = ndk.subscribe({
|
||||
"#p": [data.pubkey],
|
||||
since: lastLogin,
|
||||
});
|
||||
|
||||
sub.addListener("event", (event) => {
|
||||
switch (event.kind) {
|
||||
case 4:
|
||||
if (!isChatPage) {
|
||||
// save
|
||||
saveChat(data.pubkey, event);
|
||||
// update state
|
||||
notifyChat(event.pubkey);
|
||||
// send native notifiation
|
||||
sendNativeNotification("You've received new message");
|
||||
}
|
||||
case 4:
|
||||
if (!isChatPage) {
|
||||
// save
|
||||
saveChat(data.pubkey, event);
|
||||
// update state
|
||||
notifyChat(event.pubkey);
|
||||
// send native notifiation
|
||||
sendNativeNotification("You've received new message");
|
||||
}
|
||||
break;
|
||||
case 42:
|
||||
if (!isChannelPage) {
|
||||
// update state
|
||||
notifyChannel(event);
|
||||
// send native notifiation
|
||||
sendNativeNotification(event.content);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
break;
|
||||
case 42:
|
||||
if (!isChannelPage) {
|
||||
// update state
|
||||
notifyChannel(event);
|
||||
// send native notifiation
|
||||
sendNativeNotification(event.content);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
sub.stop();
|
||||
};
|
||||
},
|
||||
);
|
||||
@ -96,7 +72,7 @@ export function ActiveAccount({ data }: { data: any }) {
|
||||
return (
|
||||
<button type="button" className="relative h-11 w-11 overflow-hidden">
|
||||
<Image
|
||||
src={user?.picture || DEFAULT_AVATAR}
|
||||
src={user?.image || DEFAULT_AVATAR}
|
||||
alt={data.npub}
|
||||
className="h-11 w-11 rounded-md object-cover"
|
||||
/>
|
||||
|
@ -9,7 +9,7 @@ export function InactiveAccount({ data }: { data: any }) {
|
||||
return (
|
||||
<div className="relative h-11 w-11 shrink rounded-md">
|
||||
<Image
|
||||
src={user?.picture || DEFAULT_AVATAR}
|
||||
src={user?.image || DEFAULT_AVATAR}
|
||||
alt={data.npub}
|
||||
className="h-11 w-11 rounded-lg object-cover"
|
||||
/>
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
|
||||
import { ImageUploader } from "@shared/composer/imageUploader";
|
||||
import { TrashIcon } from "@shared/icons";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||
import { dateToUnix } from "@utils/date";
|
||||
import { getEventHash, getSignature } from "nostr-tools";
|
||||
import { useCallback, useContext, useMemo, useState } from "react";
|
||||
import { Node, Transforms, createEditor } from "slate";
|
||||
import { withHistory } from "slate-history";
|
||||
@ -59,12 +58,13 @@ const ImagePreview = ({
|
||||
};
|
||||
|
||||
export function Post({ pubkey, privkey }: { pubkey: string; privkey: string }) {
|
||||
const pool: any = useContext(RelayContext);
|
||||
const ndk = useContext(RelayContext);
|
||||
|
||||
const editor = useMemo(
|
||||
() => withReact(withImages(withHistory(createEditor()))),
|
||||
[],
|
||||
);
|
||||
|
||||
const [content, setContent] = useState<Node[]>([
|
||||
{
|
||||
children: [
|
||||
@ -82,20 +82,19 @@ export function Post({ pubkey, privkey }: { pubkey: string; privkey: string }) {
|
||||
const submit = () => {
|
||||
// serialize content
|
||||
const serializedContent = serialize(content);
|
||||
console.log(serializedContent);
|
||||
|
||||
const event: any = {
|
||||
content: serializedContent,
|
||||
created_at: dateToUnix(),
|
||||
kind: 1,
|
||||
pubkey: pubkey,
|
||||
tags: [],
|
||||
};
|
||||
event.id = getEventHash(event);
|
||||
event.sig = getSignature(event, privkey);
|
||||
const signer = new NDKPrivateKeySigner(privkey);
|
||||
ndk.signer = signer;
|
||||
|
||||
// publish note
|
||||
pool.publish(event, WRITEONLY_RELAYS);
|
||||
const event = new NDKEvent(ndk);
|
||||
event.kind = 1;
|
||||
event.content = serializedContent;
|
||||
event.created_at = dateToUnix();
|
||||
event.pubkey = pubkey;
|
||||
event.tags = [];
|
||||
|
||||
// publish event
|
||||
event.publish();
|
||||
};
|
||||
|
||||
const renderElement = useCallback((props: any) => {
|
||||
|
@ -9,7 +9,7 @@ export function User({ pubkey }: { pubkey: string }) {
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-8 w-8 shrink-0 overflow-hidden rounded bg-zinc-900">
|
||||
<Image
|
||||
src={user?.picture || DEFAULT_AVATAR}
|
||||
src={user?.image || DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
className="h-8 w-8 object-cover"
|
||||
loading="auto"
|
||||
|
@ -1,16 +1,10 @@
|
||||
import { FULL_RELAYS } from "@stores/constants";
|
||||
import { RelayPool } from "nostr-relaypool";
|
||||
import { initNDK } from "@libs/ndk";
|
||||
import NDK from "@nostr-dev-kit/ndk";
|
||||
import { createContext } from "react";
|
||||
|
||||
export const RelayContext = createContext({});
|
||||
|
||||
const pool = new RelayPool(FULL_RELAYS, {
|
||||
useEventCache: false,
|
||||
subscriptionCache: true,
|
||||
logErrorsAndNotices: false,
|
||||
logSubscriptions: false,
|
||||
});
|
||||
export const RelayContext = createContext<NDK>(null);
|
||||
const ndk = await initNDK();
|
||||
|
||||
export function RelayProvider({ children }: { children: React.ReactNode }) {
|
||||
return <RelayContext.Provider value={pool}>{children}</RelayContext.Provider>;
|
||||
return <RelayContext.Provider value={ndk}>{children}</RelayContext.Provider>;
|
||||
}
|
||||
|
@ -28,4 +28,7 @@ export const FULL_RELAYS = [
|
||||
"wss://welcome.nostr.wine",
|
||||
"wss://relay.nostr.band",
|
||||
"wss://relay.damus.io",
|
||||
"wss://relay.snort.social",
|
||||
"wss://relayable.org",
|
||||
"wss://nostr.mutinywallet.com",
|
||||
];
|
||||
|
@ -1,56 +1,31 @@
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { READONLY_RELAYS } from "@stores/constants";
|
||||
import { createNote, getNoteByID } from "@utils/storage";
|
||||
import { getParentID } from "@utils/transform";
|
||||
import { useContext } from "react";
|
||||
import useSWR from "swr";
|
||||
import useSWRSubscription from "swr/subscription";
|
||||
|
||||
const fetcher = ([, id]) => getNoteByID(id);
|
||||
const fetcher = async ([, ndk, id]) => {
|
||||
const result = await getNoteByID(id);
|
||||
|
||||
if (result) {
|
||||
return result;
|
||||
} else {
|
||||
const event = await ndk.fetchEvent(id);
|
||||
await createNote(
|
||||
event.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at,
|
||||
);
|
||||
event["event_id"] = event.id;
|
||||
return event;
|
||||
}
|
||||
};
|
||||
|
||||
export function useEvent(id: string) {
|
||||
const pool: any = useContext(RelayContext);
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
const ndk = useContext(RelayContext);
|
||||
const { data } = useSWR(["note", ndk, id], fetcher);
|
||||
|
||||
const { data: cache } = useSWR(["event", id], fetcher);
|
||||
const { data: newest } = useSWRSubscription(
|
||||
!cache ? id : null,
|
||||
(key, { next }) => {
|
||||
const unsubscribe = pool.subscribe(
|
||||
[
|
||||
{
|
||||
ids: [key],
|
||||
},
|
||||
],
|
||||
READONLY_RELAYS,
|
||||
(event: any) => {
|
||||
const parentID = getParentID(event.tags, event.id);
|
||||
// insert event to local database
|
||||
createNote(
|
||||
event.id,
|
||||
account.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at,
|
||||
);
|
||||
// update state
|
||||
next(null, event);
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
unsubscribeOnEose: true,
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return cache ? cache : newest;
|
||||
return data;
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
import NDK from "@nostr-dev-kit/ndk";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { METADATA_RELAY } from "@stores/constants";
|
||||
import { createPleb, getPleb } from "@utils/storage";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import { useContext } from "react";
|
||||
import useSWR from "swr";
|
||||
import useSWRSubscription from "swr/subscription";
|
||||
|
||||
const fetcher = async (key: string) => {
|
||||
const fetcher = async ([, ndk, key]) => {
|
||||
let npub: string;
|
||||
|
||||
if (key.substring(0, 4) === "npub") {
|
||||
@ -18,59 +17,27 @@ const fetcher = async (key: string) => {
|
||||
const current = Math.floor(Date.now() / 1000);
|
||||
const result = await getPleb(npub);
|
||||
|
||||
if (result && result.created_at + 86400 < current) {
|
||||
if (result && result.created_at + 86400 > current) {
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
const user = ndk.getUser({ npub });
|
||||
await user.fetchProfile();
|
||||
await createPleb(key, user.profile);
|
||||
|
||||
return user.profile;
|
||||
}
|
||||
};
|
||||
|
||||
export function useProfile(key: string) {
|
||||
const pool: any = useContext(RelayContext);
|
||||
|
||||
const {
|
||||
data: cache,
|
||||
error,
|
||||
isLoading,
|
||||
} = useSWR(key, fetcher, {
|
||||
const ndk = useContext(RelayContext);
|
||||
const { data, error, isLoading } = useSWR(["profile", ndk, key], fetcher, {
|
||||
revalidateIfStale: false,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: true,
|
||||
});
|
||||
|
||||
const { data: newest } = useSWRSubscription(
|
||||
cache ? null : key,
|
||||
(_, { next }) => {
|
||||
const unsubscribe = pool.subscribe(
|
||||
[
|
||||
{
|
||||
authors: [key],
|
||||
kinds: [0],
|
||||
},
|
||||
],
|
||||
METADATA_RELAY,
|
||||
(event: { content: string }) => {
|
||||
const content = JSON.parse(event.content);
|
||||
// update state
|
||||
next(null, content);
|
||||
// save to database
|
||||
createPleb(key, event);
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
unsubscribeOnEose: true,
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
user: newest ? newest : cache,
|
||||
user: data,
|
||||
isLoading,
|
||||
isError: error,
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { NDKTag, NDKUserProfile } from "@nostr-dev-kit/ndk";
|
||||
import { getParentID } from "@utils/transform";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import Database from "tauri-plugin-sql-api";
|
||||
@ -76,10 +77,9 @@ export async function getPleb(npub: string) {
|
||||
}
|
||||
|
||||
// create pleb
|
||||
export async function createPleb(key: string, json: any) {
|
||||
export async function createPleb(key: string, data: NDKUserProfile) {
|
||||
const db = await connect();
|
||||
const data = JSON.parse(json.content);
|
||||
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
let npub: string;
|
||||
|
||||
if (key.substring(0, 4) === "npub") {
|
||||
@ -89,21 +89,20 @@ export async function createPleb(key: string, json: any) {
|
||||
}
|
||||
|
||||
return await db.execute(
|
||||
"INSERT OR REPLACE INTO plebs (npub, display_name, name, username, about, bio, website, picture, banner, nip05, lud06, lud16, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
"INSERT OR REPLACE INTO plebs (npub, name, displayName, image, banner, bio, nip05, lud06, lud16, about, zapService, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
[
|
||||
npub,
|
||||
data.display_name || data.displayName,
|
||||
data.name,
|
||||
data.username,
|
||||
data.about,
|
||||
data.bio,
|
||||
data.website,
|
||||
data.picture || data.image,
|
||||
data.displayName,
|
||||
data.image,
|
||||
data.banner,
|
||||
data.bio,
|
||||
data.nip05,
|
||||
data.lud06,
|
||||
data.lud16,
|
||||
data.created_at,
|
||||
data.about,
|
||||
data.zapService,
|
||||
now,
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -216,39 +215,41 @@ export async function getLatestNotes(time: number) {
|
||||
// create note
|
||||
export async function createNote(
|
||||
event_id: string,
|
||||
account_id: number,
|
||||
pubkey: string,
|
||||
kind: number,
|
||||
tags: string[],
|
||||
tags: any,
|
||||
content: string,
|
||||
created_at: number,
|
||||
) {
|
||||
const db = await connect();
|
||||
const account = await getActiveAccount();
|
||||
const parentID = getParentID(tags, event_id);
|
||||
|
||||
return await db.execute(
|
||||
"INSERT INTO notes (event_id, account_id, pubkey, kind, tags, content, created_at, parent_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
[event_id, account_id, pubkey, kind, tags, content, created_at, parentID],
|
||||
"INSERT OR IGNORE INTO notes (event_id, account_id, pubkey, kind, tags, content, created_at, parent_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
[event_id, account.id, pubkey, kind, tags, content, created_at, parentID],
|
||||
);
|
||||
}
|
||||
|
||||
// create reply note
|
||||
export async function createReplyNote(
|
||||
event_id: string,
|
||||
account_id: number,
|
||||
pubkey: string,
|
||||
kind: number,
|
||||
tags: string[],
|
||||
tags: any,
|
||||
content: string,
|
||||
created_at: number,
|
||||
parent_comment_id: string,
|
||||
) {
|
||||
const db = await connect();
|
||||
const account = await getActiveAccount();
|
||||
const parentID = getParentID(tags, event_id);
|
||||
|
||||
return await db.execute(
|
||||
"INSERT INTO notes (event_id, account_id, pubkey, kind, tags, content, created_at, parent_id, parent_comment_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
"INSERT OR IGNORE INTO notes (event_id, account_id, pubkey, kind, tags, content, created_at, parent_id, parent_comment_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
[
|
||||
event_id,
|
||||
account_id,
|
||||
account.id,
|
||||
pubkey,
|
||||
kind,
|
||||
tags,
|
||||
@ -298,7 +299,7 @@ export async function updateChannelMetadata(event_id: string, value: string) {
|
||||
|
||||
return await db.execute(
|
||||
"UPDATE channels SET name = ?, picture = ?, about = ? WHERE event_id = ?;",
|
||||
[data.name, data.picture, data.about, event_id],
|
||||
[data.name, data.image, data.about, event_id],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,20 @@
|
||||
import destr from "destr";
|
||||
import { nip19 } from "nostr-tools";
|
||||
|
||||
export function truncateContent(str, n) {
|
||||
return str.length > n ? `${str.slice(0, n - 1)}…` : str;
|
||||
}
|
||||
|
||||
export function setToArray(tags: any) {
|
||||
const newArray = [];
|
||||
tags.forEach((item) => {
|
||||
const hexpubkey = nip19.decode(item.npub).data;
|
||||
newArray.push(hexpubkey);
|
||||
});
|
||||
|
||||
return newArray;
|
||||
}
|
||||
|
||||
// convert NIP-02 to array of pubkey
|
||||
export function nip02ToArray(tags: any) {
|
||||
const arr = [];
|
||||
|
@ -3,6 +3,7 @@
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@app/*": ["src/app/*"],
|
||||
"@libs/*": ["src/libs/*"],
|
||||
"@shared/*": ["src/shared/*"],
|
||||
"@stores/*": ["src/stores/*"],
|
||||
"@utils/*": ["src/utils/*"]
|
||||
|
Loading…
x
Reference in New Issue
Block a user