mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-18 11:13:30 +00:00
migrate to ndk
This commit is contained in:
parent
75a33d205a
commit
0ba9877785
@ -15,6 +15,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/react": "^0.23.1",
|
"@floating-ui/react": "^0.23.1",
|
||||||
"@headlessui/react": "^1.7.15",
|
"@headlessui/react": "^1.7.15",
|
||||||
|
"@nostr-dev-kit/ndk": "^0.4.4",
|
||||||
"@tanstack/react-virtual": "3.0.0-beta.54",
|
"@tanstack/react-virtual": "3.0.0-beta.54",
|
||||||
"@tauri-apps/api": "^1.3.0",
|
"@tauri-apps/api": "^1.3.0",
|
||||||
"@vidstack/react": "^0.4.5",
|
"@vidstack/react": "^0.4.5",
|
||||||
@ -29,7 +30,7 @@
|
|||||||
"react-hook-form": "^7.44.3",
|
"react-hook-form": "^7.44.3",
|
||||||
"react-markdown": "^8.0.7",
|
"react-markdown": "^8.0.7",
|
||||||
"react-resizable-panels": "^0.0.48",
|
"react-resizable-panels": "^0.0.48",
|
||||||
"react-virtuoso": "^4.3.8",
|
"react-virtuoso": "^4.3.9",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"slate": "^0.94.1",
|
"slate": "^0.94.1",
|
||||||
"slate-history": "^0.93.0",
|
"slate-history": "^0.93.0",
|
||||||
@ -44,7 +45,7 @@
|
|||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"@tauri-apps/cli": "^1.3.1",
|
"@tauri-apps/cli": "^1.3.1",
|
||||||
"@types/node": "^18.16.16",
|
"@types/node": "^18.16.16",
|
||||||
"@types/react": "^18.2.8",
|
"@types/react": "^18.2.9",
|
||||||
"@types/react-dom": "^18.2.4",
|
"@types/react-dom": "^18.2.4",
|
||||||
"@types/youtube-player": "^5.5.7",
|
"@types/youtube-player": "^5.5.7",
|
||||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||||
|
2220
pnpm-lock.yaml
2220
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -19,17 +19,16 @@ CREATE TABLE
|
|||||||
plebs (
|
plebs (
|
||||||
id INTEGER NOT NULL PRIMARY KEY,
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
npub TEXT NOT NULL UNIQUE,
|
npub TEXT NOT NULL UNIQUE,
|
||||||
display_name TEXT,
|
|
||||||
name TEXT,
|
name TEXT,
|
||||||
username TEXT,
|
displayName TEXT,
|
||||||
about TEXT,
|
image TEXT,
|
||||||
bio TEXT,
|
|
||||||
website TEXT,
|
|
||||||
picture TEXT,
|
|
||||||
banner TEXT,
|
banner TEXT,
|
||||||
|
bio TEXT,
|
||||||
nip05 TEXT,
|
nip05 TEXT,
|
||||||
lud06 TEXT,
|
lud06 TEXT,
|
||||||
lud16 TEXT,
|
lud16 TEXT,
|
||||||
|
about TEXT,
|
||||||
|
zapService TEXT,
|
||||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
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="flex items-center gap-2">
|
||||||
<div className="relative h-11 w-11 shrink rounded-md">
|
<div className="relative h-11 w-11 shrink rounded-md">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={pubkey}
|
alt={pubkey}
|
||||||
className="h-11 w-11 rounded-md object-cover"
|
className="h-11 w-11 rounded-md object-cover"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
@ -18,7 +18,7 @@ export function User({ pubkey }: { pubkey: string }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex w-full flex-1 flex-col items-start text-start">
|
<div className="flex w-full flex-1 flex-col items-start text-start">
|
||||||
<span className="truncate font-medium leading-tight text-white">
|
<span className="truncate font-medium leading-tight text-white">
|
||||||
{user?.display_name || user?.name}
|
{user?.displayName || user?.name}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-base leading-tight text-zinc-400">
|
<span className="text-base leading-tight text-zinc-400">
|
||||||
{user?.nip05?.toLowerCase() || shortenKey(pubkey)}
|
{user?.nip05?.toLowerCase() || shortenKey(pubkey)}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
|
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
|
||||||
import { AvatarUploader } from "@shared/avatarUploader";
|
import { AvatarUploader } from "@shared/avatarUploader";
|
||||||
import { Image } from "@shared/image";
|
import { Image } from "@shared/image";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { useActiveAccount } from "@stores/accounts";
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { DEFAULT_AVATAR, WRITEONLY_RELAYS } from "@stores/constants";
|
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { navigate } from "vite-plugin-ssr/client/router";
|
import { navigate } from "vite-plugin-ssr/client/router";
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
const account = useActiveAccount((state: any) => state.account);
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const [image, setImage] = useState(DEFAULT_AVATAR);
|
const [image, setImage] = useState(DEFAULT_AVATAR);
|
||||||
@ -25,28 +25,30 @@ export function Page() {
|
|||||||
const onSubmit = (data: any) => {
|
const onSubmit = (data: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const event: any = {
|
try {
|
||||||
content: JSON.stringify(data),
|
const signer = new NDKPrivateKeySigner(account.privkey);
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
ndk.signer = signer;
|
||||||
kind: 0,
|
|
||||||
pubkey: account.pubkey,
|
|
||||||
tags: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
event.id = getEventHash(event);
|
const event = new NDKEvent(ndk);
|
||||||
event.sig = getSignature(event, account.privkey);
|
// build event
|
||||||
|
event.content = JSON.stringify(data);
|
||||||
|
event.kind = 0;
|
||||||
|
event.pubkey = account.pubkey;
|
||||||
|
event.tags = [];
|
||||||
|
// publish event
|
||||||
|
event.publish();
|
||||||
|
|
||||||
// publish
|
// redirect to step 3
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
setTimeout(
|
||||||
|
() =>
|
||||||
// redirect to step 3
|
navigate("/app/auth/create/step-3", {
|
||||||
setTimeout(
|
overwriteLastHistoryEntry: true,
|
||||||
() =>
|
}),
|
||||||
navigate("/app/auth/create/step-3", {
|
2000,
|
||||||
overwriteLastHistoryEntry: true,
|
);
|
||||||
}),
|
} catch {
|
||||||
2000,
|
console.log("error");
|
||||||
);
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
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">
|
<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
|
<input
|
||||||
type={"text"}
|
type={"text"}
|
||||||
{...register("display_name", {
|
{...register("displayName", {
|
||||||
required: true,
|
required: true,
|
||||||
minLength: 4,
|
minLength: 4,
|
||||||
})}
|
})}
|
||||||
@ -105,11 +107,11 @@ export function Page() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<label className="text-base font-semibold uppercase tracking-wider text-zinc-400">
|
<label className="text-base font-semibold uppercase tracking-wider text-zinc-400">
|
||||||
About
|
Bio
|
||||||
</label>
|
</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">
|
<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
|
<textarea
|
||||||
{...register("about")}
|
{...register("bio")}
|
||||||
spellCheck={false}
|
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"
|
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
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!isDirty || !isValid}
|
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 ? (
|
{loading ? (
|
||||||
<svg
|
<svg
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { User } from "@app/auth/components/user";
|
import { User } from "@app/auth/components/user";
|
||||||
|
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
|
||||||
import { CheckCircleIcon } from "@shared/icons";
|
import { CheckCircleIcon } from "@shared/icons";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { useActiveAccount } from "@stores/accounts";
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
|
||||||
import { arrayToNIP02 } from "@utils/transform";
|
import { arrayToNIP02 } from "@utils/transform";
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import { navigate } from "vite-plugin-ssr/client/router";
|
import { navigate } from "vite-plugin-ssr/client/router";
|
||||||
|
|
||||||
@ -108,7 +107,7 @@ const initialList = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
|
|
||||||
const [account, updateFollows] = useActiveAccount((state: any) => [
|
const [account, updateFollows] = useActiveAccount((state: any) => [
|
||||||
state.account,
|
state.account,
|
||||||
@ -129,33 +128,34 @@ export function Page() {
|
|||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// update account follows
|
try {
|
||||||
updateFollows(follows);
|
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 = {
|
// update account follows
|
||||||
content: "",
|
updateFollows(follows);
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
|
||||||
kind: 3,
|
|
||||||
pubkey: account.pubkey,
|
|
||||||
tags: tags,
|
|
||||||
};
|
|
||||||
|
|
||||||
event.id = getEventHash(event);
|
// redirect to step 3
|
||||||
event.sig = getSignature(event, account.privkey);
|
setTimeout(
|
||||||
|
() =>
|
||||||
// publish
|
navigate("/", {
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
overwriteLastHistoryEntry: true,
|
||||||
|
}),
|
||||||
// redirect to step 3
|
2000,
|
||||||
setTimeout(
|
);
|
||||||
() =>
|
} catch {
|
||||||
navigate("/", {
|
console.log("error");
|
||||||
overwriteLastHistoryEntry: true,
|
}
|
||||||
}),
|
|
||||||
2000,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,53 +1,41 @@
|
|||||||
import { User } from "@app/auth/components/user";
|
import { User } from "@app/auth/components/user";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { useActiveAccount } from "@stores/accounts";
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { METADATA_RELAY } from "@stores/constants";
|
import { setToArray } from "@utils/transform";
|
||||||
import { nip02ToArray } from "@utils/transform";
|
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import useSWRSubscription from "swr/subscription";
|
|
||||||
import { navigate } from "vite-plugin-ssr/client/router";
|
import { navigate } from "vite-plugin-ssr/client/router";
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
const [account, updateFollows] = useActiveAccount((state: any) => [
|
const [account, updateFollows] = useActiveAccount((state: any) => [
|
||||||
state.account,
|
state.account,
|
||||||
state.updateFollows,
|
state.updateFollows,
|
||||||
]);
|
]);
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [follows, setFollows] = useState(null);
|
|
||||||
|
|
||||||
useSWRSubscription(account ? ["follows", account.pubkey] : null, () => {
|
const submit = async () => {
|
||||||
const unsubscribe = pool.subscribe(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
kinds: [3],
|
|
||||||
authors: [account.pubkey],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
METADATA_RELAY,
|
|
||||||
(event: any) => {
|
|
||||||
setFollows(event.tags);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unsubscribe();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const submit = () => {
|
|
||||||
// show loading indicator
|
// show loading indicator
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// follows as list
|
try {
|
||||||
const followsList = nip02ToArray(follows);
|
const user = ndk.getUser({ hexpubkey: account.pubkey });
|
||||||
|
const follows = await user.follows();
|
||||||
|
|
||||||
// update account follows in store
|
// follows as list
|
||||||
updateFollows(followsList);
|
const followsList = setToArray(follows);
|
||||||
|
|
||||||
// redirect to home
|
// update account follows in store
|
||||||
setTimeout(() => navigate("/", { overwriteLastHistoryEntry: true }), 2000);
|
updateFollows(followsList);
|
||||||
|
|
||||||
|
// redirect to home
|
||||||
|
setTimeout(
|
||||||
|
() => navigate("/", { overwriteLastHistoryEntry: true }),
|
||||||
|
2000,
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
console.log("error");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
|
||||||
import { AvatarUploader } from "@shared/avatarUploader";
|
import { AvatarUploader } from "@shared/avatarUploader";
|
||||||
import { CancelIcon, PlusIcon } from "@shared/icons";
|
import { CancelIcon, PlusIcon } from "@shared/icons";
|
||||||
import { Image } from "@shared/image";
|
import { Image } from "@shared/image";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { useActiveAccount } from "@stores/accounts";
|
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 { dateToUnix } from "@utils/date";
|
||||||
import { createChannel } from "@utils/storage";
|
import { createChannel } from "@utils/storage";
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
|
||||||
import { Fragment, useContext, useEffect, useState } from "react";
|
import { Fragment, useContext, useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { navigate } from "vite-plugin-ssr/client/router";
|
import { navigate } from "vite-plugin-ssr/client/router";
|
||||||
|
|
||||||
export function ChannelCreateModal() {
|
export function ChannelCreateModal() {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
const account = useActiveAccount((state: any) => state.account);
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
@ -39,20 +39,20 @@ export function ChannelCreateModal() {
|
|||||||
const onSubmit = (data: any) => {
|
const onSubmit = (data: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
if (account) {
|
try {
|
||||||
const event: any = {
|
const signer = new NDKPrivateKeySigner(account.privkey);
|
||||||
content: JSON.stringify(data),
|
ndk.signer = signer;
|
||||||
created_at: dateToUnix(),
|
|
||||||
kind: 40,
|
|
||||||
pubkey: account.pubkey,
|
|
||||||
tags: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
event.id = getEventHash(event);
|
const event = new NDKEvent(ndk);
|
||||||
event.sig = getSignature(event, account.privkey);
|
// build event
|
||||||
|
event.content = JSON.stringify(data);
|
||||||
|
event.kind = 40;
|
||||||
|
event.created_at = dateToUnix();
|
||||||
|
event.pubkey = account.pubkey;
|
||||||
|
event.tags = [];
|
||||||
|
|
||||||
// publish channel
|
// publish event
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
event.publish();
|
||||||
|
|
||||||
// insert to database
|
// insert to database
|
||||||
createChannel(event.id, event.pubkey, event.content, event.created_at);
|
createChannel(event.id, event.pubkey, event.content, event.created_at);
|
||||||
@ -65,9 +65,9 @@ export function ChannelCreateModal() {
|
|||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
// redirect to channel page
|
// redirect to channel page
|
||||||
navigate(`/app/channel?id=${event.id}`);
|
navigate(`/app/channel?id=${event.id}`);
|
||||||
}, 2000);
|
}, 1000);
|
||||||
} else {
|
} catch (e) {
|
||||||
console.log("error");
|
console.log("error: ", e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ export function Member({ pubkey }: { pubkey: string }) {
|
|||||||
) : (
|
) : (
|
||||||
<Image
|
<Image
|
||||||
className="inline-block h-8 w-8 rounded-md bg-white ring-2 ring-zinc-950 transition-all duration-150 ease-in-out"
|
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"}
|
alt={user?.pubkey || "user avatar"}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import { UserReply } from "@app/channel/components/messages/userReply";
|
import { UserReply } from "@app/channel/components/messages/userReply";
|
||||||
|
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
|
||||||
import { CancelIcon } from "@shared/icons";
|
import { CancelIcon } from "@shared/icons";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { useActiveAccount } from "@stores/accounts";
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { useChannelMessages } from "@stores/channels";
|
import { useChannelMessages } from "@stores/channels";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
|
|
||||||
export function ChannelMessageForm({ channelID }: { channelID: string }) {
|
export function ChannelMessageForm({ channelID }: { channelID: string }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
const account = useActiveAccount((state: any) => state.account);
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
@ -31,19 +30,20 @@ export function ChannelMessageForm({ channelID }: { channelID: string }) {
|
|||||||
tags = [["e", channelID, "", "root"]];
|
tags = [["e", channelID, "", "root"]];
|
||||||
}
|
}
|
||||||
|
|
||||||
const event: any = {
|
const signer = new NDKPrivateKeySigner(account.privkey);
|
||||||
content: value,
|
ndk.signer = signer;
|
||||||
created_at: dateToUnix(),
|
|
||||||
kind: 42,
|
|
||||||
pubkey: account.pubkey,
|
|
||||||
tags: tags,
|
|
||||||
};
|
|
||||||
|
|
||||||
event.id = getEventHash(event);
|
const event = new NDKEvent(ndk);
|
||||||
event.sig = getSignature(event, account.privkey);
|
// 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
|
// reset state
|
||||||
setValue("");
|
setValue("");
|
||||||
};
|
};
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
|
||||||
import { CancelIcon, HideIcon } from "@shared/icons";
|
import { CancelIcon, HideIcon } from "@shared/icons";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { Tooltip } from "@shared/tooltip";
|
import { Tooltip } from "@shared/tooltip";
|
||||||
import { useActiveAccount } from "@stores/accounts";
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { useChannelMessages } from "@stores/channels";
|
import { useChannelMessages } from "@stores/channels";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
|
||||||
import { Fragment, useContext, useState } from "react";
|
import { Fragment, useContext, useState } from "react";
|
||||||
|
|
||||||
export function MessageHideButton({ id }: { id: string }) {
|
export function MessageHideButton({ id }: { id: string }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
const account = useActiveAccount((state: any) => state.account);
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
const hide = useChannelMessages((state: any) => state.hideMessage);
|
const hide = useChannelMessages((state: any) => state.hideMessage);
|
||||||
|
|
||||||
@ -25,19 +24,19 @@ export function MessageHideButton({ id }: { id: string }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const hideMessage = () => {
|
const hideMessage = () => {
|
||||||
const event: any = {
|
const signer = new NDKPrivateKeySigner(account.privkey);
|
||||||
content: "",
|
ndk.signer = signer;
|
||||||
created_at: dateToUnix(),
|
|
||||||
kind: 43,
|
|
||||||
pubkey: account.pubkey,
|
|
||||||
tags: [["e", id]],
|
|
||||||
};
|
|
||||||
|
|
||||||
event.id = getEventHash(event);
|
const event = new NDKEvent(ndk);
|
||||||
event.sig = getSignature(event, account.privkey);
|
// build event
|
||||||
|
event.content = "";
|
||||||
|
event.kind = 43;
|
||||||
|
event.created_at = dateToUnix();
|
||||||
|
event.pubkey = account.pubkey;
|
||||||
|
event.tags = [["e", id]];
|
||||||
|
|
||||||
// publish note
|
// publish event
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
event.publish();
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
hide(id);
|
hide(id);
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
|
||||||
import { CancelIcon, MuteIcon } from "@shared/icons";
|
import { CancelIcon, MuteIcon } from "@shared/icons";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { Tooltip } from "@shared/tooltip";
|
import { Tooltip } from "@shared/tooltip";
|
||||||
import { useActiveAccount } from "@stores/accounts";
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { useChannelMessages } from "@stores/channels";
|
import { useChannelMessages } from "@stores/channels";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
|
||||||
import { Fragment, useContext, useState } from "react";
|
import { Fragment, useContext, useState } from "react";
|
||||||
|
|
||||||
export function MessageMuteButton({ pubkey }: { pubkey: string }) {
|
export function MessageMuteButton({ pubkey }: { pubkey: string }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
const account = useActiveAccount((state: any) => state.account);
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
const mute = useChannelMessages((state: any) => state.muteUser);
|
const mute = useChannelMessages((state: any) => state.muteUser);
|
||||||
|
|
||||||
@ -25,19 +24,19 @@ export function MessageMuteButton({ pubkey }: { pubkey: string }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const muteUser = () => {
|
const muteUser = () => {
|
||||||
const event: any = {
|
const signer = new NDKPrivateKeySigner(account.privkey);
|
||||||
content: "",
|
ndk.signer = signer;
|
||||||
created_at: dateToUnix(),
|
|
||||||
kind: 44,
|
|
||||||
pubkey: account.pubkey,
|
|
||||||
tags: [["p", pubkey]],
|
|
||||||
};
|
|
||||||
|
|
||||||
event.id = getEventHash(event);
|
const event = new NDKEvent(ndk);
|
||||||
event.sig = getSignature(event, account.privkey);
|
// build event
|
||||||
|
event.content = "";
|
||||||
|
event.kind = 44;
|
||||||
|
event.created_at = dateToUnix();
|
||||||
|
event.pubkey = account.pubkey;
|
||||||
|
event.tags = [["p", pubkey]];
|
||||||
|
|
||||||
// publish note
|
// publish event
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
event.publish();
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
mute(pubkey);
|
mute(pubkey);
|
||||||
|
@ -31,7 +31,7 @@ export function ChannelMessageUser({
|
|||||||
<>
|
<>
|
||||||
<div className="relative h-11 w-11 shrink-0 rounded-md">
|
<div className="relative h-11 w-11 shrink-0 rounded-md">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={pubkey}
|
alt={pubkey}
|
||||||
className="h-11 w-11 rounded-md object-cover"
|
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">
|
<div className="relative h-11 w-11 shrink-0 rounded-md">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={pubkey}
|
alt={pubkey}
|
||||||
className="h-11 w-11 rounded-md object-cover"
|
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">
|
<div className="relative h-9 w-9 shrink overflow-hidden rounded">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={pubkey}
|
alt={pubkey}
|
||||||
className="h-9 w-9 rounded object-cover"
|
className="h-9 w-9 rounded object-cover"
|
||||||
/>
|
/>
|
||||||
|
@ -22,7 +22,7 @@ export function ChannelMetadata({
|
|||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="relative shrink-0 rounded-md h-11 w-11">
|
<div className="relative shrink-0 rounded-md h-11 w-11">
|
||||||
<Image
|
<Image
|
||||||
src={metadata?.picture || DEFAULT_AVATAR}
|
src={metadata?.image || DEFAULT_AVATAR}
|
||||||
alt={id}
|
alt={id}
|
||||||
className="h-11 w-11 rounded-md object-contain bg-zinc-900"
|
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="flex items-center gap-1.5">
|
||||||
<div className="relative h-9 w-9 shrink rounded-md">
|
<div className="relative h-9 w-9 shrink rounded-md">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={data.content}
|
alt={data.content}
|
||||||
className="h-9 w-9 rounded-md object-cover"
|
className="h-9 w-9 rounded-md object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full flex-1 flex-col items-start gap-0.5 text-start">
|
<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">
|
<span className="truncate text-base font-medium leading-none text-white">
|
||||||
{user?.display_name || user?.name || "Pleb"}
|
{user?.displayName || user?.name || "Pleb"}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-base leading-none text-zinc-400">
|
<span className="text-base leading-none text-zinc-400">
|
||||||
{shortenKey(data.content)}
|
{shortenKey(data.content)}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
|
||||||
import { AvatarUploader } from "@shared/avatarUploader";
|
import { AvatarUploader } from "@shared/avatarUploader";
|
||||||
import { CancelIcon, EditIcon } from "@shared/icons";
|
import { CancelIcon, EditIcon } from "@shared/icons";
|
||||||
import { Image } from "@shared/image";
|
import { Image } from "@shared/image";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { useActiveAccount } from "@stores/accounts";
|
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 { dateToUnix } from "@utils/date";
|
||||||
import { getChannel } from "@utils/storage";
|
import { getChannel } from "@utils/storage";
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
|
||||||
import { Fragment, useContext, useEffect, useState } from "react";
|
import { Fragment, useContext, useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
export function ChannelUpdateModal({ id }: { id: string }) {
|
export function ChannelUpdateModal({ id }: { id: string }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
const account = useActiveAccount((state: any) => state.account);
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
@ -38,7 +38,7 @@ export function ChannelUpdateModal({ id }: { id: string }) {
|
|||||||
const channel = await getChannel(id);
|
const channel = await getChannel(id);
|
||||||
const metadata = JSON.parse(channel.metadata);
|
const metadata = JSON.parse(channel.metadata);
|
||||||
// update image state
|
// update image state
|
||||||
setImage(metadata.picture);
|
setImage(metadata.image);
|
||||||
// set default values
|
// set default values
|
||||||
return metadata;
|
return metadata;
|
||||||
},
|
},
|
||||||
@ -47,28 +47,28 @@ export function ChannelUpdateModal({ id }: { id: string }) {
|
|||||||
const onSubmit = (data: any) => {
|
const onSubmit = (data: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
if (account) {
|
try {
|
||||||
const event: any = {
|
const signer = new NDKPrivateKeySigner(account.privkey);
|
||||||
content: JSON.stringify(data),
|
ndk.signer = signer;
|
||||||
created_at: dateToUnix(),
|
|
||||||
kind: 41,
|
|
||||||
pubkey: account.pubkey,
|
|
||||||
tags: [["e", id]],
|
|
||||||
};
|
|
||||||
|
|
||||||
event.id = getEventHash(event);
|
const event = new NDKEvent(ndk);
|
||||||
event.sig = getSignature(event, account.privkey);
|
// build event
|
||||||
|
event.content = JSON.stringify(data);
|
||||||
|
event.kind = 41;
|
||||||
|
event.created_at = dateToUnix();
|
||||||
|
event.pubkey = account.pubkey;
|
||||||
|
event.tags = [["e", id]];
|
||||||
|
|
||||||
// publish channel
|
// publish event
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
event.publish();
|
||||||
|
|
||||||
// reset form
|
// reset form
|
||||||
reset();
|
reset();
|
||||||
|
|
||||||
// close modal
|
// close modal
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
setLoading(false);
|
} catch (e) {
|
||||||
} else {
|
console.log("error: ", e);
|
||||||
console.log("error");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ import { ChannelUpdateModal } from "@app/channel/components/updateModal";
|
|||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { useActiveAccount } from "@stores/accounts";
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { useChannelMessages } from "@stores/channels";
|
import { useChannelMessages } from "@stores/channels";
|
||||||
import { READONLY_RELAYS } from "@stores/constants";
|
|
||||||
import { dateToUnix, getHourAgo } from "@utils/date";
|
import { dateToUnix, getHourAgo } from "@utils/date";
|
||||||
import { usePageContext } from "@utils/hooks/usePageContext";
|
import { usePageContext } from "@utils/hooks/usePageContext";
|
||||||
import { getActiveBlacklist, getBlacklist } from "@utils/storage";
|
import { getActiveBlacklist, getBlacklist } from "@utils/storage";
|
||||||
@ -29,7 +28,7 @@ const fetchHided = async ([, id]) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
|
|
||||||
const pageContext = usePageContext();
|
const pageContext = usePageContext();
|
||||||
const searchParams: any = pageContext.urlParsed.search;
|
const searchParams: any = pageContext.urlParsed.search;
|
||||||
@ -57,40 +56,36 @@ export function Page() {
|
|||||||
account && channelID && muted && hided ? ["channel", channelID] : null,
|
account && channelID && muted && hided ? ["channel", channelID] : null,
|
||||||
() => {
|
() => {
|
||||||
// subscribe to channel
|
// subscribe to channel
|
||||||
const unsubscribe = pool.subscribe(
|
const sub = ndk.subscribe({
|
||||||
[
|
"#e": [channelID],
|
||||||
{
|
kinds: [42],
|
||||||
"#e": [channelID],
|
since: dateToUnix(getHourAgo(24, now.current)),
|
||||||
kinds: [42],
|
limit: 20,
|
||||||
since: dateToUnix(getHourAgo(24, now.current)),
|
});
|
||||||
limit: 20,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
READONLY_RELAYS,
|
|
||||||
(event: { id: string; pubkey: string }) => {
|
|
||||||
const message: any = event;
|
|
||||||
|
|
||||||
// handle hide message
|
sub.addListener("event", (event: { id: string; pubkey: string }) => {
|
||||||
if (hided.includes(event.id)) {
|
const message: any = event;
|
||||||
message["hide"] = true;
|
|
||||||
} else {
|
|
||||||
message["hide"] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle mute user
|
// handle hide message
|
||||||
if (muted.array.includes(event.pubkey)) {
|
if (hided.includes(event.id)) {
|
||||||
message["mute"] = true;
|
message["hide"] = true;
|
||||||
} else {
|
} else {
|
||||||
message["mute"] = false;
|
message["hide"] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add to store
|
// handle mute user
|
||||||
addMessage(message);
|
if (muted.array.includes(event.pubkey)) {
|
||||||
},
|
message["mute"] = true;
|
||||||
);
|
} else {
|
||||||
|
message["mute"] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to store
|
||||||
|
addMessage(message);
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribe();
|
sub.stop();
|
||||||
clear();
|
clear();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -34,7 +34,7 @@ export function ChatsListItem({ data }: { data: any }) {
|
|||||||
>
|
>
|
||||||
<div className="relative h-5 w-5 shrink-0 rounded">
|
<div className="relative h-5 w-5 shrink-0 rounded">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={data.sender_pubkey}
|
alt={data.sender_pubkey}
|
||||||
className="h-5 w-5 rounded bg-white object-cover"
|
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="w-full inline-flex items-center justify-between">
|
||||||
<div className="inline-flex items-baseline gap-1">
|
<div className="inline-flex items-baseline gap-1">
|
||||||
<h5 className="max-w-[9rem] truncate font-medium text-zinc-200 group-hover:text-white">
|
<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>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
|
||||||
import { EnterIcon } from "@shared/icons";
|
import { EnterIcon } from "@shared/icons";
|
||||||
import { MediaUploader } from "@shared/mediaUploader";
|
import { MediaUploader } from "@shared/mediaUploader";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { useChatMessages } from "@stores/chats";
|
import { useChatMessages } from "@stores/chats";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { getEventHash, getSignature, nip04 } from "nostr-tools";
|
import { nip04 } from "nostr-tools";
|
||||||
import { useCallback, useContext, useState } from "react";
|
import { useCallback, useContext, useState } from "react";
|
||||||
|
|
||||||
export function ChatMessageForm({
|
export function ChatMessageForm({
|
||||||
@ -12,8 +12,9 @@ export function ChatMessageForm({
|
|||||||
userPubkey,
|
userPubkey,
|
||||||
userPrivkey,
|
userPrivkey,
|
||||||
}: { receiverPubkey: string; userPubkey: string; userPrivkey: string }) {
|
}: { receiverPubkey: string; userPubkey: string; userPrivkey: string }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
const addMessage = useChatMessages((state: any) => state.add);
|
const addMessage = useChatMessages((state: any) => state.add);
|
||||||
|
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
|
|
||||||
const encryptMessage = useCallback(async () => {
|
const encryptMessage = useCallback(async () => {
|
||||||
@ -23,19 +24,19 @@ export function ChatMessageForm({
|
|||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
const message = await encryptMessage();
|
const message = await encryptMessage();
|
||||||
|
|
||||||
const event: any = {
|
const signer = new NDKPrivateKeySigner(userPrivkey);
|
||||||
content: message,
|
ndk.signer = signer;
|
||||||
created_at: dateToUnix(),
|
|
||||||
kind: 4,
|
|
||||||
pubkey: userPubkey,
|
|
||||||
tags: [["p", receiverPubkey]],
|
|
||||||
};
|
|
||||||
|
|
||||||
event.id = getEventHash(event);
|
const event = new NDKEvent(ndk);
|
||||||
event.sig = getSignature(event, userPrivkey);
|
// build event
|
||||||
|
event.content = message;
|
||||||
|
event.kind = 4;
|
||||||
|
event.created_at = dateToUnix();
|
||||||
|
event.pubkey = userPubkey;
|
||||||
|
event.tags = [["p", receiverPubkey]];
|
||||||
|
|
||||||
// publish message
|
// publish event
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
event.publish();
|
||||||
|
|
||||||
// add message to store
|
// add message to store
|
||||||
addMessage(receiverPubkey, event);
|
addMessage(receiverPubkey, event);
|
||||||
|
@ -28,7 +28,7 @@ export function ChatMessageUser({
|
|||||||
<>
|
<>
|
||||||
<div className="relative h-11 w-11 shrink rounded-md">
|
<div className="relative h-11 w-11 shrink rounded-md">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={pubkey}
|
alt={pubkey}
|
||||||
className="h-11 w-11 rounded-md object-cover"
|
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">
|
<div className="relative h-5 w-5 shrink-0 rounded">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={data.pubkey}
|
alt={data.pubkey}
|
||||||
className="h-5 w-5 rounded bg-white object-cover"
|
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="flex flex-col gap-3">
|
||||||
<div className="relative h-11 w-11 shrink rounded-md">
|
<div className="relative h-11 w-11 shrink rounded-md">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={pubkey}
|
alt={pubkey}
|
||||||
className="h-11 w-11 rounded-md object-cover"
|
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-4">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<h3 className="leading-none text-lg font-semibold">
|
<h3 className="leading-none text-lg font-semibold">
|
||||||
{user?.display_name || user?.name}
|
{user?.displayName || user?.name}
|
||||||
</h3>
|
</h3>
|
||||||
<h5 className="leading-none text-zinc-400">
|
<h5 className="leading-none text-zinc-400">
|
||||||
{user?.nip05 || user?.username || shortenKey(pubkey)}
|
{user?.nip05 || shortenKey(pubkey)}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -4,14 +4,13 @@ import { ChatMessageForm } from "@app/chat/components/messages/form";
|
|||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { useActiveAccount } from "@stores/accounts";
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { useChatMessages } from "@stores/chats";
|
import { useChatMessages } from "@stores/chats";
|
||||||
import { READONLY_RELAYS } from "@stores/constants";
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { usePageContext } from "@utils/hooks/usePageContext";
|
import { usePageContext } from "@utils/hooks/usePageContext";
|
||||||
import { useContext, useEffect } from "react";
|
import { useContext, useEffect } from "react";
|
||||||
import useSWRSubscription from "swr/subscription";
|
import useSWRSubscription from "swr/subscription";
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
const account = useActiveAccount((state: any) => state.account);
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const pageContext = usePageContext();
|
const pageContext = usePageContext();
|
||||||
@ -25,23 +24,19 @@ export function Page() {
|
|||||||
const add = useChatMessages((state: any) => state.add);
|
const add = useChatMessages((state: any) => state.add);
|
||||||
|
|
||||||
useSWRSubscription(account !== pubkey ? ["chat", pubkey] : null, () => {
|
useSWRSubscription(account !== pubkey ? ["chat", pubkey] : null, () => {
|
||||||
const unsubscribe = pool.subscribe(
|
const sub = ndk.subscribe({
|
||||||
[
|
kinds: [4],
|
||||||
{
|
authors: [pubkey],
|
||||||
kinds: [4],
|
"#p": [account.pubkey],
|
||||||
authors: [pubkey],
|
since: dateToUnix(),
|
||||||
"#p": [account.pubkey],
|
});
|
||||||
since: dateToUnix(),
|
|
||||||
},
|
sub.addListener("event", (event: any) => {
|
||||||
],
|
add(account.pubkey, event);
|
||||||
READONLY_RELAYS,
|
});
|
||||||
(event: any) => {
|
|
||||||
add(account.pubkey, event);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribe();
|
sub.stop();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ export function MentionUser(props: { children: any[] }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="text-fuchsia-500">
|
<span className="text-fuchsia-500">
|
||||||
@{user?.name || user?.display_name || shortenKey(pubkey)}
|
@{user?.name || user?.displayName || shortenKey(pubkey)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { NoteReply } from "@app/note/components/metadata/reply";
|
import { NoteReply } from "@app/note/components/metadata/reply";
|
||||||
import { NoteRepost } from "@app/note/components/metadata/repost";
|
import { NoteRepost } from "@app/note/components/metadata/repost";
|
||||||
import { NoteZap } from "@app/note/components/metadata/zap";
|
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 { RelayContext } from "@shared/relayProvider";
|
||||||
import { useActiveAccount } from "@stores/accounts";
|
|
||||||
import { READONLY_RELAYS } from "@stores/constants";
|
|
||||||
import { createReplyNote } from "@utils/storage";
|
import { createReplyNote } from "@utils/storage";
|
||||||
import { decode } from "light-bolt11-decoder";
|
import { decode } from "light-bolt11-decoder";
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
@ -16,65 +16,57 @@ export function NoteMetadata({
|
|||||||
id: string;
|
id: string;
|
||||||
eventPubkey: string;
|
eventPubkey: string;
|
||||||
}) {
|
}) {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
const account = useActiveAccount((state: any) => state.account);
|
|
||||||
|
|
||||||
const [replies, setReplies] = useState(0);
|
const [replies, setReplies] = useState(0);
|
||||||
const [reposts, setReposts] = useState(0);
|
const [reposts, setReposts] = useState(0);
|
||||||
const [zaps, setZaps] = useState(0);
|
const [zaps, setZaps] = useState(0);
|
||||||
|
|
||||||
useSWRSubscription(id ? ["note-metadata", id] : null, ([, key]) => {
|
useSWRSubscription(id ? ["note-metadata", id] : null, () => {
|
||||||
const unsubscribe = pool.subscribe(
|
const sub: NDKSubscription = ndk.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,
|
|
||||||
{
|
{
|
||||||
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 () => {
|
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 { Dialog, Transition } from "@headlessui/react";
|
||||||
|
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
|
||||||
import { ReplyIcon } from "@shared/icons";
|
import { ReplyIcon } from "@shared/icons";
|
||||||
import { Image } from "@shared/image";
|
import { Image } from "@shared/image";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { useActiveAccount } from "@stores/accounts";
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { compactNumber } from "@utils/number";
|
import { compactNumber } from "@utils/number";
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
|
||||||
import { Fragment, useContext, useEffect, useState } from "react";
|
import { Fragment, useContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
export function NoteReply({ id, replies }: { id: string; replies: number }) {
|
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 account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const [count, setCount] = useState(0);
|
const [count, setCount] = useState(0);
|
||||||
@ -26,19 +25,19 @@ export function NoteReply({ id, replies }: { id: string; replies: number }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const submitEvent = () => {
|
const submitEvent = () => {
|
||||||
const event: any = {
|
const signer = new NDKPrivateKeySigner(account.privkey);
|
||||||
content: value,
|
ndk.signer = signer;
|
||||||
created_at: dateToUnix(),
|
|
||||||
kind: 1,
|
|
||||||
pubkey: account.pubkey,
|
|
||||||
tags: [["e", id]],
|
|
||||||
};
|
|
||||||
|
|
||||||
event.id = getEventHash(event);
|
const event = new NDKEvent(ndk);
|
||||||
event.sig = getSignature(event, account.privkey);
|
// build event
|
||||||
|
event.content = value;
|
||||||
|
event.kind = 1;
|
||||||
|
event.created_at = dateToUnix();
|
||||||
|
event.pubkey = account.pubkey;
|
||||||
|
event.tags = [["e", id]];
|
||||||
|
|
||||||
// publish event
|
// publish event
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
event.publish();
|
||||||
|
|
||||||
// close modal
|
// close modal
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
@ -96,7 +95,7 @@ export function NoteReply({ id, replies }: { id: string; replies: number }) {
|
|||||||
<div>
|
<div>
|
||||||
<div className="relative h-11 w-11 shrink-0 overflow-hidden rounded-md border border-white/10">
|
<div className="relative h-11 w-11 shrink-0 overflow-hidden rounded-md border border-white/10">
|
||||||
<Image
|
<Image
|
||||||
src={account?.picture}
|
src={account?.image}
|
||||||
alt="user's avatar"
|
alt="user's avatar"
|
||||||
className="h-11 w-11 rounded-md object-cover"
|
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 { RepostIcon } from "@shared/icons";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { useActiveAccount } from "@stores/accounts";
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { compactNumber } from "@utils/number";
|
import { compactNumber } from "@utils/number";
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
export function NoteRepost({
|
export function NoteRepost({
|
||||||
@ -12,7 +11,7 @@ export function NoteRepost({
|
|||||||
pubkey,
|
pubkey,
|
||||||
reposts,
|
reposts,
|
||||||
}: { id: string; pubkey: string; reposts: number }) {
|
}: { id: string; pubkey: string; reposts: number }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
const account = useActiveAccount((state: any) => state.account);
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const [count, setCount] = useState(0);
|
const [count, setCount] = useState(0);
|
||||||
@ -20,22 +19,22 @@ export function NoteRepost({
|
|||||||
const submitEvent = (e: any) => {
|
const submitEvent = (e: any) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const event: any = {
|
const signer = new NDKPrivateKeySigner(account.privkey);
|
||||||
content: "",
|
ndk.signer = signer;
|
||||||
kind: 6,
|
|
||||||
tags: [
|
|
||||||
["e", id],
|
|
||||||
["p", pubkey],
|
|
||||||
],
|
|
||||||
created_at: dateToUnix(),
|
|
||||||
pubkey: account.pubkey,
|
|
||||||
};
|
|
||||||
|
|
||||||
event.id = getEventHash(event);
|
const event = new NDKEvent(ndk);
|
||||||
event.sig = getSignature(event, account.privkey);
|
// 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
|
// publish event
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
event.publish();
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
setCount(count + 1);
|
setCount(count + 1);
|
||||||
|
@ -17,11 +17,13 @@ export function LinkPreview({ urls }: { urls: string[] }) {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
<Image
|
{data["og:image"] && (
|
||||||
src={data["og:image"]}
|
<Image
|
||||||
alt={urls[0]}
|
src={data["og:image"]}
|
||||||
className="w-full h-auto border-t-lg object-cover"
|
alt={urls[0]}
|
||||||
/>
|
className="w-full h-auto border-t-lg object-cover"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="flex flex-col gap-2 px-3 py-3">
|
<div className="flex flex-col gap-2 px-3 py-3">
|
||||||
<h5 className="leading-none font-medium text-zinc-200">
|
<h5 className="leading-none font-medium text-zinc-200">
|
||||||
{data["og:title"]}
|
{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 { RelayContext } from "@shared/relayProvider";
|
||||||
import { useActiveAccount } from "@stores/accounts";
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { useProfile } from "@utils/hooks/useProfile";
|
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
|
|
||||||
export function NoteReplyForm({ id }: { id: string }) {
|
export function NoteReplyForm({ id }: { id: string }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
const account = useActiveAccount((state: any) => state.account);
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
|
|
||||||
const submitEvent = () => {
|
const submitEvent = () => {
|
||||||
const event: any = {
|
const signer = new NDKPrivateKeySigner(account.privkey);
|
||||||
content: value,
|
ndk.signer = signer;
|
||||||
created_at: dateToUnix(),
|
|
||||||
kind: 1,
|
|
||||||
pubkey: account.pubkey,
|
|
||||||
tags: [["e", id]],
|
|
||||||
};
|
|
||||||
|
|
||||||
event.id = getEventHash(event);
|
const event = new NDKEvent(ndk);
|
||||||
event.sig = getSignature(event, account.privkey);
|
// build event
|
||||||
|
event.content = value;
|
||||||
|
event.kind = 1;
|
||||||
|
event.created_at = dateToUnix();
|
||||||
|
event.pubkey = account.pubkey;
|
||||||
|
event.tags = [["e", id]];
|
||||||
|
|
||||||
// publish note
|
// publish event
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
event.publish();
|
||||||
|
|
||||||
// reset form
|
// reset form
|
||||||
setValue("");
|
setValue("");
|
||||||
|
@ -1,34 +1,35 @@
|
|||||||
import { NoteReplyForm } from "@app/note/components/replies/form";
|
import { NoteReplyForm } from "@app/note/components/replies/form";
|
||||||
import { Reply } from "@app/note/components/replies/item";
|
import { Reply } from "@app/note/components/replies/item";
|
||||||
|
import { NostrEvent } from "@nostr-dev-kit/ndk";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { READONLY_RELAYS } from "@stores/constants";
|
|
||||||
import { sortEvents } from "@utils/transform";
|
import { sortEvents } from "@utils/transform";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import useSWRSubscription from "swr/subscription";
|
import useSWRSubscription from "swr/subscription";
|
||||||
|
|
||||||
export function RepliesList({ id }: { id: string }) {
|
export function RepliesList({ id }: { id: string }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
|
|
||||||
const { data, error } = useSWRSubscription(
|
const { data, error } = useSWRSubscription(
|
||||||
id ? ["note-replies", id] : null,
|
id ? ["note-replies", id] : null,
|
||||||
([, key], { next }) => {
|
([, key], { next }) => {
|
||||||
// subscribe to note
|
// subscribe to note
|
||||||
const unsubscribe = pool.subscribe(
|
const sub = ndk.subscribe(
|
||||||
[
|
{
|
||||||
{
|
"#e": [key],
|
||||||
"#e": [key],
|
kinds: [1],
|
||||||
kinds: [1],
|
limit: 20,
|
||||||
limit: 20,
|
},
|
||||||
},
|
{
|
||||||
],
|
closeOnEose: true,
|
||||||
READONLY_RELAYS,
|
|
||||||
(event: any) => {
|
|
||||||
next(null, (prev: any) => (prev ? [...prev, event] : [event]));
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
sub.addListener("event", (event: NostrEvent) => {
|
||||||
|
next(null, (prev: any) => (prev ? [...prev, event] : [event]));
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribe();
|
sub.stop();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -3,8 +3,8 @@ import { Kind1063 } from "@app/note/components/kind1063";
|
|||||||
import { NoteMetadata } from "@app/note/components/metadata";
|
import { NoteMetadata } from "@app/note/components/metadata";
|
||||||
import { NoteSkeleton } from "@app/note/components/skeleton";
|
import { NoteSkeleton } from "@app/note/components/skeleton";
|
||||||
import { NoteDefaultUser } from "@app/note/components/user/default";
|
import { NoteDefaultUser } from "@app/note/components/user/default";
|
||||||
|
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { READONLY_RELAYS } from "@stores/constants";
|
|
||||||
import { noteParser } from "@utils/parser";
|
import { noteParser } from "@utils/parser";
|
||||||
import { memo, useContext } from "react";
|
import { memo, useContext } from "react";
|
||||||
import useSWRSubscription from "swr/subscription";
|
import useSWRSubscription from "swr/subscription";
|
||||||
@ -23,31 +23,22 @@ export const RootNote = memo(function RootNote({
|
|||||||
id,
|
id,
|
||||||
fallback,
|
fallback,
|
||||||
}: { id: string; fallback?: any }) {
|
}: { id: string; fallback?: any }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
const parseFallback = isJSON(fallback) ? JSON.parse(fallback) : null;
|
const parseFallback = isJSON(fallback) ? JSON.parse(fallback) : null;
|
||||||
|
|
||||||
const { data, error } = useSWRSubscription(
|
const { data, error } = useSWRSubscription(
|
||||||
parseFallback ? null : id,
|
parseFallback ? null : id,
|
||||||
(key, { next }) => {
|
(key, { next }) => {
|
||||||
const unsubscribe = pool.subscribe(
|
const sub = ndk.subscribe({
|
||||||
[
|
ids: [key],
|
||||||
{
|
});
|
||||||
ids: [key],
|
|
||||||
},
|
sub.addListener("event", (event: NDKEvent) => {
|
||||||
],
|
next(null, event);
|
||||||
READONLY_RELAYS,
|
});
|
||||||
(event: any) => {
|
|
||||||
next(null, event);
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
unsubscribeOnEose: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribe();
|
sub.stop();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -19,7 +19,7 @@ export function NoteDefaultUser({
|
|||||||
<Popover className="relative flex items-start gap-3">
|
<Popover className="relative flex items-start gap-3">
|
||||||
<Popover.Button className="h-11 w-11 shrink-0 overflow-hidden rounded-md bg-zinc-900">
|
<Popover.Button className="h-11 w-11 shrink-0 overflow-hidden rounded-md bg-zinc-900">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={pubkey}
|
alt={pubkey}
|
||||||
className="h-11 w-11 object-cover"
|
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">
|
<div className="flex items-start gap-2.5 border-b border-zinc-800 px-3 py-3">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={pubkey}
|
alt={pubkey}
|
||||||
className="h-14 w-14 shrink-0 rounded-lg object-cover"
|
className="h-14 w-14 shrink-0 rounded-lg object-cover"
|
||||||
/>
|
/>
|
||||||
<div className="flex w-full flex-1 flex-col gap-2">
|
<div className="flex w-full flex-1 flex-col gap-2">
|
||||||
<div className="inline-flex w-2/3 flex-col gap-0.5">
|
<div className="inline-flex w-2/3 flex-col gap-0.5">
|
||||||
<h5 className="text-base font-semibold leading-none">
|
<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" />
|
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
|
||||||
)}
|
)}
|
||||||
</h5>
|
</h5>
|
||||||
|
@ -20,7 +20,7 @@ export function NoteQuoteUser({
|
|||||||
<div className="group flex items-center gap-2">
|
<div className="group flex items-center gap-2">
|
||||||
<div className="relative h-6 w-6 shrink-0 rounded">
|
<div className="relative h-6 w-6 shrink-0 rounded">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={pubkey}
|
alt={pubkey}
|
||||||
className="h-6 w-6 rounded object-cover"
|
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="group flex items-start gap-2.5">
|
||||||
<div className="relative h-11 w-11 shrink-0 rounded-md">
|
<div className="relative h-11 w-11 shrink-0 rounded-md">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={pubkey}
|
alt={pubkey}
|
||||||
className="h-11 w-11 rounded-md object-cover"
|
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 className="relative flex items-start gap-3">
|
||||||
<Popover.Button className="h-11 w-11 shrink-0 overflow-hidden rounded-md bg-zinc-900">
|
<Popover.Button className="h-11 w-11 shrink-0 overflow-hidden rounded-md bg-zinc-900">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={pubkey}
|
alt={pubkey}
|
||||||
className="h-11 w-11 rounded-md object-cover"
|
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">
|
<div className="flex items-start gap-2.5 border-b border-zinc-800 px-3 py-3">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={pubkey}
|
alt={pubkey}
|
||||||
className="h-14 w-14 shrink-0 rounded-lg object-cover"
|
className="h-14 w-14 shrink-0 rounded-lg object-cover"
|
||||||
/>
|
/>
|
||||||
<div className="flex w-full flex-1 flex-col gap-2">
|
<div className="flex w-full flex-1 flex-col gap-2">
|
||||||
<div className="inline-flex w-2/3 flex-col gap-0.5">
|
<div className="inline-flex w-2/3 flex-col gap-0.5">
|
||||||
<h5 className="text-base font-semibold leading-none">
|
<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" />
|
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
|
||||||
)}
|
)}
|
||||||
</h5>
|
</h5>
|
||||||
|
@ -2,41 +2,19 @@ import { Kind1 } from "@app/note/components/kind1";
|
|||||||
import { NoteMetadata } from "@app/note/components/metadata";
|
import { NoteMetadata } from "@app/note/components/metadata";
|
||||||
import { RepliesList } from "@app/note/components/replies/list";
|
import { RepliesList } from "@app/note/components/replies/list";
|
||||||
import { NoteDefaultUser } from "@app/note/components/user/default";
|
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 { usePageContext } from "@utils/hooks/usePageContext";
|
||||||
import { noteParser } from "@utils/parser";
|
import { noteParser } from "@utils/parser";
|
||||||
import { useContext } from "react";
|
import { getNoteByID } from "@utils/storage";
|
||||||
import useSWRSubscription from "swr/subscription";
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
const fetcher = ([, id]) => getNoteByID(id);
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const pool: any = useContext(RelayContext);
|
|
||||||
const pageContext = usePageContext();
|
const pageContext = usePageContext();
|
||||||
const searchParams: any = pageContext.urlParsed.search;
|
const searchParams: any = pageContext.urlParsed.search;
|
||||||
|
|
||||||
const noteID = searchParams.id;
|
const noteID = searchParams.id;
|
||||||
|
|
||||||
const { data, error } = useSWRSubscription(
|
const { data, error } = useSWR(["note", noteID], fetcher);
|
||||||
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 content = !error && data ? noteParser(data) : null;
|
const content = !error && data ? noteParser(data) : null;
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { NDKFilter } from "@nostr-dev-kit/ndk";
|
||||||
import { LumeIcon } from "@shared/icons";
|
import { LumeIcon } from "@shared/icons";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { useActiveAccount } from "@stores/accounts";
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { READONLY_RELAYS } from "@stores/constants";
|
|
||||||
import { dateToUnix, getHourAgo } from "@utils/date";
|
import { dateToUnix, getHourAgo } from "@utils/date";
|
||||||
import {
|
import {
|
||||||
addToBlacklist,
|
addToBlacklist,
|
||||||
@ -9,9 +9,7 @@ import {
|
|||||||
createChat,
|
createChat,
|
||||||
createNote,
|
createNote,
|
||||||
} from "@utils/storage";
|
} from "@utils/storage";
|
||||||
import { getParentID } from "@utils/transform";
|
import { useContext, useEffect, useRef } from "react";
|
||||||
import { useCallback, useContext, useRef } from "react";
|
|
||||||
import useSWRSubscription from "swr/subscription";
|
|
||||||
import { navigate } from "vite-plugin-ssr/client/router";
|
import { navigate } from "vite-plugin-ssr/client/router";
|
||||||
|
|
||||||
let totalNotes: number;
|
let totalNotes: number;
|
||||||
@ -21,127 +19,69 @@ if (typeof window !== "undefined") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
|
const now = useRef(new Date());
|
||||||
|
|
||||||
const [account, lastLogin] = useActiveAccount((state: any) => [
|
const [account, lastLogin] = useActiveAccount((state: any) => [
|
||||||
state.account,
|
state.account,
|
||||||
state.lastLogin,
|
state.lastLogin,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const now = useRef(new Date());
|
async function fetchNotes() {
|
||||||
const eose = useRef(0);
|
try {
|
||||||
|
const follows = JSON.parse(account.follows);
|
||||||
|
let queryNoteSince: number;
|
||||||
|
|
||||||
const getQuery = useCallback(() => {
|
if (totalNotes === 0) {
|
||||||
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 {
|
|
||||||
queryNoteSince = dateToUnix(getHourAgo(48, now.current));
|
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
|
async function fetchChannelBlacklist() {
|
||||||
query.push({
|
try {
|
||||||
kinds: [1, 6],
|
const filter: NDKFilter = {
|
||||||
authors: follows,
|
authors: [account.pubkey],
|
||||||
since: queryNoteSince,
|
kinds: [43, 44],
|
||||||
});
|
since: lastLogin,
|
||||||
|
};
|
||||||
|
|
||||||
// kind 4 (chats) query
|
const events = await ndk.fetchEvents(filter);
|
||||||
query.push({
|
events.forEach((event) => {
|
||||||
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) => {
|
|
||||||
switch (event.kind) {
|
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:
|
case 43:
|
||||||
if (event.tags[0][0] === "e") {
|
if (event.tags[0][0] === "e") {
|
||||||
addToBlacklist(account.id, event.tags[0][1], 43, 1);
|
addToBlacklist(account.id, event.tags[0][1], 43, 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
// mute user (channel only)
|
|
||||||
case 44:
|
case 44:
|
||||||
if (event.tags[0][0] === "p") {
|
if (event.tags[0][0] === "p") {
|
||||||
addToBlacklist(account.id, event.tags[0][1], 44, 1);
|
addToBlacklist(account.id, event.tags[0][1], 44, 1);
|
||||||
@ -150,37 +90,85 @@ export function Page() {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
undefined,
|
|
||||||
() => {
|
|
||||||
eose.current += 1;
|
|
||||||
if (eose.current === READONLY_RELAYS.length) {
|
|
||||||
timeout = setTimeout(
|
|
||||||
() => navigate("/app/space", { overwriteLastHistoryEntry: true }),
|
|
||||||
2000,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
unsubscribeOnEose: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
return true;
|
||||||
unsubscribe();
|
} catch (e) {
|
||||||
clearTimeout(timeout);
|
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 (
|
return (
|
||||||
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-black dark:text-white">
|
<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">
|
<div className="relative h-full overflow-hidden">
|
||||||
{/* dragging area */}
|
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className="absolute left-0 top-0 z-20 h-16 w-full bg-transparent"
|
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="relative flex h-full flex-col items-center justify-center">
|
||||||
<div className="flex flex-col items-center gap-2">
|
<div className="flex flex-col items-center gap-2">
|
||||||
<LumeIcon className="h-16 w-16 text-black dark:text-white" />
|
<LumeIcon className="h-16 w-16 text-black dark:text-white" />
|
||||||
|
@ -1,24 +1,23 @@
|
|||||||
import { Dialog, Transition } from "@headlessui/react";
|
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 { Image } from "@shared/image";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { useActiveAccount } from "@stores/accounts";
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
|
||||||
import { open } from "@tauri-apps/api/dialog";
|
import { open } from "@tauri-apps/api/dialog";
|
||||||
import { Body, fetch } from "@tauri-apps/api/http";
|
import { Body, fetch } from "@tauri-apps/api/http";
|
||||||
import { createBlobFromFile } from "@utils/createBlobFromFile";
|
import { createBlobFromFile } from "@utils/createBlobFromFile";
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
|
||||||
import { Fragment, useContext, useEffect, useRef, useState } from "react";
|
import { Fragment, useContext, useEffect, useRef, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
export function AddImageBlock({ parentState }: { parentState: any }) {
|
export function AddImageBlock({ parentState }: { parentState: any }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
|
|
||||||
const [account, addBlock] = useActiveAccount((state: any) => [
|
const [account, addBlock] = useActiveAccount((state: any) => [
|
||||||
state.account,
|
state.account,
|
||||||
state.addBlock,
|
state.addBlock,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [isOpen, setIsOpen] = useState(true);
|
const [isOpen, setIsOpen] = useState(true);
|
||||||
const [image, setImage] = useState("");
|
const [image, setImage] = useState("");
|
||||||
@ -91,19 +90,19 @@ export function AddImageBlock({ parentState }: { parentState: any }) {
|
|||||||
const onSubmit = (data: any) => {
|
const onSubmit = (data: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const event: any = {
|
const signer = new NDKPrivateKeySigner(account.privkey);
|
||||||
content: data.title,
|
ndk.signer = signer;
|
||||||
created_at: dateToUnix(),
|
|
||||||
kind: 1063,
|
|
||||||
pubkey: account.pubkey,
|
|
||||||
tags: tags.current,
|
|
||||||
};
|
|
||||||
|
|
||||||
event.id = getEventHash(event);
|
const event = new NDKEvent(ndk);
|
||||||
event.sig = getSignature(event, account.privkey);
|
// build event
|
||||||
|
event.content = data.title;
|
||||||
|
event.kind = 1063;
|
||||||
|
event.created_at = dateToUnix();
|
||||||
|
event.pubkey = account.pubkey;
|
||||||
|
event.tags = tags.current;
|
||||||
|
|
||||||
// publish channel
|
// publish event
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
event.publish();
|
||||||
|
|
||||||
// insert to database
|
// insert to database
|
||||||
addBlock(0, data.title, data.content);
|
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 { useActiveAccount } from "@stores/accounts";
|
||||||
import { useChannels } from "@stores/channels";
|
import { useChannels } from "@stores/channels";
|
||||||
import { useChatMessages, useChats } from "@stores/chats";
|
import { useChatMessages, useChats } from "@stores/chats";
|
||||||
import { DEFAULT_AVATAR, READONLY_RELAYS } from "@stores/constants";
|
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||||
import { dateToUnix } from "@utils/date";
|
|
||||||
import { usePageContext } from "@utils/hooks/usePageContext";
|
import { usePageContext } from "@utils/hooks/usePageContext";
|
||||||
import { useProfile } from "@utils/hooks/useProfile";
|
import { useProfile } from "@utils/hooks/useProfile";
|
||||||
import { sendNativeNotification } from "@utils/notification";
|
import { sendNativeNotification } from "@utils/notification";
|
||||||
import { createNote } from "@utils/storage";
|
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import useSWRSubscription from "swr/subscription";
|
import useSWRSubscription from "swr/subscription";
|
||||||
|
|
||||||
export function ActiveAccount({ data }: { data: any }) {
|
export function ActiveAccount({ data }: { data: any }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
const account = useActiveAccount((state: any) => state.account);
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const pageContext = usePageContext();
|
const pageContext = usePageContext();
|
||||||
@ -35,60 +33,38 @@ export function ActiveAccount({ data }: { data: any }) {
|
|||||||
() => {
|
() => {
|
||||||
const follows = JSON.parse(account.follows);
|
const follows = JSON.parse(account.follows);
|
||||||
// subscribe to channel
|
// subscribe to channel
|
||||||
const unsubscribe = pool.subscribe(
|
const sub = ndk.subscribe({
|
||||||
[
|
"#p": [data.pubkey],
|
||||||
{
|
since: lastLogin,
|
||||||
kinds: [1, 6],
|
});
|
||||||
authors: follows,
|
|
||||||
since: dateToUnix(),
|
sub.addListener("event", (event) => {
|
||||||
},
|
switch (event.kind) {
|
||||||
{
|
case 4:
|
||||||
"#p": [data.pubkey],
|
if (!isChatPage) {
|
||||||
since: lastLogin,
|
// save
|
||||||
},
|
saveChat(data.pubkey, event);
|
||||||
],
|
// update state
|
||||||
READONLY_RELAYS,
|
notifyChat(event.pubkey);
|
||||||
(event) => {
|
// send native notifiation
|
||||||
switch (event.kind) {
|
sendNativeNotification("You've received new message");
|
||||||
case 1:
|
|
||||||
case 6: {
|
|
||||||
createNote(
|
|
||||||
event.id,
|
|
||||||
account.id,
|
|
||||||
event.pubkey,
|
|
||||||
event.kind,
|
|
||||||
event.tags,
|
|
||||||
event.content,
|
|
||||||
event.created_at,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case 4:
|
break;
|
||||||
if (!isChatPage) {
|
case 42:
|
||||||
// save
|
if (!isChannelPage) {
|
||||||
saveChat(data.pubkey, event);
|
// update state
|
||||||
// update state
|
notifyChannel(event);
|
||||||
notifyChat(event.pubkey);
|
// send native notifiation
|
||||||
// send native notifiation
|
sendNativeNotification(event.content);
|
||||||
sendNativeNotification("You've received new message");
|
}
|
||||||
}
|
break;
|
||||||
break;
|
default:
|
||||||
case 42:
|
break;
|
||||||
if (!isChannelPage) {
|
}
|
||||||
// update state
|
});
|
||||||
notifyChannel(event);
|
|
||||||
// send native notifiation
|
|
||||||
sendNativeNotification(event.content);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribe();
|
sub.stop();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -96,7 +72,7 @@ export function ActiveAccount({ data }: { data: any }) {
|
|||||||
return (
|
return (
|
||||||
<button type="button" className="relative h-11 w-11 overflow-hidden">
|
<button type="button" className="relative h-11 w-11 overflow-hidden">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={data.npub}
|
alt={data.npub}
|
||||||
className="h-11 w-11 rounded-md object-cover"
|
className="h-11 w-11 rounded-md object-cover"
|
||||||
/>
|
/>
|
||||||
|
@ -9,7 +9,7 @@ export function InactiveAccount({ data }: { data: any }) {
|
|||||||
return (
|
return (
|
||||||
<div className="relative h-11 w-11 shrink rounded-md">
|
<div className="relative h-11 w-11 shrink rounded-md">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={data.npub}
|
alt={data.npub}
|
||||||
className="h-11 w-11 rounded-lg object-cover"
|
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 { ImageUploader } from "@shared/composer/imageUploader";
|
||||||
import { TrashIcon } from "@shared/icons";
|
import { TrashIcon } from "@shared/icons";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
|
||||||
import { useCallback, useContext, useMemo, useState } from "react";
|
import { useCallback, useContext, useMemo, useState } from "react";
|
||||||
import { Node, Transforms, createEditor } from "slate";
|
import { Node, Transforms, createEditor } from "slate";
|
||||||
import { withHistory } from "slate-history";
|
import { withHistory } from "slate-history";
|
||||||
@ -59,12 +58,13 @@ const ImagePreview = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function Post({ pubkey, privkey }: { pubkey: string; privkey: string }) {
|
export function Post({ pubkey, privkey }: { pubkey: string; privkey: string }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
|
|
||||||
const editor = useMemo(
|
const editor = useMemo(
|
||||||
() => withReact(withImages(withHistory(createEditor()))),
|
() => withReact(withImages(withHistory(createEditor()))),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [content, setContent] = useState<Node[]>([
|
const [content, setContent] = useState<Node[]>([
|
||||||
{
|
{
|
||||||
children: [
|
children: [
|
||||||
@ -82,20 +82,19 @@ export function Post({ pubkey, privkey }: { pubkey: string; privkey: string }) {
|
|||||||
const submit = () => {
|
const submit = () => {
|
||||||
// serialize content
|
// serialize content
|
||||||
const serializedContent = serialize(content);
|
const serializedContent = serialize(content);
|
||||||
console.log(serializedContent);
|
|
||||||
|
|
||||||
const event: any = {
|
const signer = new NDKPrivateKeySigner(privkey);
|
||||||
content: serializedContent,
|
ndk.signer = signer;
|
||||||
created_at: dateToUnix(),
|
|
||||||
kind: 1,
|
|
||||||
pubkey: pubkey,
|
|
||||||
tags: [],
|
|
||||||
};
|
|
||||||
event.id = getEventHash(event);
|
|
||||||
event.sig = getSignature(event, privkey);
|
|
||||||
|
|
||||||
// publish note
|
const event = new NDKEvent(ndk);
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
event.kind = 1;
|
||||||
|
event.content = serializedContent;
|
||||||
|
event.created_at = dateToUnix();
|
||||||
|
event.pubkey = pubkey;
|
||||||
|
event.tags = [];
|
||||||
|
|
||||||
|
// publish event
|
||||||
|
event.publish();
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderElement = useCallback((props: any) => {
|
const renderElement = useCallback((props: any) => {
|
||||||
|
@ -9,7 +9,7 @@ export function User({ pubkey }: { pubkey: string }) {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="h-8 w-8 shrink-0 overflow-hidden rounded bg-zinc-900">
|
<div className="h-8 w-8 shrink-0 overflow-hidden rounded bg-zinc-900">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.image || DEFAULT_AVATAR}
|
||||||
alt={pubkey}
|
alt={pubkey}
|
||||||
className="h-8 w-8 object-cover"
|
className="h-8 w-8 object-cover"
|
||||||
loading="auto"
|
loading="auto"
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
import { FULL_RELAYS } from "@stores/constants";
|
import { initNDK } from "@libs/ndk";
|
||||||
import { RelayPool } from "nostr-relaypool";
|
import NDK from "@nostr-dev-kit/ndk";
|
||||||
import { createContext } from "react";
|
import { createContext } from "react";
|
||||||
|
|
||||||
export const RelayContext = createContext({});
|
export const RelayContext = createContext<NDK>(null);
|
||||||
|
const ndk = await initNDK();
|
||||||
const pool = new RelayPool(FULL_RELAYS, {
|
|
||||||
useEventCache: false,
|
|
||||||
subscriptionCache: true,
|
|
||||||
logErrorsAndNotices: false,
|
|
||||||
logSubscriptions: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function RelayProvider({ children }: { children: React.ReactNode }) {
|
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://welcome.nostr.wine",
|
||||||
"wss://relay.nostr.band",
|
"wss://relay.nostr.band",
|
||||||
"wss://relay.damus.io",
|
"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 { RelayContext } from "@shared/relayProvider";
|
||||||
import { useActiveAccount } from "@stores/accounts";
|
|
||||||
import { READONLY_RELAYS } from "@stores/constants";
|
|
||||||
import { createNote, getNoteByID } from "@utils/storage";
|
import { createNote, getNoteByID } from "@utils/storage";
|
||||||
import { getParentID } from "@utils/transform";
|
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import useSWR from "swr";
|
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) {
|
export function useEvent(id: string) {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
const account = useActiveAccount((state: any) => state.account);
|
const { data } = useSWR(["note", ndk, id], fetcher);
|
||||||
|
|
||||||
const { data: cache } = useSWR(["event", id], fetcher);
|
return data;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
|
import NDK from "@nostr-dev-kit/ndk";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { METADATA_RELAY } from "@stores/constants";
|
|
||||||
import { createPleb, getPleb } from "@utils/storage";
|
import { createPleb, getPleb } from "@utils/storage";
|
||||||
import { nip19 } from "nostr-tools";
|
import { nip19 } from "nostr-tools";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import useSWRSubscription from "swr/subscription";
|
|
||||||
|
|
||||||
const fetcher = async (key: string) => {
|
const fetcher = async ([, ndk, key]) => {
|
||||||
let npub: string;
|
let npub: string;
|
||||||
|
|
||||||
if (key.substring(0, 4) === "npub") {
|
if (key.substring(0, 4) === "npub") {
|
||||||
@ -18,59 +17,27 @@ const fetcher = async (key: string) => {
|
|||||||
const current = Math.floor(Date.now() / 1000);
|
const current = Math.floor(Date.now() / 1000);
|
||||||
const result = await getPleb(npub);
|
const result = await getPleb(npub);
|
||||||
|
|
||||||
if (result && result.created_at + 86400 < current) {
|
if (result && result.created_at + 86400 > current) {
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
const user = ndk.getUser({ npub });
|
||||||
|
await user.fetchProfile();
|
||||||
|
await createPleb(key, user.profile);
|
||||||
|
|
||||||
|
return user.profile;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useProfile(key: string) {
|
export function useProfile(key: string) {
|
||||||
const pool: any = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
|
const { data, error, isLoading } = useSWR(["profile", ndk, key], fetcher, {
|
||||||
const {
|
|
||||||
data: cache,
|
|
||||||
error,
|
|
||||||
isLoading,
|
|
||||||
} = useSWR(key, fetcher, {
|
|
||||||
revalidateIfStale: false,
|
revalidateIfStale: false,
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
revalidateOnReconnect: true,
|
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 {
|
return {
|
||||||
user: newest ? newest : cache,
|
user: data,
|
||||||
isLoading,
|
isLoading,
|
||||||
isError: error,
|
isError: error,
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { NDKTag, NDKUserProfile } from "@nostr-dev-kit/ndk";
|
||||||
import { getParentID } from "@utils/transform";
|
import { getParentID } from "@utils/transform";
|
||||||
import { nip19 } from "nostr-tools";
|
import { nip19 } from "nostr-tools";
|
||||||
import Database from "tauri-plugin-sql-api";
|
import Database from "tauri-plugin-sql-api";
|
||||||
@ -76,10 +77,9 @@ export async function getPleb(npub: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create pleb
|
// create pleb
|
||||||
export async function createPleb(key: string, json: any) {
|
export async function createPleb(key: string, data: NDKUserProfile) {
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
const data = JSON.parse(json.content);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
let npub: string;
|
let npub: string;
|
||||||
|
|
||||||
if (key.substring(0, 4) === "npub") {
|
if (key.substring(0, 4) === "npub") {
|
||||||
@ -89,21 +89,20 @@ export async function createPleb(key: string, json: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return await db.execute(
|
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,
|
npub,
|
||||||
data.display_name || data.displayName,
|
|
||||||
data.name,
|
data.name,
|
||||||
data.username,
|
data.displayName,
|
||||||
data.about,
|
data.image,
|
||||||
data.bio,
|
|
||||||
data.website,
|
|
||||||
data.picture || data.image,
|
|
||||||
data.banner,
|
data.banner,
|
||||||
|
data.bio,
|
||||||
data.nip05,
|
data.nip05,
|
||||||
data.lud06,
|
data.lud06,
|
||||||
data.lud16,
|
data.lud16,
|
||||||
data.created_at,
|
data.about,
|
||||||
|
data.zapService,
|
||||||
|
now,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -216,39 +215,41 @@ export async function getLatestNotes(time: number) {
|
|||||||
// create note
|
// create note
|
||||||
export async function createNote(
|
export async function createNote(
|
||||||
event_id: string,
|
event_id: string,
|
||||||
account_id: number,
|
|
||||||
pubkey: string,
|
pubkey: string,
|
||||||
kind: number,
|
kind: number,
|
||||||
tags: string[],
|
tags: any,
|
||||||
content: string,
|
content: string,
|
||||||
created_at: number,
|
created_at: number,
|
||||||
) {
|
) {
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
|
const account = await getActiveAccount();
|
||||||
const parentID = getParentID(tags, event_id);
|
const parentID = getParentID(tags, event_id);
|
||||||
|
|
||||||
return await db.execute(
|
return await db.execute(
|
||||||
"INSERT INTO notes (event_id, account_id, pubkey, kind, tags, content, created_at, parent_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?);",
|
"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],
|
[event_id, account.id, pubkey, kind, tags, content, created_at, parentID],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create reply note
|
// create reply note
|
||||||
export async function createReplyNote(
|
export async function createReplyNote(
|
||||||
event_id: string,
|
event_id: string,
|
||||||
account_id: number,
|
|
||||||
pubkey: string,
|
pubkey: string,
|
||||||
kind: number,
|
kind: number,
|
||||||
tags: string[],
|
tags: any,
|
||||||
content: string,
|
content: string,
|
||||||
created_at: number,
|
created_at: number,
|
||||||
parent_comment_id: string,
|
parent_comment_id: string,
|
||||||
) {
|
) {
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
|
const account = await getActiveAccount();
|
||||||
const parentID = getParentID(tags, event_id);
|
const parentID = getParentID(tags, event_id);
|
||||||
|
|
||||||
return await db.execute(
|
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,
|
event_id,
|
||||||
account_id,
|
account.id,
|
||||||
pubkey,
|
pubkey,
|
||||||
kind,
|
kind,
|
||||||
tags,
|
tags,
|
||||||
@ -298,7 +299,7 @@ export async function updateChannelMetadata(event_id: string, value: string) {
|
|||||||
|
|
||||||
return await db.execute(
|
return await db.execute(
|
||||||
"UPDATE channels SET name = ?, picture = ?, about = ? WHERE event_id = ?;",
|
"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 destr from "destr";
|
||||||
|
import { nip19 } from "nostr-tools";
|
||||||
|
|
||||||
export function truncateContent(str, n) {
|
export function truncateContent(str, n) {
|
||||||
return str.length > n ? `${str.slice(0, n - 1)}…` : str;
|
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
|
// convert NIP-02 to array of pubkey
|
||||||
export function nip02ToArray(tags: any) {
|
export function nip02ToArray(tags: any) {
|
||||||
const arr = [];
|
const arr = [];
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@app/*": ["src/app/*"],
|
"@app/*": ["src/app/*"],
|
||||||
|
"@libs/*": ["src/libs/*"],
|
||||||
"@shared/*": ["src/shared/*"],
|
"@shared/*": ["src/shared/*"],
|
||||||
"@stores/*": ["src/stores/*"],
|
"@stores/*": ["src/stores/*"],
|
||||||
"@utils/*": ["src/utils/*"]
|
"@utils/*": ["src/utils/*"]
|
||||||
|
Loading…
Reference in New Issue
Block a user